blob: 5a6a3e03054431792e8f57ad7b66cb812af1ea2d [file] [log] [blame]
// Copyright 2020 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#pragma once
#include <algorithm>
#include <initializer_list>
#include <limits>
#include "pw_containers/intrusive_list.h"
#include "pw_preprocessor/arguments.h"
#include "pw_tokenizer/tokenize.h"
namespace pw::metric {
// Currently, this is for tokens, but later may be a char* when non-tokenized
// metric names are supported.
using tokenizer::Token;
#define _PW_METRIC_TOKEN_MASK 0x7fffffff
// An individual metric. There are only two supported types: uint32_t and
// float. More complicated compound metrics can be built on these primitives.
// See the documentation for a discussion for this design was selected.
//
// Size: 12 bytes / 96 bits - next, name, value.
//
// TODO(keir): Implement Set() and Increment() using atomics.
// TODO(keir): Consider an alternative structure where metrics have pointers to
// parent groups, which would enable (1) safe destruction and (2) safe static
// initialization, but at the cost of an additional 4 bytes per metric and 4
// bytes per group..
class Metric : public IntrusiveList<Metric>::Item {
public:
Token name() const { return name_and_type_ & kTokenMask; }
bool is_float() const { return (name_and_type_ & kTypeMask) == kTypeFloat; }
bool is_int() const { return (name_and_type_ & kTypeMask) == kTypeInt; }
float as_float() const;
uint32_t as_int() const;
// Dump a metric or metrics to logs. Level determines the indentation
// indent_level up to a maximum of 4. Example output:
//
// "$FCM4qQ==": 0,
//
// Note the base64-encoded token name. Detokenization tools are necessary to
// convert this to human-readable form.
void Dump(int indent_level = 0);
static void Dump(IntrusiveList<Metric>& metrics, int indent_level = 0);
// Disallow copy and assign.
Metric(Metric const&) = delete;
void operator=(const Metric&) = delete;
protected:
Metric(Token name, float value)
: name_and_type_((name & kTokenMask) | kTypeFloat), float_(value) {}
Metric(Token name, uint32_t value)
: name_and_type_((name & kTokenMask) | kTypeInt), uint_(value) {}
Metric(Token name, float value, IntrusiveList<Metric>& metrics);
Metric(Token name, uint32_t value, IntrusiveList<Metric>& metrics);
// Hide mutation methods, and only offer write access through the specialized
// TypedMetric below. This makes it impossible to call metric.Increment() on
// a float metric at compile time.
void Increment(uint32_t amount = 1);
void SetInt(uint32_t value);
void SetFloat(float value);
private:
// The name of this metric as a token; from PW_TOKENIZE_STRING("my_metric").
// Last bit of the token is used to store int or float; 0 == int, 1 == float.
Token name_and_type_;
union {
float float_;
uint32_t uint_;
};
enum : uint32_t {
kTokenMask = _PW_METRIC_TOKEN_MASK, // 0x7fff'ffff
kTypeMask = 0x8000'0000,
kTypeFloat = 0x8000'0000,
kTypeInt = 0x0,
};
};
// TypedMetric provides a type-safe wrapper the runtime-typed Metric object.
// Note: Definition omitted to prevent accidental instantiation.
// TODO(keir): Provide a more precise error message via static assert.
template <typename T>
class TypedMetric;
// A metric for floats. Does not offer an Increment() function, since it is too
// easy to do unsafe operations like accumulating small values in floats.
template <>
class TypedMetric<float> : public Metric {
public:
TypedMetric(Token name, float value) : Metric(name, value) {}
TypedMetric(Token name, float value, IntrusiveList<Metric>& metrics)
: Metric(name, value, metrics) {}
void Set(float value) { SetFloat(value); }
float value() const { return Metric::as_float(); }
private:
// Shadow these accessors to hide them on the typed version of Metric.
float as_float() const { return 0.0; }
uint32_t as_int() const { return 0; }
};
// A metric for uint32_ts. Offers both Set() and Increment().
template <>
class TypedMetric<uint32_t> : public Metric {
public:
TypedMetric(Token name, uint32_t value) : Metric(name, value) {}
TypedMetric(Token name, uint32_t value, IntrusiveList<Metric>& metrics)
: Metric(name, value, metrics) {}
void Increment(uint32_t amount = 1u) { Metric::Increment(amount); }
void Set(uint32_t value) { SetInt(value); }
uint32_t value() const { return Metric::as_int(); }
private:
// Shadow these accessors to hide them on the typed version of Metric.
float as_float() const { return 0.0; }
uint32_t as_int() const { return 0; }
};
// A metric tree; consisting of children groups and leaf metrics.
//
// Size: 16 bytes/128 bits - next, name, metrics, children.
class Group : public IntrusiveList<Group>::Item {
public:
Group(Token name);
Group(Token name, IntrusiveList<Group>& groups);
Token name() const { return name_; }
void Add(Metric& metric) { metrics_.push_front(metric); }
void Add(Group& group) { children_.push_front(group); }
IntrusiveList<Metric>& metrics() { return metrics_; }
IntrusiveList<Group>& children() { return children_; }
const IntrusiveList<Metric>& metrics() const { return metrics_; }
const IntrusiveList<Group>& children() const { return children_; }
// Dump a metric group or groups to logs. Level determines the indentation
// indent_level up to a maximum of 4. Example output:
//
// "$6doqFw==": {
// "$05OCZw==": {
// "$VpPfzg==": 1,
// "$LGPMBQ==": 1.000000,
// "$+iJvUg==": 5,
// }
// "$9hPNxw==": 65,
// "$oK7HmA==": 13,
// "$FCM4qQ==": 0,
// }
//
// Note the base64-encoded token name. Detokenization tools are necessary to
// convert this to human-readable form.
void Dump(int indent_level = 0);
static void Dump(IntrusiveList<Group>& groups, int indent_level = 0);
// Disallow copy and assign.
Group(Group const&) = delete;
void operator=(const Group&) = delete;
private:
// The name of this group as a token; from PW_TOKENIZE_STRING("my_group").
Token name_;
IntrusiveList<Metric> metrics_;
IntrusiveList<Group> children_;
};
// Declare a metric, optionally adding it to a group. Use:
//
// PW_METRIC(variable_name, metric_name, value)
// PW_METRIC(group, variable_name, metric_name, value)
//
// - variable_name is an identifier
// - metric_name is a string name for the metric (will be tokenized)
// - value must be either a floating point value (3.2f) or unsigned int (21u).
// - group is a Group instance.
//
// The macro declares a variable or member named "name" with type Metric, and
// works in three contexts: global, local, and member.
//
// 1. At global scope
//
// PW_METRIC(foo, 15.5f);
//
// void MyFunc() {
// foo.Increment();
// }
//
// 2. At local function or member function scope:
//
// void MyFunc() {
// PW_METRIC(foo, "foo", 15.5f);
// foo.Increment();
// // foo goes out of scope here; be careful!
// }
//
// 3. At member level inside a class or struct:
//
// struct MyStructy {
// void DoSomething() {
// somethings_.Increment();
// }
// // Every instance of MyStructy will have a separate somethings counter.
// PW_METRIC(somethings_, "somethings", 0u);
// }
//
// You can also put a metric into a group with the macro. Metrics can belong to
// strictly one group, otherwise a assertion will fail. Example:
//
// PW_METRIC_GROUP(my_group, "my_group_name_here");
// PW_METRIC(my_group, foo_, "foo", 0.2f);
// PW_METRIC(my_group, bar_, "bar", 44000u);
// PW_METRIC(my_group, zap_, "zap", 3.14f);
//
// NOTE: If you want a globally registered metric, see pw_metric/global.h; in
// that contexts, metrics are globally registered without the need to centrally
// register in a single place.
#define PW_METRIC(...) PW_DELEGATE_BY_ARG_COUNT(_PW_METRIC_, , __VA_ARGS__)
#define PW_METRIC_STATIC(...) \
PW_DELEGATE_BY_ARG_COUNT(_PW_METRIC_, static, __VA_ARGS__)
// Force conversion to uint32_t for non-float types, no matter what the
// platform uses as the "u" suffix literal. This enables dispatching to the
// correct TypedMetric specialization.
#define _PW_METRIC_FLOAT_OR_UINT32(literal) \
std::conditional_t<std::is_floating_point_v<decltype(literal)>, \
float, \
uint32_t>
// Case: PW_METRIC(name, initial_value)
#define _PW_METRIC_4(static_def, variable_name, metric_name, init) \
static constexpr uint32_t variable_name##_token = \
PW_TOKENIZE_STRING_MASK("metrics", _PW_METRIC_TOKEN_MASK, metric_name); \
static_def ::pw::metric::TypedMetric<_PW_METRIC_FLOAT_OR_UINT32(init)> \
variable_name = {variable_name##_token, init}
// Case: PW_METRIC(group, name, initial_value)
#define _PW_METRIC_5(static_def, group, variable_name, metric_name, init) \
static constexpr uint32_t variable_name##_token = \
PW_TOKENIZE_STRING_MASK("metrics", _PW_METRIC_TOKEN_MASK, metric_name); \
static_def ::pw::metric::TypedMetric<_PW_METRIC_FLOAT_OR_UINT32(init)> \
variable_name = {variable_name##_token, init, group.metrics()}
// Define a metric group. Works like PW_METRIC, and works in the same contexts.
//
// Example:
//
// class MySubsystem {
// public:
// void DoSomething() {
// attempts.Increment();
// if (ActionSucceeds()) {
// successes.Increment();
// }
// }
// const Group& metrics() const { return metrics_; }
// Group& metrics() { return metrics_; }
//
// private:
// PW_METRIC_GROUP(metrics_, "my_subsystem");
// PW_METRIC(metrics_, attempts_, "attempts", 0u);
// PW_METRIC(metrics_, successes_, "successes", 0u);
// };
//
#define PW_METRIC_GROUP(...) \
PW_DELEGATE_BY_ARG_COUNT(_PW_METRIC_GROUP_, , __VA_ARGS__)
#define PW_METRIC_GROUP_STATIC(...) \
PW_DELEGATE_BY_ARG_COUNT(_PW_METRIC_GROUP_, static, __VA_ARGS__)
#define _PW_METRIC_GROUP_3(static_def, variable_name, group_name) \
static constexpr uint32_t variable_name##_token = \
PW_TOKENIZE_STRING_DOMAIN("metrics", group_name); \
static_def ::pw::metric::Group variable_name = {variable_name##_token}
#define _PW_METRIC_GROUP_4(static_def, parent, variable_name, group_name) \
static constexpr uint32_t variable_name##_token = \
PW_TOKENIZE_STRING_DOMAIN("metrics", group_name); \
static_def ::pw::metric::Group variable_name = {variable_name##_token, \
parent.children()}
} // namespace pw::metric