326 lines
8.0 KiB
C++
326 lines
8.0 KiB
C++
#include "metrics/MetricSeries.hpp"
|
|
|
|
#include "catch2/catch_test_macros.hpp"
|
|
|
|
#include <chrono>
|
|
|
|
using namespace std::chrono_literals;
|
|
|
|
using tw::metrics::MetricField;
|
|
using tw::metrics::MetricSeries;
|
|
|
|
using Clock = std::chrono::steady_clock;
|
|
using Series = MetricSeries<std::chrono::seconds>;
|
|
|
|
/** Fixed origin so every test drives the series by hand. */
|
|
static Clock::time_point at(int64_t seconds) {
|
|
return Clock::time_point{} + std::chrono::hours(1) + std::chrono::seconds(seconds);
|
|
}
|
|
|
|
TEST_CASE("Series starts empty", "[metric_series]") {
|
|
Series series(8);
|
|
|
|
REQUIRE(series.capacity() == 8);
|
|
REQUIRE(series.size() == 0);
|
|
REQUIRE(series.is_empty());
|
|
REQUIRE(series.window().is_empty());
|
|
}
|
|
|
|
TEST_CASE("Series rejects a zero capacity", "[metric_series]") {
|
|
REQUIRE_THROWS_AS(Series(0), std::invalid_argument);
|
|
}
|
|
|
|
TEST_CASE("Values in the same interval fold into one bucket", "[metric_series]") {
|
|
Series series(8);
|
|
|
|
series.push(1.0, at(0));
|
|
series.push(3.0, at(0));
|
|
|
|
REQUIRE(series.size() == 1);
|
|
REQUIRE(series.at_age(0).count == 2);
|
|
REQUIRE(series.at_age(0).avg() == 2.0);
|
|
REQUIRE(series.at_age(0).min == 1.0);
|
|
REQUIRE(series.at_age(0).max == 3.0);
|
|
}
|
|
|
|
TEST_CASE("Values in different intervals land in different buckets", "[metric_series]") {
|
|
Series series(8);
|
|
|
|
series.push(1.0, at(0));
|
|
series.push(5.0, at(1));
|
|
|
|
REQUIRE(series.size() == 2);
|
|
REQUIRE(series.at_age(0).sum == 5.0);
|
|
REQUIRE(series.at_age(1).sum == 1.0);
|
|
}
|
|
|
|
TEST_CASE("Intervals without a value become empty buckets", "[metric_series]") {
|
|
Series series(8);
|
|
|
|
series.push(1.0, at(0));
|
|
series.push(4.0, at(3));
|
|
|
|
REQUIRE(series.size() == 4);
|
|
REQUIRE(series.at_age(0).sum == 4.0);
|
|
REQUIRE(series.at_age(1).is_empty());
|
|
REQUIRE(series.at_age(2).is_empty());
|
|
REQUIRE(series.at_age(3).sum == 1.0);
|
|
}
|
|
|
|
TEST_CASE("Series never holds more than its capacity", "[metric_series]") {
|
|
Series series(4);
|
|
|
|
for(int64_t i = 0; i < 10; i++) {
|
|
series.push((double)i, at(i));
|
|
}
|
|
|
|
REQUIRE(series.size() == 4);
|
|
REQUIRE(series.at_age(0).sum == 9.0);
|
|
REQUIRE(series.at_age(3).sum == 6.0);
|
|
}
|
|
|
|
TEST_CASE("A gap wider than the ring leaves only the newest bucket filled", "[metric_series]") {
|
|
Series series(4);
|
|
|
|
series.push(1.0, at(0));
|
|
series.push(2.0, at(100));
|
|
|
|
REQUIRE(series.size() == 4);
|
|
REQUIRE(series.at_age(0).sum == 2.0);
|
|
REQUIRE(series.at_age(1).is_empty());
|
|
REQUIRE(series.at_age(2).is_empty());
|
|
REQUIRE(series.at_age(3).is_empty());
|
|
}
|
|
|
|
TEST_CASE("Buckets dropped by wrapping do not come back", "[metric_series]") {
|
|
Series series(4);
|
|
|
|
series.push(100.0, at(0));
|
|
|
|
for(int64_t i = 1; i < 5; i++) {
|
|
series.push(1.0, at(i));
|
|
}
|
|
|
|
REQUIRE(series.window().max == 1.0);
|
|
}
|
|
|
|
TEST_CASE("Window aggregates across buckets", "[metric_series]") {
|
|
Series series(8);
|
|
|
|
series.push(4.0, at(0));
|
|
series.push(1.0, at(1));
|
|
series.push(7.0, at(2));
|
|
|
|
auto window = series.window();
|
|
|
|
REQUIRE(window.count == 3);
|
|
REQUIRE(window.sum == 12.0);
|
|
REQUIRE(window.min == 1.0);
|
|
REQUIRE(window.max == 7.0);
|
|
REQUIRE(window.avg() == 4.0);
|
|
}
|
|
|
|
TEST_CASE("Window can be narrowed to the newest buckets", "[metric_series]") {
|
|
Series series(8);
|
|
|
|
series.push(4.0, at(0));
|
|
series.push(1.0, at(1));
|
|
series.push(7.0, at(2));
|
|
|
|
auto window = series.window(2);
|
|
|
|
REQUIRE(window.count == 2);
|
|
REQUIRE(window.min == 1.0);
|
|
REQUIRE(window.max == 7.0);
|
|
}
|
|
|
|
TEST_CASE("Empty buckets do not skew the window extremes", "[metric_series]") {
|
|
Series series(8);
|
|
|
|
series.push(5.0, at(0));
|
|
series.push(9.0, at(4));
|
|
|
|
auto window = series.window();
|
|
|
|
REQUIRE(window.count == 2);
|
|
REQUIRE(window.min == 5.0);
|
|
REQUIRE(window.max == 9.0);
|
|
}
|
|
|
|
TEST_CASE("A late value folds into the bucket it belongs to", "[metric_series]") {
|
|
Series series(8);
|
|
|
|
series.push(1.0, at(0));
|
|
series.push(2.0, at(2));
|
|
series.push(6.0, at(1));
|
|
|
|
REQUIRE(series.size() == 3);
|
|
REQUIRE(series.at_age(1).sum == 6.0);
|
|
REQUIRE(series.at_age(0).sum == 2.0);
|
|
}
|
|
|
|
TEST_CASE("A value older than every bucket held is dropped", "[metric_series]") {
|
|
Series series(4);
|
|
|
|
for(int64_t i = 0; i < 4; i++) {
|
|
series.push(1.0, at(i));
|
|
}
|
|
|
|
series.push(99.0, at(-10));
|
|
|
|
REQUIRE(series.size() == 4);
|
|
REQUIRE(series.window().max == 1.0);
|
|
REQUIRE(series.window().count == 4);
|
|
}
|
|
|
|
TEST_CASE("Linearize writes buckets oldest first", "[metric_series]") {
|
|
Series series(8);
|
|
|
|
series.push(1.0, at(0));
|
|
series.push(2.0, at(1));
|
|
series.push(3.0, at(2));
|
|
|
|
std::vector<double> xs;
|
|
std::vector<double> ys;
|
|
|
|
size_t count = series.linearize(xs, ys, MetricField::Sum);
|
|
|
|
REQUIRE(count == 3);
|
|
REQUIRE(xs == std::vector<double>{-2.0, -1.0, 0.0});
|
|
REQUIRE(ys == std::vector<double>{1.0, 2.0, 3.0});
|
|
}
|
|
|
|
TEST_CASE("Linearize can be limited to the newest buckets", "[metric_series]") {
|
|
Series series(8);
|
|
|
|
series.push(1.0, at(0));
|
|
series.push(2.0, at(1));
|
|
series.push(3.0, at(2));
|
|
|
|
std::vector<double> xs;
|
|
std::vector<double> ys;
|
|
|
|
size_t count = series.linearize(xs, ys, MetricField::Sum, 2);
|
|
|
|
REQUIRE(count == 2);
|
|
REQUIRE(xs == std::vector<double>{-1.0, 0.0});
|
|
REQUIRE(ys == std::vector<double>{2.0, 3.0});
|
|
}
|
|
|
|
TEST_CASE("Linearize can leave out the newest buckets", "[metric_series]") {
|
|
Series series(8);
|
|
|
|
series.push(1.0, at(0));
|
|
series.push(2.0, at(1));
|
|
series.push(3.0, at(2));
|
|
|
|
std::vector<double> xs;
|
|
std::vector<double> ys;
|
|
|
|
size_t count = series.linearize(xs, ys, MetricField::Sum, 8, 1);
|
|
|
|
REQUIRE(count == 2);
|
|
REQUIRE(ys == std::vector<double>{1.0, 2.0});
|
|
}
|
|
|
|
TEST_CASE("Skipping the newest bucket keeps the ages of the rest", "[metric_series]") {
|
|
Series series(8);
|
|
|
|
series.push(1.0, at(0));
|
|
series.push(2.0, at(1));
|
|
series.push(3.0, at(2));
|
|
|
|
std::vector<double> xs;
|
|
std::vector<double> ys;
|
|
|
|
series.linearize(xs, ys, MetricField::Sum, 8, 1);
|
|
|
|
REQUIRE(xs == std::vector<double>{-2.0, -1.0});
|
|
}
|
|
|
|
TEST_CASE("Skipping more buckets than are held writes nothing", "[metric_series]") {
|
|
Series series(8);
|
|
|
|
series.push(1.0, at(0));
|
|
|
|
std::vector<double> xs;
|
|
std::vector<double> ys;
|
|
|
|
size_t count = series.linearize(xs, ys, MetricField::Sum, 8, 4);
|
|
|
|
REQUIRE(count == 0);
|
|
REQUIRE(xs.empty());
|
|
REQUIRE(ys.empty());
|
|
}
|
|
|
|
TEST_CASE("A limit counts buckets that were not skipped", "[metric_series]") {
|
|
Series series(8);
|
|
|
|
for(int64_t i = 0; i < 5; i++) {
|
|
series.push((double)i, at(i));
|
|
}
|
|
|
|
std::vector<double> xs;
|
|
std::vector<double> ys;
|
|
|
|
size_t count = series.linearize(xs, ys, MetricField::Sum, 2, 1);
|
|
|
|
REQUIRE(count == 2);
|
|
REQUIRE(ys == std::vector<double>{2.0, 3.0});
|
|
}
|
|
|
|
TEST_CASE("Linearize reports empty buckets as zero", "[metric_series]") {
|
|
Series series(8);
|
|
|
|
series.push(5.0, at(0));
|
|
series.push(9.0, at(2));
|
|
|
|
std::vector<double> xs;
|
|
std::vector<double> ys;
|
|
|
|
series.linearize(xs, ys, MetricField::Max);
|
|
|
|
REQUIRE(ys == std::vector<double>{5.0, 0.0, 9.0});
|
|
}
|
|
|
|
TEST_CASE("Linearize resizes the vectors it is given", "[metric_series]") {
|
|
Series series(8);
|
|
|
|
series.push(1.0, at(0));
|
|
|
|
std::vector<double> xs(64, 7.0);
|
|
std::vector<double> ys(64, 7.0);
|
|
|
|
series.linearize(xs, ys, MetricField::Avg);
|
|
|
|
REQUIRE(xs.size() == 1);
|
|
REQUIRE(ys.size() == 1);
|
|
}
|
|
|
|
TEST_CASE("Clear empties the series but keeps its capacity", "[metric_series]") {
|
|
Series series(8);
|
|
|
|
series.push(1.0, at(0));
|
|
series.clear();
|
|
|
|
REQUIRE(series.is_empty());
|
|
REQUIRE(series.capacity() == 8);
|
|
|
|
series.push(2.0, at(1));
|
|
|
|
REQUIRE(series.size() == 1);
|
|
REQUIRE(series.at_age(0).sum == 2.0);
|
|
}
|
|
|
|
TEST_CASE("A coarser interval folds more values together", "[metric_series]") {
|
|
MetricSeries<std::chrono::minutes> series(4);
|
|
|
|
series.push(1.0, at(0));
|
|
series.push(2.0, at(30));
|
|
series.push(3.0, at(90));
|
|
|
|
REQUIRE(series.size() == 2);
|
|
REQUIRE(series.at_age(1).count == 2);
|
|
REQUIRE(series.at_age(0).sum == 3.0);
|
|
}
|