1 // Copyright 2020 The Pigweed Authors
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
7 // https://www.apache.org/licenses/LICENSE-2.0
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
15 #include "pw_metric/metric.h"
17 #include "gtest/gtest.h"
18 #include "pw_log/log.h"
20 namespace pw::metric {
22 TEST(Metric, FloatFromObject) {
23 // Note leading bit is 1; it is stripped from the name to store the type.
24 Token token = 0xf1223344;
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);
33 EXPECT_EQ(m.value(), 55.1f);
35 // No increment operation for float.
38 TEST(Metric, IntFromObject) {
39 // Note leading bit is 1; it is stripped from the name to store the type.
40 Token token = 0xf1223344;
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);
49 EXPECT_EQ(m.value(), 414u);
52 EXPECT_EQ(m.value(), 415u);
55 EXPECT_EQ(m.value(), 426u);
58 TEST(m, IntFromMacroLocal) {
59 PW_METRIC(m, "some_metric", 14u);
60 EXPECT_TRUE(m.is_int());
61 EXPECT_EQ(m.value(), 14u);
64 TEST(Metric, FloatFromMacroLocal) {
65 PW_METRIC(m, "some_metric", 3.14f);
66 EXPECT_TRUE(m.is_float());
67 EXPECT_EQ(m.value(), 3.14f);
70 TEST(Metric, GroupMacroInFunctionContext) {
71 PW_METRIC_GROUP(group, "fancy_subsystem");
72 PW_METRIC(group, x, "x", 5555u);
73 PW_METRIC(group, y, "y", 6.0f);
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):
78 // metric_test.cc:72:20: error: variable 'x' set but not used
79 // [-Werror=unused-but-set-variable]
85 for (auto& m : group.metrics()) {
90 EXPECT_EQ(num_metrics, 2);
93 // The below are compile tests to ensure the macros work at global scope.
95 // Case 1: No group specified.
96 PW_METRIC(global_x, "global_x", 5555u);
97 PW_METRIC(global_y, "global_y", 6.0f);
99 // Case 2: Group specified.
100 PW_METRIC_GROUP(global_group, "a_global_group");
101 PW_METRIC(global_group, global_z, "global_x", 5555u);
102 PW_METRIC(global_group, global_w, "global_y", 6.0f);
104 // A fake object to illustrate the API and show nesting metrics.
105 // This also tests creating metrics as members inside a class.
109 // An entirely unconvincing fake I2C transaction implementation.
110 transactions_.Increment();
111 bytes_sent_.Increment(5);
114 Group& stats() { return metrics_; }
117 // Test a group with metrics in it, as a class member.
118 // Note that in many cases, the group would be passed in externally instead.
119 PW_METRIC_GROUP(metrics_, "i2c");
120 PW_METRIC(metrics_, bus_errors_, "bus_errors", 0u);
121 PW_METRIC(metrics_, transactions_, "transactions", 0u);
122 PW_METRIC(metrics_, bytes_sent_, "bytes_sent", 0u);
124 // Test metrics without a group, as a class member.
125 PW_METRIC(a, "a", 0u);
126 PW_METRIC(b, "b", 10.0f);
127 PW_METRIC(c, "c", 525u);
132 Gyro(I2cBus& i2c_bus, Group& parent_metrics) : i2c_bus_(i2c_bus) {
133 // Make the gyro a child of the I2C bus. Note that the other arrangement,
134 // where the i2c bus is a child of the gyro, doesn't work if there are
135 // multiple objects on the I2C bus due to the intrusive list mechanism.
136 parent_metrics.Add(metrics_);
140 i2c_bus_.Transaction();
141 initialized_.Increment();
144 void ReadAngularVelocity() {
145 // Pretend to be doing some transactions and pulling angular velocity.
146 // Pretend this gyro is inefficient and requires multiple transactions.
147 i2c_bus_.Transaction();
148 i2c_bus_.Transaction();
149 i2c_bus_.Transaction();
150 num_samples_.Increment();
153 Group& stats() { return metrics_; }
158 // In this case, "gyro" groups the relevant metrics, but it is possible to
159 // have freestanding metrics directly without a group; however, those
160 // free-standing metrics must be added to a group or list supplied elsewhere
162 PW_METRIC_GROUP(metrics_, "gyro");
163 PW_METRIC(metrics_, num_samples_, "num_samples", 1u);
164 PW_METRIC(metrics_, init_time_us_, "init_time_us", 1.0f);
165 PW_METRIC(metrics_, initialized_, "initialized", 0u);
168 // The below test produces output like:
173 // "$LGPMBQ==": 1.000000,
181 // Note the metric names are tokenized with base64. Decoding requires using the
182 // Pigweed detokenizer. With a detokenizing-enabled logger, you would get:
186 // "num_sampleses": 1,
187 // "init_time_us": 1.000000,
191 // "transactions": 13,
195 TEST(Metric, InlineConstructionWithGroups) {
197 Gyro gyro(i2c_bus, i2c_bus.stats());
200 gyro.ReadAngularVelocity();
201 gyro.ReadAngularVelocity();
202 gyro.ReadAngularVelocity();
203 gyro.ReadAngularVelocity();
205 // This "test" doesn't really test anything, and more illustrates how to the
206 // metrics could be instantiated in an object tree.
208 // Unfortunatlely, testing dump is difficult since we don't have log
209 // redirection for tests.
210 i2c_bus.stats().Dump();
213 // PW_METRIC_STATIC doesn't support class scopes, since a definition must be
214 // provided outside of the class body.
215 // TODO(paulmathieu): add support for class scopes and enable this test
217 class MetricTest: public ::testing::Test {
224 PW_METRIC_STATIC(metric_, "metric", 0u);
227 TEST_F(MetricTest, StaticWithinAClass) {
232 Metric* StaticMetricIncrement() {
233 PW_METRIC_STATIC(metric, "metric", 0u);
238 TEST(Metric, StaticWithinAFunction) {
239 Metric* metric = StaticMetricIncrement();
240 EXPECT_EQ(metric->as_int(), 1u);
241 StaticMetricIncrement();
242 EXPECT_EQ(metric->as_int(), 2u);
245 } // namespace pw::metric