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_metric/metric_service_pwpb.h"
16
17 #include "pw_log/log.h"
18 #include "pw_metric_proto/metric_service.pwpb.h"
19 #include "pw_protobuf/decoder.h"
20 #include "pw_rpc/pwpb/test_method_context.h"
21 #include "pw_rpc/raw/test_method_context.h"
22 #include "pw_span/span.h"
23 #include "pw_unit_test/framework.h"
24
25 namespace pw::metric {
26 namespace {
27
28 #define MetricMethodContext \
29 PW_PWPB_TEST_METHOD_CONTEXT(MetricService, Get, 4, 256)
30
CountEncodedMetrics(ConstByteSpan serialized_path)31 size_t CountEncodedMetrics(ConstByteSpan serialized_path) {
32 protobuf::Decoder decoder(serialized_path);
33 size_t num_metrics = 0;
34 while (decoder.Next().ok()) {
35 switch (decoder.FieldNumber()) {
36 case static_cast<uint32_t>(
37 pw::metric::proto::pwpb::MetricResponse::Fields::kMetrics): {
38 num_metrics++;
39 }
40 }
41 }
42 return num_metrics;
43 }
44
SumMetricInts(ConstByteSpan serialized_path)45 size_t SumMetricInts(ConstByteSpan serialized_path) {
46 protobuf::Decoder decoder(serialized_path);
47 size_t metrics_sum = 0;
48 while (decoder.Next().ok()) {
49 switch (decoder.FieldNumber()) {
50 case static_cast<uint32_t>(
51 pw::metric::proto::pwpb::Metric::Fields::kAsInt): {
52 uint32_t metric_value;
53 EXPECT_EQ(OkStatus(), decoder.ReadUint32(&metric_value));
54 metrics_sum += metric_value;
55 }
56 }
57 }
58 return metrics_sum;
59 }
60
GetMetricsSum(ConstByteSpan serialized_metric_buffer)61 size_t GetMetricsSum(ConstByteSpan serialized_metric_buffer) {
62 protobuf::Decoder decoder(serialized_metric_buffer);
63 size_t metrics_sum = 0;
64 while (decoder.Next().ok()) {
65 switch (decoder.FieldNumber()) {
66 case static_cast<uint32_t>(
67 pw::metric::proto::pwpb::MetricResponse::Fields::kMetrics): {
68 ConstByteSpan metric_buffer;
69 EXPECT_EQ(OkStatus(), decoder.ReadBytes(&metric_buffer));
70 metrics_sum += SumMetricInts(metric_buffer);
71 }
72 }
73 }
74 return metrics_sum;
75 }
76
TEST(MetricService,EmptyGroupAndNoMetrics)77 TEST(MetricService, EmptyGroupAndNoMetrics) {
78 // Empty root group.
79 PW_METRIC_GROUP(root, "/");
80
81 // Run the RPC and ensure it completes.
82
83 PW_RAW_TEST_METHOD_CONTEXT(MetricService, Get)
84 ctx{root.metrics(), root.children()};
85 ctx.call({});
86 EXPECT_TRUE(ctx.done());
87 EXPECT_EQ(OkStatus(), ctx.status());
88
89 // No metrics should be in the response.
90 EXPECT_EQ(0u, ctx.responses().size());
91 }
92
TEST(MetricService,OneGroupOneMetric)93 TEST(MetricService, OneGroupOneMetric) {
94 // One root group with one metric.
95 PW_METRIC_GROUP(root, "/");
96 PW_METRIC(root, a, "a", 3u);
97
98 // Run the RPC and ensure it completes.
99
100 PW_RAW_TEST_METHOD_CONTEXT(MetricService, Get)
101 ctx{root.metrics(), root.children()};
102 ctx.call({});
103 EXPECT_TRUE(ctx.done());
104 EXPECT_EQ(OkStatus(), ctx.status());
105
106 // One metric should be in the response.
107 EXPECT_EQ(1u, ctx.responses().size());
108
109 // Sum should be 3.
110 EXPECT_EQ(3u, GetMetricsSum(ctx.responses()[0]));
111 }
112
TEST(MetricService,OneGroupFiveMetrics)113 TEST(MetricService, OneGroupFiveMetrics) {
114 // One root group with five metrics.
115 PW_METRIC_GROUP(root, "/");
116 PW_METRIC(root, a, "a", 1u);
117 PW_METRIC(root, b, "b", 2u); // Note: Max # per response is 3.
118 PW_METRIC(root, c, "c", 3u);
119 PW_METRIC(root, x, "x", 4u);
120 PW_METRIC(root, y, "y", 5u);
121
122 // Run the RPC and ensure it completes.
123
124 PW_RAW_TEST_METHOD_CONTEXT(MetricService, Get)
125 ctx{root.metrics(), root.children()};
126 ctx.call({});
127 EXPECT_TRUE(ctx.done());
128 EXPECT_EQ(OkStatus(), ctx.status());
129
130 // Two metrics should be in the response.
131 EXPECT_EQ(2u, ctx.responses().size());
132 EXPECT_EQ(3u, CountEncodedMetrics(ctx.responses()[0]));
133 EXPECT_EQ(2u, CountEncodedMetrics(ctx.responses()[1]));
134
135 // The metrics are the numbers 1..5; sum them and compare.
136 EXPECT_EQ(
137 15u,
138 GetMetricsSum(ctx.responses()[0]) + GetMetricsSum(ctx.responses()[1]));
139 }
140
TEST(MetricService,NestedGroupFiveMetrics)141 TEST(MetricService, NestedGroupFiveMetrics) {
142 // Set up a nested group of metrics.
143 PW_METRIC_GROUP(root, "/");
144 PW_METRIC(root, a, "a", 1u);
145 PW_METRIC(root, b, "b", 2u);
146
147 PW_METRIC_GROUP(inner, "inner");
148 PW_METRIC(root, x, "x", 3u); // Note: Max # per response is 3.
149 PW_METRIC(inner, y, "y", 4u);
150 PW_METRIC(inner, z, "z", 5u);
151
152 root.Add(inner);
153
154 // Run the RPC and ensure it completes.
155
156 PW_RAW_TEST_METHOD_CONTEXT(MetricService, Get)
157 ctx{root.metrics(), root.children()};
158 ctx.call({});
159 EXPECT_TRUE(ctx.done());
160 EXPECT_EQ(OkStatus(), ctx.status());
161
162 // Two metrics should be in the response.
163 EXPECT_EQ(2u, ctx.responses().size());
164 EXPECT_EQ(3u, CountEncodedMetrics(ctx.responses()[0]));
165 EXPECT_EQ(2u, CountEncodedMetrics(ctx.responses()[1]));
166
167 EXPECT_EQ(
168 15u,
169 GetMetricsSum(ctx.responses()[0]) + GetMetricsSum(ctx.responses()[1]));
170 }
171
TEST(MetricService,NestedGroupsWithBatches)172 TEST(MetricService, NestedGroupsWithBatches) {
173 // Set up a nested group of metrics that will not fit in a single batch.
174 PW_METRIC_GROUP(root, "/");
175 PW_METRIC(root, a, "a", 1u);
176 PW_METRIC(root, d, "d", 2u);
177 PW_METRIC(root, f, "f", 3u);
178
179 PW_METRIC_GROUP(inner_1, "inner1");
180 PW_METRIC(inner_1, x, "x", 4u);
181 PW_METRIC(inner_1, y, "y", 5u);
182 PW_METRIC(inner_1, z, "z", 6u);
183
184 PW_METRIC_GROUP(inner_2, "inner2");
185 PW_METRIC(inner_2, p, "p", 7u);
186 PW_METRIC(inner_2, q, "q", 8u);
187 PW_METRIC(inner_2, r, "r", 9u);
188 PW_METRIC(inner_2, s, "s", 10u); // Note: Max # per response is 3.
189 PW_METRIC(inner_2, t, "t", 11u);
190 PW_METRIC(inner_2, u, "u", 12u);
191
192 root.Add(inner_1);
193 root.Add(inner_2);
194
195 // Run the RPC and ensure it completes.
196 PW_RAW_TEST_METHOD_CONTEXT(MetricService, Get)
197 ctx{root.metrics(), root.children()};
198 ctx.call({});
199 EXPECT_TRUE(ctx.done());
200 EXPECT_EQ(OkStatus(), ctx.status());
201
202 // The response had to be split into four parts; check that they have the
203 // appropriate sizes.
204 EXPECT_EQ(4u, ctx.responses().size());
205 EXPECT_EQ(3u, CountEncodedMetrics(ctx.responses()[0]));
206 EXPECT_EQ(3u, CountEncodedMetrics(ctx.responses()[1]));
207 EXPECT_EQ(3u, CountEncodedMetrics(ctx.responses()[2]));
208 EXPECT_EQ(3u, CountEncodedMetrics(ctx.responses()[3]));
209
210 EXPECT_EQ(78u,
211 GetMetricsSum(ctx.responses()[0]) +
212 GetMetricsSum(ctx.responses()[1]) +
213 GetMetricsSum(ctx.responses()[2]) +
214 GetMetricsSum(ctx.responses()[3]));
215 }
216
TEST(MetricService,MaxDepth4)217 TEST(MetricService, MaxDepth4) {
218 // MetricWalker internally uses: Vector<Token, /*capacity=*/4> path_;
219 // pw.metric.proto.Metric.token_path max_count:4
220
221 IntrusiveList<Group> global_groups; // Simulate pw::metric::global_groups
222 IntrusiveList<Metric> global_metrics; // Simulate pw::metric::global_metrics
223
224 PW_METRIC_GROUP(global_group_lvl1, "level1");
225 global_groups.push_back(global_group_lvl1);
226
227 PW_METRIC_GROUP(global_group_lvl1, group_lvl2, "level2");
228 PW_METRIC_GROUP(group_lvl2, group_lvl3, "level3");
229
230 // Note: kMaxNumPackedEntries = 3
231 PW_METRIC(group_lvl3, metric_a, "metric A", 1u);
232 PW_METRIC(group_lvl3, metric_b, "metric B", 2u);
233 PW_METRIC(group_lvl3, metric_c, "metric C", 3u);
234
235 // Run the RPC and ensure it completes.
236 PW_RAW_TEST_METHOD_CONTEXT(MetricService, Get)
237 ctx{global_metrics, global_groups};
238 ctx.call({});
239 EXPECT_TRUE(ctx.done());
240 EXPECT_EQ(OkStatus(), ctx.status());
241
242 // Verify the response
243 EXPECT_EQ(1u, ctx.responses().size());
244 EXPECT_EQ(3u, CountEncodedMetrics(ctx.responses()[0]));
245 EXPECT_EQ(6u, GetMetricsSum(ctx.responses()[0]));
246 }
247
248 } // namespace
249 } // namespace pw::metric
250