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