1 // Copyright 2022 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #include "pw_thread/thread_snapshot_service.h"
16
17 #include "pw_protobuf/decoder.h"
18 #include "pw_rpc/raw/server_reader_writer.h"
19 #include "pw_span/span.h"
20 #include "pw_thread/thread_info.h"
21 #include "pw_thread/thread_iteration.h"
22 #include "pw_thread_private/thread_snapshot_service.h"
23 #include "pw_thread_protos/thread.pwpb.h"
24 #include "pw_thread_protos/thread_snapshot_service.pwpb.h"
25 #include "pw_unit_test/framework.h"
26
27 namespace pw::thread::proto {
28 namespace {
29
30 // Iterates through each proto encoded thread in the buffer.
EncodedThreadExists(ConstByteSpan serialized_thread_buffer,ConstByteSpan thread_name)31 bool EncodedThreadExists(ConstByteSpan serialized_thread_buffer,
32 ConstByteSpan thread_name) {
33 protobuf::Decoder decoder(serialized_thread_buffer);
34 while (decoder.Next().ok()) {
35 switch (decoder.FieldNumber()) {
36 case static_cast<uint32_t>(
37 proto::pwpb::SnapshotThreadInfo::Fields::kThreads): {
38 ConstByteSpan thread_buffer;
39 EXPECT_EQ(OkStatus(), decoder.ReadBytes(&thread_buffer));
40 ConstByteSpan encoded_name;
41 EXPECT_EQ(OkStatus(), DecodeThreadName(thread_buffer, encoded_name));
42 if (encoded_name.size() == thread_name.size()) {
43 if (std::equal(thread_name.begin(),
44 thread_name.end(),
45 encoded_name.begin())) {
46 return true;
47 }
48 }
49 }
50 }
51 }
52 return false;
53 }
54
CreateThreadInfoObject(std::optional<ConstByteSpan> name,std::optional<uintptr_t> low_addr,std::optional<uintptr_t> high_addr,std::optional<uintptr_t> peak_addr)55 ThreadInfo CreateThreadInfoObject(std::optional<ConstByteSpan> name,
56 std::optional<uintptr_t> low_addr,
57 std::optional<uintptr_t> high_addr,
58 std::optional<uintptr_t> peak_addr) {
59 ThreadInfo thread_info;
60
61 if (name.has_value()) {
62 thread_info.set_thread_name(name.value());
63 }
64 if (low_addr.has_value()) {
65 thread_info.set_stack_low_addr(low_addr.value());
66 }
67 if (high_addr.has_value()) {
68 thread_info.set_stack_high_addr(high_addr.value());
69 }
70 if (peak_addr.has_value()) {
71 thread_info.set_stack_peak_addr(peak_addr.value());
72 }
73
74 return thread_info;
75 }
76
77 // Test creates a custom thread info object and proto encodes. Checks that the
78 // custom object is encoded properly.
TEST(ThreadSnapshotService,DecodeSingleThreadInfoObject)79 TEST(ThreadSnapshotService, DecodeSingleThreadInfoObject) {
80 std::array<std::byte, RequiredServiceBufferSizeWithoutVariableFields(1)>
81 encode_buffer;
82
83 proto::pwpb::SnapshotThreadInfo::MemoryEncoder encoder(encode_buffer);
84
85 ThreadInfo thread_info = CreateThreadInfoObject(
86 as_bytes(span("MyThread")), /* thread name */
87 static_cast<uintptr_t>(12345678u) /* stack low address */,
88 static_cast<uintptr_t>(0u) /* stack high address */,
89 static_cast<uintptr_t>(987654321u) /* stack peak address */);
90
91 EXPECT_EQ(OkStatus(), ProtoEncodeThreadInfo(encoder, thread_info));
92
93 ConstByteSpan response_span(encoder);
94 EXPECT_TRUE(
95 EncodedThreadExists(response_span, thread_info.thread_name().value()));
96 }
97
TEST(ThreadSnapshotService,DecodeMultipleThreadInfoObjects)98 TEST(ThreadSnapshotService, DecodeMultipleThreadInfoObjects) {
99 std::array<std::byte, RequiredServiceBufferSizeWithoutVariableFields(3)>
100 encode_buffer;
101
102 proto::pwpb::SnapshotThreadInfo::MemoryEncoder encoder(encode_buffer);
103
104 ThreadInfo thread_info_1 =
105 CreateThreadInfoObject(as_bytes(span("MyThread1")),
106 static_cast<uintptr_t>(123u),
107 static_cast<uintptr_t>(1023u),
108 static_cast<uintptr_t>(321u));
109
110 ThreadInfo thread_info_2 =
111 CreateThreadInfoObject(as_bytes(span("MyThread2")),
112 static_cast<uintptr_t>(1000u),
113 static_cast<uintptr_t>(999999u),
114 static_cast<uintptr_t>(0u));
115
116 ThreadInfo thread_info_3 =
117 CreateThreadInfoObject(as_bytes(span("MyThread3")),
118 static_cast<uintptr_t>(123u),
119 static_cast<uintptr_t>(1023u),
120 static_cast<uintptr_t>(321u));
121
122 // Encode out of order.
123 EXPECT_EQ(OkStatus(), ProtoEncodeThreadInfo(encoder, thread_info_3));
124 EXPECT_EQ(OkStatus(), ProtoEncodeThreadInfo(encoder, thread_info_1));
125 EXPECT_EQ(OkStatus(), ProtoEncodeThreadInfo(encoder, thread_info_2));
126
127 ConstByteSpan response_span(encoder);
128 EXPECT_TRUE(
129 EncodedThreadExists(response_span, thread_info_1.thread_name().value()));
130 EXPECT_TRUE(
131 EncodedThreadExists(response_span, thread_info_2.thread_name().value()));
132 EXPECT_TRUE(
133 EncodedThreadExists(response_span, thread_info_3.thread_name().value()));
134 }
135
TEST(ThreadSnapshotService,DefaultBufferSize)136 TEST(ThreadSnapshotService, DefaultBufferSize) {
137 static std::array<std::byte, RequiredServiceBufferSizeWithoutVariableFields()>
138 encode_buffer;
139
140 proto::pwpb::SnapshotThreadInfo::MemoryEncoder encoder(encode_buffer);
141
142 std::optional<uintptr_t> example_addr = std::numeric_limits<uintptr_t>::max();
143
144 ThreadInfo thread_info = CreateThreadInfoObject(
145 as_bytes(span("MyThread")), example_addr, example_addr, example_addr);
146
147 for (int i = 0; i < PW_THREAD_MAXIMUM_THREADS; i++) {
148 EXPECT_EQ(OkStatus(), ProtoEncodeThreadInfo(encoder, thread_info));
149 }
150
151 ConstByteSpan response_span(encoder);
152 EXPECT_TRUE(
153 EncodedThreadExists(response_span, thread_info.thread_name().value()));
154 }
155
TEST(ThreadSnapshotService,FailedPrecondition)156 TEST(ThreadSnapshotService, FailedPrecondition) {
157 static std::array<std::byte,
158 RequiredServiceBufferSizeWithoutVariableFields(1)>
159 encode_buffer;
160
161 proto::pwpb::SnapshotThreadInfo::MemoryEncoder encoder(encode_buffer);
162
163 ThreadInfo thread_info_no_name =
164 CreateThreadInfoObject(std::nullopt,
165 static_cast<uintptr_t>(1111111111u),
166 static_cast<uintptr_t>(2222222222u),
167 static_cast<uintptr_t>(3333333333u));
168 Status status = ProtoEncodeThreadInfo(encoder, thread_info_no_name);
169 EXPECT_EQ(status, Status::FailedPrecondition());
170 // Expected log: "Thread missing information needed by service."
171 ErrorLog(status);
172
173 // Same error log as above.
174 ThreadInfo thread_info_no_high_addr =
175 CreateThreadInfoObject(as_bytes(span("MyThread")),
176 static_cast<uintptr_t>(1111111111u),
177 std::nullopt,
178 static_cast<uintptr_t>(3333333333u));
179 EXPECT_EQ(ProtoEncodeThreadInfo(encoder, thread_info_no_high_addr),
180 Status::FailedPrecondition());
181 }
182
TEST(ThreadSnapshotService,Unimplemented)183 TEST(ThreadSnapshotService, Unimplemented) {
184 static std::array<std::byte,
185 RequiredServiceBufferSizeWithoutVariableFields(1)>
186 encode_buffer;
187
188 proto::pwpb::SnapshotThreadInfo::MemoryEncoder encoder(encode_buffer);
189
190 ThreadInfo thread_info_no_peak_addr =
191 CreateThreadInfoObject(as_bytes(span("MyThread")),
192 static_cast<uintptr_t>(0u),
193 static_cast<uintptr_t>(0u),
194 std::nullopt);
195
196 Status status = ProtoEncodeThreadInfo(encoder, thread_info_no_peak_addr);
197 EXPECT_EQ(status, Status::Unimplemented());
198 // Expected log: "Peak stack usage reporting not supported by your current OS
199 // or configuration."
200 ErrorLog(status);
201 }
202
203 } // namespace
204 } // namespace pw::thread::proto
205