blob: 958460f3319a7bc81a9d46025c1e7c5aff03208c [file]
// 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.
#include "pw_metric/metric.h"
#include <cstdint>
#include <limits>
#include "pw_metric/config.h"
#include "pw_tokenizer/tokenize.h"
#include "pw_unit_test/framework.h"
namespace pw::metric {
namespace {
TEST(Metric, FloatFromObject) {
// Note leading 4 bits are stripped from the name to store the type.
Token token = 0xf1223344;
TypedMetric<float> m(token, 1.5f);
EXPECT_EQ(m.name(), 0x01223344u);
EXPECT_TRUE(m.is_float());
EXPECT_FALSE(m.is_uint32());
EXPECT_EQ(m.value(), 1.5f);
m.Set(55.1f);
EXPECT_EQ(m.value(), 55.1f);
// No increment operation for float.
}
TEST(Metric, IntFromObject) {
// Note leading 4 bits are stripped from the name to store the type.
Token token = 0xf1223344;
TypedMetric<uint32_t> m(token, static_cast<uint32_t>(31337u));
EXPECT_EQ(m.name(), 0x01223344u);
EXPECT_TRUE(m.is_uint32());
EXPECT_FALSE(m.is_float());
EXPECT_EQ(m.value(), 31337u);
m.Set(414u);
EXPECT_EQ(m.value(), 414u);
m.Increment();
EXPECT_EQ(m.value(), 415u);
m.Increment(11u);
EXPECT_EQ(m.value(), 426u);
}
TEST(Metric, IntLimits) {
// Note leading 4 bits are stripped from the name to store the type.
Token token = 0xf1223344;
TypedMetric<uint32_t> m(token, static_cast<uint32_t>(31337u));
EXPECT_EQ(m.name(), 0x01223344u);
EXPECT_TRUE(m.is_uint32());
EXPECT_FALSE(m.is_float());
EXPECT_EQ(m.value(), 31337u);
m.Set(10);
EXPECT_EQ(m.value(), 10u);
m.Decrement(7);
EXPECT_EQ(m.value(), 3u);
m.Decrement(7);
EXPECT_EQ(m.value(), 0u);
m.Decrement(7);
EXPECT_EQ(m.value(), 0u);
m.Set(std::numeric_limits<uint32_t>::max() - 10);
EXPECT_EQ(m.value(), std::numeric_limits<uint32_t>::max() - 10);
m.Increment();
EXPECT_EQ(m.value(), std::numeric_limits<uint32_t>::max() - 9);
m.Increment(9);
EXPECT_EQ(m.value(), std::numeric_limits<uint32_t>::max());
m.Increment(2);
EXPECT_EQ(m.value(), std::numeric_limits<uint32_t>::max());
}
#if PW_METRIC_CONFIG_ENABLE_64BIT
TEST(Metric, Uint64FromObject) {
// Note leading 4 bits are stripped from the name to store the type.
Token token = 0xf1223344;
TypedMetric<uint64_t> m(token, static_cast<uint64_t>(31337ULL));
EXPECT_EQ(m.name(), 0x01223344u);
EXPECT_TRUE(m.is_uint64());
EXPECT_FALSE(m.is_uint32());
EXPECT_FALSE(m.is_float());
EXPECT_EQ(m.value(), 31337ULL);
m.Set(414ULL);
EXPECT_EQ(m.value(), 414ULL);
m.Increment();
EXPECT_EQ(m.value(), 415ULL);
m.Increment(11ULL);
EXPECT_EQ(m.value(), 426ULL);
}
TEST(Metric, Uint64Limits) {
Token token = 0xf1223344;
TypedMetric<uint64_t> m(token, static_cast<uint64_t>(31337ULL));
EXPECT_EQ(m.name(), 0x01223344u);
EXPECT_TRUE(m.is_uint64());
EXPECT_EQ(m.value(), 31337ULL);
m.Set(10);
EXPECT_EQ(m.value(), 10ULL);
m.Decrement(7);
EXPECT_EQ(m.value(), 3ULL);
m.Decrement(7);
EXPECT_EQ(m.value(), 0ULL);
m.Decrement(7);
EXPECT_EQ(m.value(), 0ULL);
m.Set(std::numeric_limits<uint64_t>::max() - 10);
EXPECT_EQ(m.value(), std::numeric_limits<uint64_t>::max() - 10);
m.Increment();
EXPECT_EQ(m.value(), std::numeric_limits<uint64_t>::max() - 9);
m.Increment(9);
EXPECT_EQ(m.value(), std::numeric_limits<uint64_t>::max());
m.Increment(2);
EXPECT_EQ(m.value(), std::numeric_limits<uint64_t>::max());
}
TEST(Metric, Uint64FromMacroLocal) {
PW_METRIC_TYPED(m, "some_metric", uint64_t, 14ULL);
EXPECT_TRUE(m.is_uint64());
EXPECT_EQ(m.value(), 14ULL);
}
TEST(Metric, Int64Basic) {
Token token = 0xf1223344;
TypedMetric<int64_t> m(token, static_cast<int64_t>(31337LL));
EXPECT_EQ(m.name(), 0x01223344u);
EXPECT_TRUE(m.is_int64());
EXPECT_FALSE(m.is_uint64());
EXPECT_FALSE(m.is_uint32());
EXPECT_FALSE(m.is_float());
EXPECT_EQ(m.value(), 31337LL);
m.Set(414LL);
EXPECT_EQ(m.value(), 414LL);
m.Increment();
EXPECT_EQ(m.value(), 415LL);
m.Increment(11LL);
EXPECT_EQ(m.value(), 426LL);
}
TEST(Metric, Int64Limits) {
Token token = 0xf1223344;
TypedMetric<int64_t> m(token, static_cast<int64_t>(31337LL));
EXPECT_EQ(m.name(), 0x01223344u);
EXPECT_TRUE(m.is_int64());
EXPECT_EQ(m.value(), 31337LL);
m.Set(10);
EXPECT_EQ(m.value(), 10LL);
m.Decrement(7);
EXPECT_EQ(m.value(), 3LL);
m.Decrement(7);
EXPECT_EQ(m.value(), -4LL);
m.Set(std::numeric_limits<int64_t>::min() + 10);
EXPECT_EQ(m.value(), std::numeric_limits<int64_t>::min() + 10);
m.Decrement(7);
EXPECT_EQ(m.value(), std::numeric_limits<int64_t>::min() + 3);
m.Decrement(7);
EXPECT_EQ(m.value(), std::numeric_limits<int64_t>::min());
m.Decrement(7);
EXPECT_EQ(m.value(), std::numeric_limits<int64_t>::min());
m.Set(std::numeric_limits<int64_t>::max() - 10);
EXPECT_EQ(m.value(), std::numeric_limits<int64_t>::max() - 10);
m.Increment();
EXPECT_EQ(m.value(), std::numeric_limits<int64_t>::max() - 9);
m.Increment(9);
EXPECT_EQ(m.value(), std::numeric_limits<int64_t>::max());
m.Increment(2);
EXPECT_EQ(m.value(), std::numeric_limits<int64_t>::max());
}
TEST(Metric, Int64FromMacroLocal) {
PW_METRIC_TYPED(m, "some_metric", int64_t, 14LL);
EXPECT_TRUE(m.is_int64());
EXPECT_EQ(m.value(), 14LL);
}
TEST(Metric, DoubleBasic) {
Token token = 0xf1223344;
TypedMetric<double> m(token, 1.5);
EXPECT_EQ(m.name(), 0x01223344u);
EXPECT_TRUE(m.is_double());
EXPECT_FALSE(m.is_float());
EXPECT_EQ(m.value(), 1.5);
m.Set(55.1);
EXPECT_EQ(m.value(), 55.1);
}
TEST(Metric, DoubleFromMacroLocal) {
PW_METRIC_TYPED(m, "some_metric", double, 14.2);
EXPECT_TRUE(m.is_double());
EXPECT_EQ(m.value(), 14.2);
}
#endif // PW_METRIC_CONFIG_ENABLE_64BIT
TEST(Metric, BoolBasic) {
Token token = 0xf1223344;
TypedMetric<bool> m(token, true);
EXPECT_EQ(m.name(), 0x01223344u);
EXPECT_TRUE(m.is_bool());
EXPECT_FALSE(m.is_uint32());
EXPECT_EQ(m.value(), true);
m.Set(false);
EXPECT_EQ(m.value(), false);
}
TEST(Metric, Int32Basic) {
Token token = 0xf1223344;
TypedMetric<int32_t> m(token, -31337);
EXPECT_EQ(m.name(), 0x01223344u);
EXPECT_TRUE(m.is_int32());
EXPECT_FALSE(m.is_uint32());
EXPECT_EQ(m.value(), -31337);
m.Set(414);
EXPECT_EQ(m.value(), 414);
m.Increment();
EXPECT_EQ(m.value(), 415);
m.Increment(11);
EXPECT_EQ(m.value(), 426);
}
TEST(Metric, Int32Limits) {
Token token = 0xf1223344;
TypedMetric<int32_t> m(token, 10);
EXPECT_EQ(m.name(), 0x01223344u);
EXPECT_TRUE(m.is_int32());
EXPECT_EQ(m.value(), 10);
m.Decrement(7);
EXPECT_EQ(m.value(), 3);
m.Decrement(7);
EXPECT_EQ(m.value(), -4);
m.Set(std::numeric_limits<int32_t>::min() + 10);
EXPECT_EQ(m.value(), std::numeric_limits<int32_t>::min() + 10);
m.Decrement(7);
EXPECT_EQ(m.value(), std::numeric_limits<int32_t>::min() + 3);
m.Decrement(7);
EXPECT_EQ(m.value(), std::numeric_limits<int32_t>::min());
m.Decrement(7);
EXPECT_EQ(m.value(), std::numeric_limits<int32_t>::min());
m.Set(std::numeric_limits<int32_t>::max() - 10);
EXPECT_EQ(m.value(), std::numeric_limits<int32_t>::max() - 10);
m.Increment();
EXPECT_EQ(m.value(), std::numeric_limits<int32_t>::max() - 9);
m.Increment(9);
EXPECT_EQ(m.value(), std::numeric_limits<int32_t>::max());
m.Increment(2);
EXPECT_EQ(m.value(), std::numeric_limits<int32_t>::max());
}
TEST(Metric, TokenBasic) {
Token token = 0xf1223344;
TypedMetric<TokenValue> m(token, TokenValue(0x12345678));
EXPECT_EQ(m.name(), 0x01223344u);
EXPECT_TRUE(m.is_token());
EXPECT_FALSE(m.is_uint32());
EXPECT_EQ(m.value().value, 0x12345678u);
m.Set(TokenValue(0x87654321));
EXPECT_EQ(m.value().value, 0x87654321u);
}
TEST(Metric, NewTypesFromMacroLocal) {
PW_METRIC_TYPED(m_bool, "bool_metric", bool, true);
EXPECT_TRUE(m_bool.is_bool());
EXPECT_EQ(m_bool.value(), true);
PW_METRIC_TYPED(m_int32, "int32_metric", int32_t, -15);
EXPECT_TRUE(m_int32.is_int32());
EXPECT_EQ(m_int32.value(), -15);
PW_METRIC_TYPED(m_token, "token_metric", TokenValue, TokenValue(0xabcdef));
EXPECT_TRUE(m_token.is_token());
EXPECT_EQ(m_token.value().value, 0xabcdefu);
}
TEST(Metric, IntFromMacroLocal) {
PW_METRIC(m, "some_metric", 14u);
EXPECT_TRUE(m.is_uint32());
EXPECT_EQ(m.value(), 14u);
}
TEST(Metric, FloatFromMacroLocal) {
PW_METRIC(m, "some_metric", 3.14f);
EXPECT_TRUE(m.is_float());
EXPECT_EQ(m.value(), 3.14f);
}
TEST(Metric, ExposedToken) {
PW_METRIC(m, "some_metric", 0u);
static constexpr Token kToken = PW_METRIC_TOKEN("some_metric");
EXPECT_EQ(m.name(), kToken);
}
TEST(Metric, GroupMacroInFunctionContext) {
PW_METRIC_GROUP(group, "fancy_subsystem");
PW_METRIC(group, x, "x", 5555u);
PW_METRIC(group, y, "y", 6.0f);
// These calls are needed to satisfy GCC, otherwise GCC warns about an unused
// variable (even though it is used and passed to the group, which adds it):
//
// metric_test.cc:72:20: error: variable 'x' set but not used
// [-Werror=unused-but-set-variable]
//
x.Increment(10);
y.Set(5.0f);
group.Dump();
EXPECT_EQ(group.metrics().size(), 2u);
}
// The below are compile tests to ensure the macros work at global scope.
// Case 1: No group specified.
PW_METRIC(global_x, "global_x", 5555u);
PW_METRIC(global_y, "global_y", 6.0f);
// Case 2: Group specified.
PW_METRIC_GROUP(global_group, "a_global_group");
PW_METRIC(global_group, global_z, "global_x", 5555u);
PW_METRIC(global_group, global_w, "global_y", 6.0f);
TEST(Metric, GlobalScope) {
EXPECT_EQ(global_x.value(), 5555u);
EXPECT_EQ(global_y.value(), 6.0f);
}
// A fake object to illustrate the API and show nesting metrics.
// This also tests creating metrics as members inside a class.
class I2cBus {
public:
void Transaction() {
// An entirely unconvincing fake I2C transaction implementation.
transactions_.Increment();
bytes_sent_.Increment(5);
}
Group& stats() { return metrics_; }
private:
// Test a group with metrics in it, as a class member.
// Note that in many cases, the group would be passed in externally instead.
PW_METRIC_GROUP(metrics_, "i2c");
PW_METRIC(metrics_, bus_errors_, "bus_errors", 0u);
PW_METRIC(metrics_, transactions_, "transactions", 0u);
PW_METRIC(metrics_, bytes_sent_, "bytes_sent", 0u);
// Test metrics without a group, as a class member.
PW_METRIC(a, "a", 0u);
PW_METRIC(b, "b", 10.0f);
PW_METRIC(c, "c", 525u);
};
class Gyro {
public:
Gyro(I2cBus& i2c_bus, Group& parent_metrics) : i2c_bus_(i2c_bus) {
// Make the gyro a child of the I2C bus. Note that the other arrangement,
// where the i2c bus is a child of the gyro, doesn't work if there are
// multiple objects on the I2C bus due to the intrusive list mechanism.
parent_metrics.Add(metrics_);
}
void Init() {
i2c_bus_.Transaction();
initialized_.Increment();
}
void ReadAngularVelocity() {
// Pretend to be doing some transactions and pulling angular velocity.
// Pretend this gyro is inefficient and requires multiple transactions.
i2c_bus_.Transaction();
i2c_bus_.Transaction();
i2c_bus_.Transaction();
num_samples_.Increment();
}
Group& stats() { return metrics_; }
private:
I2cBus& i2c_bus_;
// In this case, "gyro" groups the relevant metrics, but it is possible to
// have freestanding metrics directly without a group; however, those
// free-standing metrics must be added to a group or list supplied elsewhere
// for collection.
PW_METRIC_GROUP(metrics_, "gyro");
PW_METRIC(metrics_, num_samples_, "num_samples", 1u);
PW_METRIC(metrics_, init_time_us_, "init_time_us", 1.0f);
PW_METRIC(metrics_, initialized_, "initialized", 0u);
};
// The below test produces output like:
//
// "$6doqFw==": {
// "$05OCZw==": {
// "$VpPfzg==": 1,
// "$LGPMBQ==": 1.000000,
// "$+iJvUg==": 5,
// }
// "$9hPNxw==": 65,
// "$oK7HmA==": 13,
// "$FCM4qQ==": 0,
// }
//
// Note the metric names are tokenized with base64. Decoding requires using the
// Pigweed detokenizer. With a detokenizing-enabled logger, you would get:
//
// "i2c": {
// "gyro": {
// "num_sampleses": 1,
// "init_time_us": 1.000000,
// "initialized": 5,
// }
// "bus_errors": 65,
// "transactions": 13,
// "bytes_sent": 0,
// }
//
TEST(Metric, InlineConstructionWithGroups) {
I2cBus i2c_bus;
Gyro gyro(i2c_bus, i2c_bus.stats());
gyro.Init();
gyro.ReadAngularVelocity();
gyro.ReadAngularVelocity();
gyro.ReadAngularVelocity();
gyro.ReadAngularVelocity();
// This "test" doesn't really test anything, and more illustrates how to the
// metrics could be instantiated in an object tree.
//
// Unfortunatlely, testing dump is difficult since we don't have log
// redirection for tests.
i2c_bus.stats().Dump();
}
// PW_METRIC_STATIC doesn't support class scopes, since a definition must be
// provided outside of the class body.
// TODO(keir): add support for class scopes and enable this test
#if 0
class MetricTest: public ::testing::Test {
public:
void Increment() {
metric_.Increment();
}
private:
PW_METRIC_STATIC(metric_, "metric", 0u);
};
TEST_F(MetricTest, StaticWithinAClass) {
Increment();
}
#endif
Metric* StaticMetricIncrement() {
PW_METRIC_STATIC(metric, "metric", 0u);
metric.Increment();
return &metric;
}
TEST(Metric, StaticWithinAFunction) {
Metric* metric = StaticMetricIncrement();
EXPECT_EQ(metric->as_int(), 1u);
StaticMetricIncrement();
EXPECT_EQ(metric->as_int(), 2u);
}
TEST(Metric, ListFind) {
PW_METRIC_GROUP(group, "fancy_subsystem");
PW_METRIC(group, x, "x", 5555u);
PW_METRIC(group, y, "y", 6.0f);
static constexpr Token token_x = PW_METRIC_TOKEN("x");
static constexpr Token token_y = PW_METRIC_TOKEN("y");
static constexpr Token token_z = PW_METRIC_TOKEN("z");
EXPECT_EQ(group.metrics().find(token_x), &x);
EXPECT_EQ(group.metrics().find(token_y), &y);
EXPECT_EQ(group.metrics().find(token_z), nullptr);
}
TEST(Metric, GroupListFind) {
PW_METRIC_GROUP(parent, "parent");
PW_METRIC_GROUP(parent, child1, "child1");
PW_METRIC_GROUP(parent, child2, "child2");
static constexpr Token token_1 =
PW_TOKENIZE_STRING_DOMAIN("metrics", "child1");
static constexpr Token token_2 =
PW_TOKENIZE_STRING_DOMAIN("metrics", "child2");
static constexpr Token token_3 =
PW_TOKENIZE_STRING_DOMAIN("metrics", "child3");
EXPECT_EQ(parent.children().find(token_1), &child1);
EXPECT_EQ(parent.children().find(token_2), &child2);
EXPECT_EQ(parent.children().find(token_3), nullptr);
}
TEST(Metric, ListForEachWithFunctor) {
PW_METRIC_GROUP(group, "fancy_subsystem");
PW_METRIC(group, x, "x", 5555u);
PW_METRIC(group, y, "y", 6.0f);
struct Count {
int count = 0;
void operator()(const UntypedMetric&) { count++; }
};
Count c = group.metrics().for_each(Count());
EXPECT_EQ(c.count, 2);
}
} // namespace
} // namespace pw::metric