blob: 2677ad898f55dc8a29ba65d6bd5af7179982cde5 [file] [log] [blame]
Keir Mierle45fa7852020-08-10 21:09:54 -07001// Copyright 2020 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.h"
16
17#include "gtest/gtest.h"
18#include "pw_log/log.h"
19
20namespace pw::metric {
21
22TEST(Metric, FloatFromObject) {
23 // Note leading bit is 1; it is stripped from the name to store the type.
24 Token token = 0xf1223344;
25
26 TypedMetric<float> m(token, 1.5f);
27 EXPECT_EQ(m.name(), 0x71223344u);
28 EXPECT_TRUE(m.is_float());
29 EXPECT_FALSE(m.is_int());
30 EXPECT_EQ(m.value(), 1.5f);
31
32 m.Set(55.1f);
33 EXPECT_EQ(m.value(), 55.1f);
34
35 // No increment operation for float.
36}
37
38TEST(Metric, IntFromObject) {
39 // Note leading bit is 1; it is stripped from the name to store the type.
40 Token token = 0xf1223344;
41
42 TypedMetric<uint32_t> m(token, static_cast<uint32_t>(31337u));
43 EXPECT_EQ(m.name(), 0x71223344u);
44 EXPECT_TRUE(m.is_int());
45 EXPECT_FALSE(m.is_float());
46 EXPECT_EQ(m.value(), 31337u);
47
48 m.Set(414u);
49 EXPECT_EQ(m.value(), 414u);
50
51 m.Increment();
52 EXPECT_EQ(m.value(), 415u);
53
54 m.Increment(11u);
55 EXPECT_EQ(m.value(), 426u);
56}
57
58TEST(m, IntFromMacroLocal) {
59 PW_METRIC(m, "some_metric", 14u);
60 EXPECT_TRUE(m.is_int());
61 EXPECT_EQ(m.value(), 14u);
62}
63
64TEST(Metric, FloatFromMacroLocal) {
65 PW_METRIC(m, "some_metric", 3.14f);
66 EXPECT_TRUE(m.is_float());
67 EXPECT_EQ(m.value(), 3.14f);
68}
69
70TEST(Metric, GroupMacroInFunctionContext) {
71 PW_METRIC_GROUP(group, "fancy_subsystem");
72 PW_METRIC(group, x, "x", 5555u);
73 PW_METRIC(group, y, "y", 6.0f);
74
75 // These calls are needed to satisfy GCC, otherwise GCC warns about an unused
76 // variable (even though it is used and passed to the group, which adds it):
77 //
78 // metric_test.cc:72:20: error: variable 'x' set but not used
79 // [-Werror=unused-but-set-variable]
80 //
81 x.Increment(10);
82 y.Set(5.0f);
83
Keir Mierle45fa7852020-08-10 21:09:54 -070084 group.Dump();
Wyatt Hepler63afc002021-02-08 13:20:26 -080085 EXPECT_EQ(group.metrics().size(), 2u);
Keir Mierle45fa7852020-08-10 21:09:54 -070086}
87
88// The below are compile tests to ensure the macros work at global scope.
89
90// Case 1: No group specified.
91PW_METRIC(global_x, "global_x", 5555u);
92PW_METRIC(global_y, "global_y", 6.0f);
93
94// Case 2: Group specified.
95PW_METRIC_GROUP(global_group, "a_global_group");
96PW_METRIC(global_group, global_z, "global_x", 5555u);
97PW_METRIC(global_group, global_w, "global_y", 6.0f);
98
99// A fake object to illustrate the API and show nesting metrics.
100// This also tests creating metrics as members inside a class.
101class I2cBus {
102 public:
103 void Transaction() {
104 // An entirely unconvincing fake I2C transaction implementation.
105 transactions_.Increment();
106 bytes_sent_.Increment(5);
107 }
108
109 Group& stats() { return metrics_; }
110
111 private:
112 // Test a group with metrics in it, as a class member.
113 // Note that in many cases, the group would be passed in externally instead.
114 PW_METRIC_GROUP(metrics_, "i2c");
115 PW_METRIC(metrics_, bus_errors_, "bus_errors", 0u);
116 PW_METRIC(metrics_, transactions_, "transactions", 0u);
117 PW_METRIC(metrics_, bytes_sent_, "bytes_sent", 0u);
118
119 // Test metrics without a group, as a class member.
120 PW_METRIC(a, "a", 0u);
121 PW_METRIC(b, "b", 10.0f);
122 PW_METRIC(c, "c", 525u);
123};
124
125class Gyro {
126 public:
127 Gyro(I2cBus& i2c_bus, Group& parent_metrics) : i2c_bus_(i2c_bus) {
128 // Make the gyro a child of the I2C bus. Note that the other arrangement,
129 // where the i2c bus is a child of the gyro, doesn't work if there are
130 // multiple objects on the I2C bus due to the intrusive list mechanism.
131 parent_metrics.Add(metrics_);
132 }
133
134 void Init() {
135 i2c_bus_.Transaction();
136 initialized_.Increment();
137 }
138
139 void ReadAngularVelocity() {
140 // Pretend to be doing some transactions and pulling angular velocity.
141 // Pretend this gyro is inefficient and requires multiple transactions.
142 i2c_bus_.Transaction();
143 i2c_bus_.Transaction();
144 i2c_bus_.Transaction();
145 num_samples_.Increment();
146 }
147
148 Group& stats() { return metrics_; }
149
150 private:
151 I2cBus& i2c_bus_;
152
153 // In this case, "gyro" groups the relevant metrics, but it is possible to
154 // have freestanding metrics directly without a group; however, those
155 // free-standing metrics must be added to a group or list supplied elsewhere
156 // for collection.
157 PW_METRIC_GROUP(metrics_, "gyro");
158 PW_METRIC(metrics_, num_samples_, "num_samples", 1u);
159 PW_METRIC(metrics_, init_time_us_, "init_time_us", 1.0f);
160 PW_METRIC(metrics_, initialized_, "initialized", 0u);
161};
162
163// The below test produces output like:
164//
165// "$6doqFw==": {
166// "$05OCZw==": {
167// "$VpPfzg==": 1,
168// "$LGPMBQ==": 1.000000,
169// "$+iJvUg==": 5,
170// }
171// "$9hPNxw==": 65,
172// "$oK7HmA==": 13,
173// "$FCM4qQ==": 0,
174// }
175//
176// Note the metric names are tokenized with base64. Decoding requires using the
177// Pigweed detokenizer. With a detokenizing-enabled logger, you would get:
178//
179// "i2c": {
180// "gyro": {
181// "num_sampleses": 1,
182// "init_time_us": 1.000000,
183// "initialized": 5,
184// }
185// "bus_errors": 65,
186// "transactions": 13,
187// "bytes_sent": 0,
188// }
189//
190TEST(Metric, InlineConstructionWithGroups) {
191 I2cBus i2c_bus;
192 Gyro gyro(i2c_bus, i2c_bus.stats());
193
194 gyro.Init();
195 gyro.ReadAngularVelocity();
196 gyro.ReadAngularVelocity();
197 gyro.ReadAngularVelocity();
198 gyro.ReadAngularVelocity();
199
200 // This "test" doesn't really test anything, and more illustrates how to the
201 // metrics could be instantiated in an object tree.
202 //
203 // Unfortunatlely, testing dump is difficult since we don't have log
204 // redirection for tests.
205 i2c_bus.stats().Dump();
206}
207
Paul Mathieu2182c662020-08-28 17:02:50 +0200208// PW_METRIC_STATIC doesn't support class scopes, since a definition must be
209// provided outside of the class body.
210// TODO(paulmathieu): add support for class scopes and enable this test
211#if 0
212class MetricTest: public ::testing::Test {
213 public:
214 void Increment() {
215 metric_.Increment();
216 }
217
218 private:
219 PW_METRIC_STATIC(metric_, "metric", 0u);
220};
221
222TEST_F(MetricTest, StaticWithinAClass) {
223 Increment();
224}
225#endif
226
227Metric* StaticMetricIncrement() {
228 PW_METRIC_STATIC(metric, "metric", 0u);
229 metric.Increment();
230 return &metric;
231}
232
233TEST(Metric, StaticWithinAFunction) {
234 Metric* metric = StaticMetricIncrement();
235 EXPECT_EQ(metric->as_int(), 1u);
236 StaticMetricIncrement();
237 EXPECT_EQ(metric->as_int(), 2u);
238}
239
Keir Mierle45fa7852020-08-10 21:09:54 -0700240} // namespace pw::metric