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, RequiredServiceBufferSize(1)> encode_buffer;
81
82 proto::pwpb::SnapshotThreadInfo::MemoryEncoder encoder(encode_buffer);
83
84 ThreadInfo thread_info = CreateThreadInfoObject(
85 as_bytes(span("MyThread")), /* thread name */
86 static_cast<uintptr_t>(12345678u) /* stack low address */,
87 static_cast<uintptr_t>(0u) /* stack high address */,
88 static_cast<uintptr_t>(987654321u) /* stack peak address */);
89
90 EXPECT_EQ(OkStatus(), ProtoEncodeThreadInfo(encoder, thread_info));
91
92 ConstByteSpan response_span(encoder);
93 EXPECT_TRUE(
94 EncodedThreadExists(response_span, thread_info.thread_name().value()));
95 }
96
TEST(ThreadSnapshotService,DecodeMultipleThreadInfoObjects)97 TEST(ThreadSnapshotService, DecodeMultipleThreadInfoObjects) {
98 std::array<std::byte, RequiredServiceBufferSize(3)> encode_buffer;
99
100 proto::pwpb::SnapshotThreadInfo::MemoryEncoder encoder(encode_buffer);
101
102 ThreadInfo thread_info_1 =
103 CreateThreadInfoObject(as_bytes(span("MyThread1")),
104 static_cast<uintptr_t>(123u),
105 static_cast<uintptr_t>(1023u),
106 static_cast<uintptr_t>(321u));
107
108 ThreadInfo thread_info_2 =
109 CreateThreadInfoObject(as_bytes(span("MyThread2")),
110 static_cast<uintptr_t>(1000u),
111 static_cast<uintptr_t>(999999u),
112 static_cast<uintptr_t>(0u));
113
114 ThreadInfo thread_info_3 =
115 CreateThreadInfoObject(as_bytes(span("MyThread3")),
116 static_cast<uintptr_t>(123u),
117 static_cast<uintptr_t>(1023u),
118 static_cast<uintptr_t>(321u));
119
120 // Encode out of order.
121 EXPECT_EQ(OkStatus(), ProtoEncodeThreadInfo(encoder, thread_info_3));
122 EXPECT_EQ(OkStatus(), ProtoEncodeThreadInfo(encoder, thread_info_1));
123 EXPECT_EQ(OkStatus(), ProtoEncodeThreadInfo(encoder, thread_info_2));
124
125 ConstByteSpan response_span(encoder);
126 EXPECT_TRUE(
127 EncodedThreadExists(response_span, thread_info_1.thread_name().value()));
128 EXPECT_TRUE(
129 EncodedThreadExists(response_span, thread_info_2.thread_name().value()));
130 EXPECT_TRUE(
131 EncodedThreadExists(response_span, thread_info_3.thread_name().value()));
132 }
133
TEST(ThreadSnapshotService,DefaultBufferSize)134 TEST(ThreadSnapshotService, DefaultBufferSize) {
135 static std::array<std::byte, RequiredServiceBufferSize()> encode_buffer;
136
137 proto::pwpb::SnapshotThreadInfo::MemoryEncoder encoder(encode_buffer);
138
139 std::optional<uintptr_t> example_addr = std::numeric_limits<uintptr_t>::max();
140
141 ThreadInfo thread_info = CreateThreadInfoObject(
142 as_bytes(span("MyThread")), example_addr, example_addr, example_addr);
143
144 for (int i = 0; i < PW_THREAD_MAXIMUM_THREADS; i++) {
145 EXPECT_EQ(OkStatus(), ProtoEncodeThreadInfo(encoder, thread_info));
146 }
147
148 ConstByteSpan response_span(encoder);
149 EXPECT_TRUE(
150 EncodedThreadExists(response_span, thread_info.thread_name().value()));
151 }
152
TEST(ThreadSnapshotService,FailedPrecondition)153 TEST(ThreadSnapshotService, FailedPrecondition) {
154 static std::array<std::byte, RequiredServiceBufferSize(1)> encode_buffer;
155
156 proto::pwpb::SnapshotThreadInfo::MemoryEncoder encoder(encode_buffer);
157
158 ThreadInfo thread_info_no_name =
159 CreateThreadInfoObject(std::nullopt,
160 static_cast<uintptr_t>(1111111111u),
161 static_cast<uintptr_t>(2222222222u),
162 static_cast<uintptr_t>(3333333333u));
163 Status status = ProtoEncodeThreadInfo(encoder, thread_info_no_name);
164 EXPECT_EQ(status, Status::FailedPrecondition());
165 // Expected log: "Thread missing information needed by service."
166 ErrorLog(status);
167
168 // Same error log as above.
169 ThreadInfo thread_info_no_high_addr =
170 CreateThreadInfoObject(as_bytes(span("MyThread")),
171 static_cast<uintptr_t>(1111111111u),
172 std::nullopt,
173 static_cast<uintptr_t>(3333333333u));
174 EXPECT_EQ(ProtoEncodeThreadInfo(encoder, thread_info_no_high_addr),
175 Status::FailedPrecondition());
176 }
177
TEST(ThreadSnapshotService,Unimplemented)178 TEST(ThreadSnapshotService, Unimplemented) {
179 static std::array<std::byte, RequiredServiceBufferSize(1)> encode_buffer;
180
181 proto::pwpb::SnapshotThreadInfo::MemoryEncoder encoder(encode_buffer);
182
183 ThreadInfo thread_info_no_peak_addr =
184 CreateThreadInfoObject(as_bytes(span("MyThread")),
185 static_cast<uintptr_t>(0u),
186 static_cast<uintptr_t>(0u),
187 std::nullopt);
188
189 Status status = ProtoEncodeThreadInfo(encoder, thread_info_no_peak_addr);
190 EXPECT_EQ(status, Status::Unimplemented());
191 // Expected log: "Peak stack usage reporting not supported by your current OS
192 // or configuration."
193 ErrorLog(status);
194 }
195
196 } // namespace
197 } // namespace pw::thread::proto
198