1 // 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 
20 namespace pw::metric {
21 
TEST(Metric,FloatFromObject)22 TEST(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 
TEST(Metric,IntFromObject)38 TEST(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 
TEST(m,IntFromMacroLocal)58 TEST(m, IntFromMacroLocal) {
59   PW_METRIC(m, "some_metric", 14u);
60   EXPECT_TRUE(m.is_int());
61   EXPECT_EQ(m.value(), 14u);
62 }
63 
TEST(Metric,FloatFromMacroLocal)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);
68 }
69 
TEST(Metric,GroupMacroInFunctionContext)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);
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 
84   group.Dump();
85   EXPECT_EQ(group.metrics().size(), 2u);
86 }
87 
88 // The below are compile tests to ensure the macros work at global scope.
89 
90 // Case 1: No group specified.
91 PW_METRIC(global_x, "global_x", 5555u);
92 PW_METRIC(global_y, "global_y", 6.0f);
93 
94 // Case 2: Group specified.
95 PW_METRIC_GROUP(global_group, "a_global_group");
96 PW_METRIC(global_group, global_z, "global_x", 5555u);
97 PW_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.
101 class I2cBus {
102  public:
Transaction()103   void Transaction() {
104     // An entirely unconvincing fake I2C transaction implementation.
105     transactions_.Increment();
106     bytes_sent_.Increment(5);
107   }
108 
stats()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 
125 class Gyro {
126  public:
Gyro(I2cBus & i2c_bus,Group & parent_metrics)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 
Init()134   void Init() {
135     i2c_bus_.Transaction();
136     initialized_.Increment();
137   }
138 
ReadAngularVelocity()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 
stats()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 //
TEST(Metric,InlineConstructionWithGroups)190 TEST(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 
208 // 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
212 class 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 
222 TEST_F(MetricTest, StaticWithinAClass) {
223   Increment();
224 }
225 #endif
226 
StaticMetricIncrement()227 Metric* StaticMetricIncrement() {
228   PW_METRIC_STATIC(metric, "metric", 0u);
229   metric.Increment();
230   return &metric;
231 }
232 
TEST(Metric,StaticWithinAFunction)233 TEST(Metric, StaticWithinAFunction) {
234   Metric* metric = StaticMetricIncrement();
235   EXPECT_EQ(metric->as_int(), 1u);
236   StaticMetricIncrement();
237   EXPECT_EQ(metric->as_int(), 2u);
238 }
239 
240 }  // namespace pw::metric
241