• 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 #pragma once
15 
16 #include <algorithm>
17 #include <initializer_list>
18 #include <limits>
19 
20 #include "pw_containers/intrusive_list.h"
21 #include "pw_preprocessor/arguments.h"
22 #include "pw_tokenizer/tokenize.h"
23 
24 namespace pw::metric {
25 
26 // Currently, this is for tokens, but later may be a char* when non-tokenized
27 // metric names are supported.
28 using tokenizer::Token;
29 
30 #define _PW_METRIC_TOKEN_MASK 0x7fffffff
31 
32 // An individual metric. There are only two supported types: uint32_t and
33 // float. More complicated compound metrics can be built on these primitives.
34 // See the documentation for a discussion for this design was selected.
35 //
36 // Size: 12 bytes / 96 bits - next, name, value.
37 //
38 // TODO(keir): Implement Set() and Increment() using atomics.
39 // TODO(keir): Consider an alternative structure where metrics have pointers to
40 // parent groups, which would enable (1) safe destruction and (2) safe static
41 // initialization, but at the cost of an additional 4 bytes per metric and 4
42 // bytes per group..
43 class Metric : public IntrusiveList<Metric>::Item {
44  public:
name()45   Token name() const { return name_and_type_ & kTokenMask; }
46 
is_float()47   bool is_float() const { return (name_and_type_ & kTypeMask) == kTypeFloat; }
is_int()48   bool is_int() const { return (name_and_type_ & kTypeMask) == kTypeInt; }
49 
50   float as_float() const;
51   uint32_t as_int() const;
52 
53   // Dump a metric or metrics to logs. Level determines the indentation
54   // indent_level up to a maximum of 4. Example output:
55   //
56   //   "$FCM4qQ==": 0,
57   //
58   // Note the base64-encoded token name. Detokenization tools are necessary to
59   // convert this to human-readable form.
60   void Dump(int indent_level = 0);
61   static void Dump(IntrusiveList<Metric>& metrics, int indent_level = 0);
62 
63   // Disallow copy and assign.
64   Metric(Metric const&) = delete;
65   void operator=(const Metric&) = delete;
66 
67  protected:
Metric(Token name,float value)68   Metric(Token name, float value)
69       : name_and_type_((name & kTokenMask) | kTypeFloat), float_(value) {}
70 
Metric(Token name,uint32_t value)71   Metric(Token name, uint32_t value)
72       : name_and_type_((name & kTokenMask) | kTypeInt), uint_(value) {}
73 
74   Metric(Token name, float value, IntrusiveList<Metric>& metrics);
75   Metric(Token name, uint32_t value, IntrusiveList<Metric>& metrics);
76 
77   // Hide mutation methods, and only offer write access through the specialized
78   // TypedMetric below. This makes it impossible to call metric.Increment() on
79   // a float metric at compile time.
80   void Increment(uint32_t amount = 1);
81 
82   void SetInt(uint32_t value);
83 
84   void SetFloat(float value);
85 
86  private:
87   // The name of this metric as a token; from PW_TOKENIZE_STRING("my_metric").
88   // Last bit of the token is used to store int or float; 0 == int, 1 == float.
89   Token name_and_type_;
90 
91   union {
92     float float_;
93     uint32_t uint_;
94   };
95 
96   enum : uint32_t {
97     kTokenMask = _PW_METRIC_TOKEN_MASK,  // 0x7fff'ffff
98     kTypeMask = 0x8000'0000,
99     kTypeFloat = 0x8000'0000,
100     kTypeInt = 0x0,
101   };
102 };
103 
104 // TypedMetric provides a type-safe wrapper the runtime-typed Metric object.
105 // Note: Definition omitted to prevent accidental instantiation.
106 // TODO(keir): Provide a more precise error message via static assert.
107 template <typename T>
108 class TypedMetric;
109 
110 // A metric for floats. Does not offer an Increment() function, since it is too
111 // easy to do unsafe operations like accumulating small values in floats.
112 template <>
113 class TypedMetric<float> : public Metric {
114  public:
TypedMetric(Token name,float value)115   TypedMetric(Token name, float value) : Metric(name, value) {}
TypedMetric(Token name,float value,IntrusiveList<Metric> & metrics)116   TypedMetric(Token name, float value, IntrusiveList<Metric>& metrics)
117       : Metric(name, value, metrics) {}
118 
Set(float value)119   void Set(float value) { SetFloat(value); }
value()120   float value() const { return Metric::as_float(); }
121 
122  private:
123   // Shadow these accessors to hide them on the typed version of Metric.
as_float()124   float as_float() const { return 0.0; }
as_int()125   uint32_t as_int() const { return 0; }
126 };
127 
128 // A metric for uint32_ts. Offers both Set() and Increment().
129 template <>
130 class TypedMetric<uint32_t> : public Metric {
131  public:
TypedMetric(Token name,uint32_t value)132   TypedMetric(Token name, uint32_t value) : Metric(name, value) {}
TypedMetric(Token name,uint32_t value,IntrusiveList<Metric> & metrics)133   TypedMetric(Token name, uint32_t value, IntrusiveList<Metric>& metrics)
134       : Metric(name, value, metrics) {}
135 
136   void Increment(uint32_t amount = 1u) { Metric::Increment(amount); }
Set(uint32_t value)137   void Set(uint32_t value) { SetInt(value); }
value()138   uint32_t value() const { return Metric::as_int(); }
139 
140  private:
141   // Shadow these accessors to hide them on the typed version of Metric.
as_float()142   float as_float() const { return 0.0; }
as_int()143   uint32_t as_int() const { return 0; }
144 };
145 
146 // A metric tree; consisting of children groups and leaf metrics.
147 //
148 // Size: 16 bytes/128 bits - next, name, metrics, children.
149 class Group : public IntrusiveList<Group>::Item {
150  public:
151   Group(Token name);
152   Group(Token name, IntrusiveList<Group>& groups);
153 
name()154   Token name() const { return name_; }
155 
Add(Metric & metric)156   void Add(Metric& metric) { metrics_.push_front(metric); }
Add(Group & group)157   void Add(Group& group) { children_.push_front(group); }
158 
metrics()159   IntrusiveList<Metric>& metrics() { return metrics_; }
children()160   IntrusiveList<Group>& children() { return children_; }
161 
metrics()162   const IntrusiveList<Metric>& metrics() const { return metrics_; }
children()163   const IntrusiveList<Group>& children() const { return children_; }
164 
165   // Dump a metric group or groups to logs. Level determines the indentation
166   // indent_level up to a maximum of 4. Example output:
167   //
168   //   "$6doqFw==": {
169   //     "$05OCZw==": {
170   //       "$VpPfzg==": 1,
171   //       "$LGPMBQ==": 1.000000,
172   //       "$+iJvUg==": 5,
173   //     }
174   //     "$9hPNxw==": 65,
175   //     "$oK7HmA==": 13,
176   //     "$FCM4qQ==": 0,
177   //   }
178   //
179   // Note the base64-encoded token name. Detokenization tools are necessary to
180   // convert this to human-readable form.
181   void Dump(int indent_level = 0);
182   static void Dump(IntrusiveList<Group>& groups, int indent_level = 0);
183 
184   // Disallow copy and assign.
185   Group(Group const&) = delete;
186   void operator=(const Group&) = delete;
187 
188  private:
189   // The name of this group as a token; from PW_TOKENIZE_STRING("my_group").
190   Token name_;
191 
192   IntrusiveList<Metric> metrics_;
193   IntrusiveList<Group> children_;
194 };
195 
196 // Declare a metric, optionally adding it to a group. Use:
197 //
198 //   PW_METRIC(variable_name, metric_name, value)
199 //   PW_METRIC(group, variable_name, metric_name, value)
200 //
201 // - variable_name is an identifier
202 // - metric_name is a string name for the metric (will be tokenized)
203 // - value must be either a floating point value (3.2f) or unsigned int (21u).
204 // - group is a Group instance.
205 //
206 // The macro declares a variable or member named "name" with type Metric, and
207 // works in three contexts: global, local, and member.
208 //
209 // 1. At global scope
210 //
211 //    PW_METRIC(foo, 15.5f);
212 //
213 //    void MyFunc() {
214 //      foo.Increment();
215 //    }
216 //
217 // 2. At local function or member function scope:
218 //
219 //    void MyFunc() {
220 //      PW_METRIC(foo, "foo", 15.5f);
221 //      foo.Increment();
222 //      // foo goes out of scope here; be careful!
223 //    }
224 //
225 // 3. At member level inside a class or struct:
226 //
227 //    struct MyStructy {
228 //      void DoSomething() {
229 //        somethings_.Increment();
230 //      }
231 //      // Every instance of MyStructy will have a separate somethings counter.
232 //      PW_METRIC(somethings_, "somethings", 0u);
233 //    }
234 //
235 // You can also put a metric into a group with the macro. Metrics can belong to
236 // strictly one group, otherwise a assertion will fail. Example:
237 //
238 //   PW_METRIC_GROUP(my_group, "my_group_name_here");
239 //   PW_METRIC(my_group, foo_, "foo", 0.2f);
240 //   PW_METRIC(my_group, bar_, "bar", 44000u);
241 //   PW_METRIC(my_group, zap_, "zap", 3.14f);
242 //
243 // NOTE: If you want a globally registered metric, see pw_metric/global.h; in
244 // that contexts, metrics are globally registered without the need to centrally
245 // register in a single place.
246 #define PW_METRIC(...) PW_DELEGATE_BY_ARG_COUNT(_PW_METRIC_, , __VA_ARGS__)
247 #define PW_METRIC_STATIC(...) \
248   PW_DELEGATE_BY_ARG_COUNT(_PW_METRIC_, static, __VA_ARGS__)
249 
250 // Force conversion to uint32_t for non-float types, no matter what the
251 // platform uses as the "u" suffix literal. This enables dispatching to the
252 // correct TypedMetric specialization.
253 #define _PW_METRIC_FLOAT_OR_UINT32(literal)                       \
254   std::conditional_t<std::is_floating_point_v<decltype(literal)>, \
255                      float,                                       \
256                      uint32_t>
257 
258 // Case: PW_METRIC(name, initial_value)
259 #define _PW_METRIC_4(static_def, variable_name, metric_name, init)            \
260   static constexpr uint32_t variable_name##_token =                           \
261       PW_TOKENIZE_STRING_MASK("metrics", _PW_METRIC_TOKEN_MASK, metric_name); \
262   static_def ::pw::metric::TypedMetric<_PW_METRIC_FLOAT_OR_UINT32(init)>      \
263       variable_name = {variable_name##_token, init}
264 
265 // Case: PW_METRIC(group, name, initial_value)
266 #define _PW_METRIC_5(static_def, group, variable_name, metric_name, init)     \
267   static constexpr uint32_t variable_name##_token =                           \
268       PW_TOKENIZE_STRING_MASK("metrics", _PW_METRIC_TOKEN_MASK, metric_name); \
269   static_def ::pw::metric::TypedMetric<_PW_METRIC_FLOAT_OR_UINT32(init)>      \
270       variable_name = {variable_name##_token, init, group.metrics()}
271 
272 // Define a metric group. Works like PW_METRIC, and works in the same contexts.
273 //
274 // Example:
275 //
276 //   class MySubsystem {
277 //    public:
278 //     void DoSomething() {
279 //       attempts.Increment();
280 //       if (ActionSucceeds()) {
281 //         successes.Increment();
282 //       }
283 //     }
284 //     const Group& metrics() const { return metrics_; }
285 //     Group& metrics() { return metrics_; }
286 //
287 //    private:
288 //     PW_METRIC_GROUP(metrics_, "my_subsystem");
289 //     PW_METRIC(metrics_, attempts_, "attempts", 0u);
290 //     PW_METRIC(metrics_, successes_, "successes", 0u);
291 //   };
292 //
293 #define PW_METRIC_GROUP(...) \
294   PW_DELEGATE_BY_ARG_COUNT(_PW_METRIC_GROUP_, , __VA_ARGS__)
295 #define PW_METRIC_GROUP_STATIC(...) \
296   PW_DELEGATE_BY_ARG_COUNT(_PW_METRIC_GROUP_, static, __VA_ARGS__)
297 
298 #define _PW_METRIC_GROUP_3(static_def, variable_name, group_name) \
299   static constexpr uint32_t variable_name##_token =               \
300       PW_TOKENIZE_STRING_DOMAIN("metrics", group_name);           \
301   static_def ::pw::metric::Group variable_name = {variable_name##_token}
302 
303 #define _PW_METRIC_GROUP_4(static_def, parent, variable_name, group_name) \
304   static constexpr uint32_t variable_name##_token =                       \
305       PW_TOKENIZE_STRING_DOMAIN("metrics", group_name);                   \
306   static_def ::pw::metric::Group variable_name = {variable_name##_token,  \
307                                                   parent.children()}
308 
309 }  // namespace pw::metric
310