diff --git a/dynolog/src/metric_frame/MetricFrameBase.h b/dynolog/src/metric_frame/MetricFrameBase.h index 14dd457..01a2b74 100644 --- a/dynolog/src/metric_frame/MetricFrameBase.h +++ b/dynolog/src/metric_frame/MetricFrameBase.h @@ -11,7 +11,6 @@ #include #include -#include #include #include diff --git a/dynolog/src/metric_frame/MetricFrameTsUnit.cpp b/dynolog/src/metric_frame/MetricFrameTsUnit.cpp index 081564c..332aea4 100644 --- a/dynolog/src/metric_frame/MetricFrameTsUnit.cpp +++ b/dynolog/src/metric_frame/MetricFrameTsUnit.cpp @@ -10,6 +10,118 @@ namespace facebook::dynolog { +MetricFrameTsUnit::MetricFrameTsUnit(size_t frameLength) + : timeSeries_{ + frameLength, + "MetricFrameTsUnit", + "Ring buffer for recording timepoint of samples"} {} + +void MetricFrameTsUnit::addSample(TimePoint time) { + if (timeSeries_.size() > 0 && + time <= timeSeries_.at(timeSeries_.size() - 1)) { + throw std::invalid_argument( + "new timepoint value " + + std::to_string(time.time_since_epoch().count()) + + "is not larger than the previous value " + + std::to_string( + timeSeries_.at(timeSeries_.size() - 1).time_since_epoch().count())); + } + timeSeries_.addSample(time); +} + +std::optional MetricFrameTsUnit::firstSampleTime() const { + if (timeSeries_.size() == 0) { + return std::nullopt; + } + return *timeSeries_.begin(); +} + +std::optional MetricFrameTsUnit::lastSampleTime() const { + if (timeSeries_.size() == 0) { + return std::nullopt; + } + return *(timeSeries_.end() - 1); +} + +std::vector MetricFrameTsUnit::getTimeVector() const { + return std::vector(timeSeries_.begin(), timeSeries_.end()); +} + +size_t MetricFrameTsUnit::length() const { + return timeSeries_.size(); +} + +size_t MetricFrameTsUnit::maxLength() const { + return timeSeries_.maxLength(); +} + +std::optional MetricFrameTsUnit::getRange( + TimePoint startTime, + TimePoint endTime, + MATCH_POLICY startTimePolicy, + MATCH_POLICY endTimePolicy) const { + auto startOffset = findMatchingOffset(startTime, startTimePolicy); + auto endOffset = findMatchingOffset(endTime, endTimePolicy); + if (!startOffset.has_value() || !endOffset.has_value()) { + return std::nullopt; + } + return MetricFrameRange{ + .start = startOffset.value(), .end = endOffset.value()}; +} + +std::optional MetricFrameTsUnit::findMatchingOffset( + TimePoint time, + MATCH_POLICY policy) const { + switch (policy) { + case MATCH_POLICY::CLOSEST: + return closestPolicy(time); + case MATCH_POLICY::PREV_CLOSEST: + return prevClosestPolicy(time); + case MATCH_POLICY::NEXT_CLOSEST: + return nextClosestPolicy(time); + } +} + +std::optional MetricFrameTsUnit::closestPolicy( + TimePoint time) const { + auto prevClosest = prevClosestPolicy(time); + auto nextClosest = nextClosestPolicy(time); + if (!prevClosest.has_value() && !nextClosest.has_value()) { + return std::nullopt; + } + if (!prevClosest.has_value()) { + return nextClosest.value(); + } + if (!nextClosest.has_value()) { + return prevClosest.value(); + } + if (time - prevClosest.value().time < nextClosest.value().time - time) { + return prevClosest.value(); + } + return nextClosest.value(); +} + +std::optional MetricFrameTsUnit::prevClosestPolicy( + TimePoint time) const { + auto it = std::upper_bound(timeSeries_.begin(), timeSeries_.end(), time); + if (timeSeries_.size() == 0 || it == timeSeries_.begin()) { + return std::nullopt; + } + return MetricFrameOffset{ + .offset = static_cast(it - 1 - timeSeries_.begin()), + .time = *(it - 1)}; +} + +std::optional MetricFrameTsUnit::nextClosestPolicy( + TimePoint time) const { + auto it = std::lower_bound(timeSeries_.begin(), timeSeries_.end(), time); + if (it == timeSeries_.end()) { + return std::nullopt; + } + return MetricFrameOffset{ + .offset = static_cast(it - timeSeries_.begin()), .time = *it}; +} + MetricFrameTsUnitFixInterval::MetricFrameTsUnitFixInterval( std::chrono::microseconds interval, size_t frameLength) diff --git a/dynolog/src/metric_frame/MetricFrameTsUnit.h b/dynolog/src/metric_frame/MetricFrameTsUnit.h index ca3fbc0..8b8512e 100644 --- a/dynolog/src/metric_frame/MetricFrameTsUnit.h +++ b/dynolog/src/metric_frame/MetricFrameTsUnit.h @@ -6,11 +6,38 @@ #pragma once #include "dynolog/src/metric_frame/MetricFrameTsUnitInterface.h" +#include "dynolog/src/metric_frame/MetricSeries.h" #include namespace facebook::dynolog { +class MetricFrameTsUnit : public MetricFrameTsUnitInterface { + public: + explicit MetricFrameTsUnit(size_t frameLength); + virtual void addSample(TimePoint time) override; + virtual std::optional firstSampleTime() const override; + virtual std::optional lastSampleTime() const override; + virtual std::vector getTimeVector() const override; + virtual size_t length() const override; + virtual size_t maxLength() const override; + virtual std::optional getRange( + TimePoint startTime, + TimePoint endTime, + MATCH_POLICY startTimePolicy, + MATCH_POLICY endTimePolicy) const override; + + protected: + MetricSeries timeSeries_; + + std::optional findMatchingOffset( + TimePoint time, + MATCH_POLICY policy) const; + std::optional closestPolicy(TimePoint time) const; + std::optional prevClosestPolicy(TimePoint time) const; + std::optional nextClosestPolicy(TimePoint time) const; +}; + class MetricFrameTsUnitFixInterval : public MetricFrameTsUnitInterface { public: MetricFrameTsUnitFixInterval( diff --git a/dynolog/src/metric_frame/MetricSeries.h b/dynolog/src/metric_frame/MetricSeries.h index ab0eb96..c48f98d 100644 --- a/dynolog/src/metric_frame/MetricSeries.h +++ b/dynolog/src/metric_frame/MetricSeries.h @@ -184,7 +184,7 @@ class MetricSeries final { return size_; } - size_t maxLength() { + size_t maxLength() const { return data_.size(); } diff --git a/dynolog/tests/metric_frame/MetricFrameTsUnitTest.cpp b/dynolog/tests/metric_frame/MetricFrameTsUnitTest.cpp index c98aa3b..d6ef559 100644 --- a/dynolog/tests/metric_frame/MetricFrameTsUnitTest.cpp +++ b/dynolog/tests/metric_frame/MetricFrameTsUnitTest.cpp @@ -18,13 +18,17 @@ class MetricFrameTsUnitFixIntervalTest : public MetricFrameTsUnitFixInterval { MetricFrameTsUnitFixIntervalTest() : MetricFrameTsUnitFixInterval{60s, 10} {} }; +class MetricFrameTsUnitTest : public MetricFrameTsUnit { + public: + MetricFrameTsUnitTest() : MetricFrameTsUnit{10} {} +}; + TEST(MetricFrameTsUnitTest, constructor) { - MetricFrameTsUnitFixIntervalTest t; + MetricFrameTsUnitFixIntervalTest fix; + MetricFrameTsUnitTest t; } -TEST(MetricFrameTsUnitTest, smokeTest) { - MetricFrameTsUnitFixIntervalTest t; - MetricFrameTsUnitInterface& i = t; +void smokeTest(MetricFrameTsUnitInterface& i) { auto now = std::chrono::steady_clock::now(); i.addSample(now - 120s); i.addSample(now - 60s); @@ -50,9 +54,14 @@ TEST(MetricFrameTsUnitTest, smokeTest) { EXPECT_EQ(timeVector[2], now); } -TEST(MetricFrameTsUnitTest, emptyFrame) { +TEST(MetricFrameTsUnitTest, smokeTest) { MetricFrameTsUnitFixIntervalTest t; - MetricFrameTsUnitInterface& i = t; + smokeTest(t); + MetricFrameTsUnitTest t2; + smokeTest(t2); +} + +void emptyFrame(MetricFrameTsUnitInterface& i) { auto now = std::chrono::steady_clock::now(); auto resMaybe = i.getRange(now - 120s, now); EXPECT_FALSE(resMaybe.has_value()); @@ -62,9 +71,14 @@ TEST(MetricFrameTsUnitTest, emptyFrame) { EXPECT_EQ(i.maxLength(), 10); } -TEST(MetricFrameTsUnitTest, interpolationPolicies) { +TEST(MetricFrameTsUnitTest, emptyFrame) { MetricFrameTsUnitFixIntervalTest t; - MetricFrameTsUnitInterface& i = t; + emptyFrame(t); + MetricFrameTsUnitTest t2; + emptyFrame(t2); +} + +void interpolationPolicies(MetricFrameTsUnitInterface& i) { auto now = std::chrono::steady_clock::now(); i.addSample(now - 120s); i.addSample(now - 60s); @@ -133,3 +147,10 @@ TEST(MetricFrameTsUnitTest, interpolationPolicies) { EXPECT_EQ(res.start.offset, 1); EXPECT_EQ(res.end.offset, 2); } + +TEST(MetricFrameTsUnitTest, interpolationPolicies) { + MetricFrameTsUnitFixIntervalTest t; + interpolationPolicies(t); + MetricFrameTsUnitTest t2; + interpolationPolicies(t2); +}