From c10a6640488dc4805bc383b98734e9c6bf616be1 Mon Sep 17 00:00:00 2001 From: Xuli Liu Date: Fri, 3 Feb 2017 14:53:09 -0800 Subject: [PATCH] Expose more functions/constructors on BucketedTimeSeries Summary: Currently BucketedTimeSeries does not expose enough functions to allow assessing its data, therefore there is no way to do serialization/deserialization. Adding functions/constructors to support this. Reviewed By: simpkins Differential Revision: D4500075 fbshipit-source-id: 656ac8a208547d8d3fadf9ea150606b6e74775c9 --- folly/stats/BucketedTimeSeries-defs.h | 40 ++++++++++- folly/stats/BucketedTimeSeries.h | 35 ++++++++++ folly/test/TimeseriesTest.cpp | 98 ++++++++++++++++++++++++++- 3 files changed, 169 insertions(+), 4 deletions(-) diff --git a/folly/stats/BucketedTimeSeries-defs.h b/folly/stats/BucketedTimeSeries-defs.h index a24aff86..5b3b6391 100644 --- a/folly/stats/BucketedTimeSeries-defs.h +++ b/folly/stats/BucketedTimeSeries-defs.h @@ -16,10 +16,11 @@ #pragma once -#include -#include #include #include +#include +#include +#include namespace folly { @@ -43,6 +44,41 @@ BucketedTimeSeries::BucketedTimeSeries( } } +template +BucketedTimeSeries::BucketedTimeSeries( + TimePoint theFirstTime, + TimePoint theLatestTime, + Duration maxDuration, + const std::vector& bucketsList) + : firstTime_(theFirstTime), + latestTime_(theLatestTime), + duration_(maxDuration), + buckets_(bucketsList) { + // Come up with the total_ from buckets_ being passed in + for (auto const& bucket : buckets_) { + total_.add(bucket.sum, bucket.count); + } + + // Verify the integrity of the data + + // If firstTime is greater than latestTime, the total count should be 0. + // (firstTime being greater than latestTime means that no data points have + // ever been added to the time series.) + if (firstTime_ > latestTime_ && (total_.sum != 0 || total_.count != 0)) { + throw std::invalid_argument( + "The total should have been 0 " + "if firstTime is greater than lastestTime"); + } + + // If firstTime is less than or equal to latestTime, + // latestTime - firstTime should be less than or equal to the duration. + if (firstTime_ <= latestTime_ && latestTime_ - firstTime_ > duration_) { + throw std::invalid_argument( + "The difference between firstTime and latestTime " + "should be less than or equal to the duration"); + } +} + template bool BucketedTimeSeries::addValue(TimePoint now, const ValueType& val) { return addValueAggregated(now, val, 1); diff --git a/folly/stats/BucketedTimeSeries.h b/folly/stats/BucketedTimeSeries.h index 14468d61..a31904da 100644 --- a/folly/stats/BucketedTimeSeries.h +++ b/folly/stats/BucketedTimeSeries.h @@ -81,6 +81,18 @@ class BucketedTimeSeries { */ BucketedTimeSeries(size_t numBuckets, Duration duration); + /* + * Create a new BucketedTimeSeries. + * + * This constructor is used to reconstruct a timeseries using + * previously saved data + */ + BucketedTimeSeries( + TimePoint theFirstTime, + TimePoint theLatestTime, + Duration maxDuration, + const std::vector& bucketsList); + /* * Adds the value 'val' at time 'now' * @@ -192,6 +204,29 @@ class BucketedTimeSeries { return firstTime_ > latestTime_; } + /* + * Returns time of first update() since clear()/constructor. + * Note that the returned value is only meaningful when empty() is false. + */ + TimePoint firstTime() const { + return firstTime_; + } + + /* + * Returns time of last update(). + * Note that the returned value is only meaningful when empty() is false. + */ + TimePoint latestTime() const { + return latestTime_; + } + + /* + * Returns actual buckets of values + */ + const std::vector& buckets() const { + return buckets_; + } + /* * Get the amount of time tracked by this timeseries. * diff --git a/folly/test/TimeseriesTest.cpp b/folly/test/TimeseriesTest.cpp index 66f4a1cf..27e73117 100644 --- a/folly/test/TimeseriesTest.cpp +++ b/folly/test/TimeseriesTest.cpp @@ -14,10 +14,11 @@ * limitations under the License. */ -#include +#include #include -#include +#include #include +#include #include @@ -31,8 +32,10 @@ using std::string; using std::vector; using folly::BucketedTimeSeries; +using Bucket = folly::detail::Bucket; using StatsClock = folly::LegacyStatsClock; using TimePoint = StatsClock::time_point; +using Duration = StatsClock::duration; /* * Helper functions to allow us to directly log time points and duration @@ -795,6 +798,97 @@ TEST(BucketedTimeSeries, addHistorical) { EXPECT_EQ(10, b.count()); } +TEST(BucketedTimeSeries, reConstructEmptyTimeSeries) { + auto verify = [](auto ts) { + EXPECT_TRUE(ts.empty()); + EXPECT_EQ(0, ts.sum()); + EXPECT_EQ(0, ts.count()); + }; + + // Create a 100 second timeseries with 10 buckets_ + BucketedTimeSeries ts(10, seconds(100)); + + verify(ts); + + auto firstTime = ts.firstTime(); + auto latestTime = ts.latestTime(); + auto duration = ts.duration(); + auto buckets = ts.buckets(); + + // Reconstruct the timeseries + BucketedTimeSeries newTs(firstTime, latestTime, duration, buckets); + + verify(newTs); +} + +TEST(BucketedTimeSeries, reConstructWithValidData) { + // Create a 100 second timeseries with 10 buckets_ + BucketedTimeSeries ts(10, seconds(100)); + + auto setup = [&] { + ts.clear(); + // Add 1 value to each bucket + for (int n = 5; n <= 95; n += 10) { + ts.addValue(seconds(n), 6); + } + + EXPECT_EQ(10, ts.count()); + EXPECT_EQ(60, ts.sum()); + EXPECT_EQ(6, ts.avg()); + }; + + setup(); + + auto firstTime = ts.firstTime(); + auto latestTime = ts.latestTime(); + auto duration = ts.duration(); + auto buckets = ts.buckets(); + + // Reconstruct the timeseries + BucketedTimeSeries newTs(firstTime, latestTime, duration, buckets); + + auto compare = [&] { + EXPECT_EQ(ts.firstTime(), newTs.firstTime()); + EXPECT_EQ(ts.latestTime(), newTs.latestTime()); + EXPECT_EQ(ts.duration(), newTs.duration()); + EXPECT_EQ(ts.buckets().size(), newTs.buckets().size()); + EXPECT_EQ(ts.sum(), newTs.sum()); + EXPECT_EQ(ts.count(), newTs.count()); + + for (auto it1 = ts.buckets().begin(), it2 = newTs.buckets().begin(); + it1 != ts.buckets().end(); + it1++, it2++) { + EXPECT_EQ(it1->sum, it2->sum); + EXPECT_EQ(it1->count, it2->count); + } + }; + + compare(); +} + +TEST(BucketedTimeSeries, reConstructWithCorruptedData) { + // The total should have been 0 as firstTime > latestTime + EXPECT_THROW( + { + std::vector buckets(10); + buckets[0].sum = 1; + buckets[0].count = 1; + + BucketedTimeSeries ts( + mkTimePoint(1), mkTimePoint(0), Duration(10), buckets); + }, + std::invalid_argument); + + // The duration should be no less than latestTime - firstTime + EXPECT_THROW( + BucketedTimeSeries( + mkTimePoint(1), + mkTimePoint(100), + Duration(10), + std::vector(10)), + std::invalid_argument); +} + namespace IntMHTS { enum Levels { MINUTE, -- 2.34.1