Skip to content

Commit

Permalink
support non fixed interval timepoint (#334)
Browse files Browse the repository at this point in the history
Summary:

MetricFrameTsUnit is another implementation of MetricFrameTsUnitInterface. comparing to MetricFrameTsUnitFixInterval, MetricFrameTsUnit doesn't assume that interval between two adjacent sample is fixed. this comes with the cost of extra memory usage and slower getRange() query.

Reviewed By: bigzachattack

Differential Revision: D66910877
  • Loading branch information
Alston Tang authored and facebook-github-bot committed Jan 10, 2025
1 parent 3deba4b commit 0a5f95d
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 10 deletions.
1 change: 0 additions & 1 deletion dynolog/src/metric_frame/MetricFrameBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

#include <chrono>
#include <memory>
#include <stdexcept>
#include <string>
#include <variant>

Expand Down
112 changes: 112 additions & 0 deletions dynolog/src/metric_frame/MetricFrameTsUnit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<TimePoint> MetricFrameTsUnit::firstSampleTime() const {
if (timeSeries_.size() == 0) {
return std::nullopt;
}
return *timeSeries_.begin();
}

std::optional<TimePoint> MetricFrameTsUnit::lastSampleTime() const {
if (timeSeries_.size() == 0) {
return std::nullopt;
}
return *(timeSeries_.end() - 1);
}

std::vector<TimePoint> MetricFrameTsUnit::getTimeVector() const {
return std::vector<TimePoint>(timeSeries_.begin(), timeSeries_.end());
}

size_t MetricFrameTsUnit::length() const {
return timeSeries_.size();
}

size_t MetricFrameTsUnit::maxLength() const {
return timeSeries_.maxLength();
}

std::optional<MetricFrameRange> 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<MetricFrameOffset> 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<MetricFrameOffset> 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<MetricFrameOffset> 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<size_t>(it - 1 - timeSeries_.begin()),
.time = *(it - 1)};
}

std::optional<MetricFrameOffset> 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<size_t>(it - timeSeries_.begin()), .time = *it};
}

MetricFrameTsUnitFixInterval::MetricFrameTsUnitFixInterval(
std::chrono::microseconds interval,
size_t frameLength)
Expand Down
27 changes: 27 additions & 0 deletions dynolog/src/metric_frame/MetricFrameTsUnit.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,38 @@
#pragma once

#include "dynolog/src/metric_frame/MetricFrameTsUnitInterface.h"
#include "dynolog/src/metric_frame/MetricSeries.h"

#include <optional>

namespace facebook::dynolog {

class MetricFrameTsUnit : public MetricFrameTsUnitInterface {
public:
explicit MetricFrameTsUnit(size_t frameLength);
virtual void addSample(TimePoint time) override;
virtual std::optional<TimePoint> firstSampleTime() const override;
virtual std::optional<TimePoint> lastSampleTime() const override;
virtual std::vector<TimePoint> getTimeVector() const override;
virtual size_t length() const override;
virtual size_t maxLength() const override;
virtual std::optional<MetricFrameRange> getRange(
TimePoint startTime,
TimePoint endTime,
MATCH_POLICY startTimePolicy,
MATCH_POLICY endTimePolicy) const override;

protected:
MetricSeries<TimePoint> timeSeries_;

std::optional<MetricFrameOffset> findMatchingOffset(
TimePoint time,
MATCH_POLICY policy) const;
std::optional<MetricFrameOffset> closestPolicy(TimePoint time) const;
std::optional<MetricFrameOffset> prevClosestPolicy(TimePoint time) const;
std::optional<MetricFrameOffset> nextClosestPolicy(TimePoint time) const;
};

class MetricFrameTsUnitFixInterval : public MetricFrameTsUnitInterface {
public:
MetricFrameTsUnitFixInterval(
Expand Down
2 changes: 1 addition & 1 deletion dynolog/src/metric_frame/MetricSeries.h
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ class MetricSeries final {
return size_;
}

size_t maxLength() {
size_t maxLength() const {
return data_.size();
}

Expand Down
37 changes: 29 additions & 8 deletions dynolog/tests/metric_frame/MetricFrameTsUnitTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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());
Expand All @@ -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);
Expand Down Expand Up @@ -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);
}

0 comments on commit 0a5f95d

Please sign in to comment.