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