diff --git a/packages/livestatus/include/livestatus/RRDConsolidate.h b/packages/livestatus/include/livestatus/RRDConsolidate.h new file mode 100644 index 00000000000..855a57a711f --- /dev/null +++ b/packages/livestatus/include/livestatus/RRDConsolidate.h @@ -0,0 +1,66 @@ +// Copyright (C) 2019 Checkmk GmbH - License: GNU General Public License v2 +// This file is part of Checkmk (https://checkmk.com). It is subject to the +// terms and conditions defined in the file COPYING, which is part of this +// source code package. + +#include +#include +#include +#include + +/// The four consolidation functions from +/// https://oss.oetiker.ch/rrdtool/doc/rrdfetch.en.html +struct CF { + virtual ~CF() = default; + [[nodiscard]] virtual double init() = 0; + virtual void handle(double value) = 0; +}; + +class MaxCF : public CF { +public: + MaxCF() = default; + ~MaxCF() override = default; + [[nodiscard]] double init() override; + void handle(double value) override; + +private: + double counter_ = std::numeric_limits::quiet_NaN(); +}; + +class MinCF : public CF { +public: + MinCF() = default; + ~MinCF() override = default; + [[nodiscard]] double init() override; + void handle(double value) override; + +private: + double counter_ = std::numeric_limits::quiet_NaN(); +}; + +class AvgCF : public CF { +public: + AvgCF() = default; + ~AvgCF() override = default; + [[nodiscard]] double init() override; + void handle(double value) override; + +private: + std::size_t nelem = 0; + double counter_ = std::numeric_limits::quiet_NaN(); +}; + +class LastCF : public CF { +public: + LastCF() = default; + ~LastCF() override = default; + [[nodiscard]] double init() override; + void handle(double value) override; + +private: + double counter_ = std::numeric_limits::quiet_NaN(); +}; + +std::vector rrd_consolidate(const std::unique_ptr &f, + const std::vector &input, + std::size_t act_step, std::size_t target); diff --git a/packages/livestatus/src/RRDConsolidate.cc b/packages/livestatus/src/RRDConsolidate.cc new file mode 100644 index 00000000000..63b3a04e09e --- /dev/null +++ b/packages/livestatus/src/RRDConsolidate.cc @@ -0,0 +1,82 @@ +// Copyright (C) 2019 Checkmk GmbH - License: GNU General Public License v2 +// This file is part of Checkmk (https://checkmk.com). It is subject to the +// terms and conditions defined in the file COPYING, which is part of this +// source code package. + +#include "livestatus/RRDConsolidate.h" + +#include +#include +#include +#include + +namespace { +constexpr double NaN = std::numeric_limits::quiet_NaN(); +} + +double MaxCF::init() { + const double out = counter_; + counter_ = NaN; + return out; +} + +void MaxCF::handle(double value) { + if (std::isnan(value)) { + return; + } + counter_ = std::isnan(counter_) ? value : std::max(counter_, value); +} + +double MinCF::init() { + const double out = counter_; + counter_ = NaN; + return out; +} + +void MinCF::handle(double value) { + if (std::isnan(value)) { + return; + } + counter_ = std::isnan(counter_) ? value : std::min(counter_, value); +} + +double AvgCF::init() { + const double out = + nelem == 0 ? counter_ : counter_ / static_cast(nelem); + counter_ = NaN; + nelem = 0; + return out; +} + +void AvgCF::handle(double value) { + if (std::isnan(value)) { + return; + } + counter_ = std::isnan(counter_) ? value : counter_ + value; + nelem += 1; +} + +double LastCF::init() { + const double out = counter_; + counter_ = NaN; + return out; +} + +void LastCF::handle(double value) { counter_ = value; } + +std::vector rrd_consolidate(const std::unique_ptr &cf, + const std::vector &input, + std::size_t act_step, std::size_t target) { + if (act_step >= target) { + return input; + } + const std::size_t factor = target / act_step; + auto out = std::vector{}; + for (auto iter = input.begin(); iter != input.end(); iter++) { + cf->handle(*iter); + if (std::distance(input.begin(), iter) % factor == factor - 1) { + out.emplace_back(cf->init()); + } + } + return out; +} diff --git a/packages/livestatus/test/test_RRDConsolidate.cc b/packages/livestatus/test/test_RRDConsolidate.cc new file mode 100644 index 00000000000..5edd06ba715 --- /dev/null +++ b/packages/livestatus/test/test_RRDConsolidate.cc @@ -0,0 +1,129 @@ +// Copyright (C) 2019 Checkmk GmbH - License: GNU General Public License v2 +// This file is part of Checkmk (https://checkmk.com). It is subject to the +// terms and conditions defined in the file COPYING, which is part of this +// source code package. + +#include +#include +#include +#include +#include +#include + +#include "gtest/gtest.h" +#include "livestatus/RRDConsolidate.h" + +namespace { +constexpr auto NaN = std::numeric_limits::quiet_NaN(); +} // namespace + +TEST(TestRRDConsolidate, Constant) { + const auto value = 2.0; + const auto input = std::vector(200, value); + EXPECT_EQ(input, rrd_consolidate(std::make_unique(), input, 10, 10)); + EXPECT_EQ(input, rrd_consolidate(std::make_unique(), input, 10, 10)); + EXPECT_EQ(input, rrd_consolidate(std::make_unique(), input, 10, 10)); + EXPECT_EQ(input, + rrd_consolidate(std::make_unique(), input, 10, 10)); + + { + const auto min_ = + rrd_consolidate(std::make_unique(), input, 10, 20); + ASSERT_EQ(input.size() / 2, min_.size()); + EXPECT_EQ(std::vector(min_.size(), value), min_); + } + { + const auto max_ = + rrd_consolidate(std::make_unique(), input, 10, 20); + ASSERT_EQ(input.size() / 2, max_.size()); + EXPECT_EQ(std::vector(max_.size(), value), max_); + } + { + const auto avg_ = + rrd_consolidate(std::make_unique(), input, 10, 20); + ASSERT_EQ(input.size() / 2, avg_.size()); + EXPECT_EQ(std::vector(avg_.size(), value), avg_); + } + { + const auto last_ = + rrd_consolidate(std::make_unique(), input, 10, 20); + ASSERT_EQ(input.size() / 2, last_.size()); + EXPECT_EQ(std::vector(last_.size(), value), last_); + } +} + +TEST(TestRRDConsolidate, NaN) { + const auto value = NaN; + const auto input = std::vector(20, value); + + { + const auto min_ = + rrd_consolidate(std::make_unique(), input, 10, 20); + ASSERT_EQ(input.size() / 2, min_.size()); + EXPECT_TRUE(std::accumulate( + input.begin(), input.end(), true, + [](auto &&lhs, auto &&rhs) { return lhs && std::isnan(rhs); })); + } + { + const auto max_ = + rrd_consolidate(std::make_unique(), input, 10, 20); + ASSERT_EQ(input.size() / 2, max_.size()); + EXPECT_TRUE(std::accumulate( + input.begin(), input.end(), true, + [](auto &&lhs, auto &&rhs) { return lhs && std::isnan(rhs); })); + } + { + const auto avg_ = + rrd_consolidate(std::make_unique(), input, 10, 20); + ASSERT_EQ(input.size() / 2, avg_.size()); + EXPECT_TRUE(std::accumulate( + input.begin(), input.end(), true, + [](auto &&lhs, auto &&rhs) { return lhs && std::isnan(rhs); })); + } + { + const auto last_ = + rrd_consolidate(std::make_unique(), input, 10, 20); + ASSERT_EQ(input.size() / 2, last_.size()); + EXPECT_TRUE(std::accumulate( + input.begin(), input.end(), true, + [](auto &&lhs, auto &&rhs) { return lhs && std::isnan(rhs); })); + } +} + +TEST(TestRRDConsolidate, SimpleCases) { + const auto input = std::vector{1, 2, 1, 2, 1, 2, 1, 2}; + EXPECT_EQ(std::vector(4, 1.0), + rrd_consolidate(std::make_unique(), input, 10, 20)); + EXPECT_EQ(std::vector(4, 2.0), + rrd_consolidate(std::make_unique(), input, 10, 20)); + EXPECT_EQ(std::vector(4, 1.5), + rrd_consolidate(std::make_unique(), input, 10, 20)); + EXPECT_EQ(std::vector(4, 2.0), + rrd_consolidate(std::make_unique(), input, 10, 20)); +} + +TEST(TestRRDConsolidate, ComplexCases) { + const auto input = std::vector{1, NaN, 1, 2}; + { + const auto min_ = std::vector{1, 1}; + EXPECT_EQ(min_, + rrd_consolidate(std::make_unique(), input, 10, 20)); + } + { + const auto max_ = std::vector{1, 2}; + EXPECT_EQ(max_, + rrd_consolidate(std::make_unique(), input, 10, 20)); + } + { + const auto avg_ = std::vector{1, 1.5}; + EXPECT_EQ(avg_, + rrd_consolidate(std::make_unique(), input, 10, 20)); + } + { + const auto last_ = + rrd_consolidate(std::make_unique(), input, 10, 20); + EXPECT_EQ(2, last_.size()); + EXPECT_TRUE(std::isnan(last_[0])); + EXPECT_DOUBLE_EQ(2.0, last_[1]); + } +}