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