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