Commit 0600259d authored by Stephen Chen's avatar Stephen Chen Committed by Jordan DeLong

Add MultiLevelTimeSeries to folly.

Summary:
Add MultiLevelTimeSeries class which represents a timeseries which keeps several
levels of data granularity (similar in principle to the loads reported by the
UNIX 'uptime' command).  It uses several instances (one per level) of
BucketedTimeSeries as the underlying storage.

This can easily be used to track sums (and thus rates or averages) over several
predetermined time periods, as well as all-time sums.  For example, you would
use to it to track query rate or response speed over the last 5, 15, 30, and 60
minutes.

Test Plan: unittest included.

Reviewed By: simpkins@fb.com

FB internal diff: D851444
parent cdf9c435
...@@ -27,10 +27,14 @@ ...@@ -27,10 +27,14 @@
#include "folly/stats/Histogram.h" #include "folly/stats/Histogram.h"
#include "folly/stats/Histogram-defs.h" #include "folly/stats/Histogram-defs.h"
#include "folly/stats/MultiLevelTimeSeries.h"
#include "folly/stats/MultiLevelTimeSeries-defs.h"
namespace folly { namespace folly {
template class BucketedTimeSeries<int64_t>; template class BucketedTimeSeries<int64_t>;
template class Histogram<int64_t>; template class Histogram<int64_t>;
template class detail::HistogramBuckets<int64_t, Histogram<int64_t>::Bucket>; template class detail::HistogramBuckets<int64_t, Histogram<int64_t>::Bucket>;
template class MultiLevelTimeSeries<int64_t>;
} // folly } // folly
/*
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FOLLY_STATS_MULTILEVELTIMESERIES_DEFS_H_
#define FOLLY_STATS_MULTILEVELTIMESERIES_DEFS_H_
#include <glog/logging.h>
namespace folly {
template <typename VT, typename TT>
MultiLevelTimeSeries<VT, TT>::MultiLevelTimeSeries(
size_t numBuckets,
size_t numLevels,
const TimeType levelDurations[])
: numBuckets_(numBuckets),
cachedTime_(0),
cachedSum_(0),
cachedCount_(0) {
CHECK_GT(numLevels, 0);
CHECK(levelDurations);
levels_.reserve(numLevels);
for (int i = 0; i < numLevels; ++i) {
if (levelDurations[i] == TT(0)) {
CHECK_EQ(i, numLevels - 1);
} else if (i > 0) {
CHECK(levelDurations[i-1] < levelDurations[i]);
}
levels_.emplace_back(numBuckets, levelDurations[i]);
}
}
template <typename VT, typename TT>
void MultiLevelTimeSeries<VT, TT>::addValue(TimeType now,
const ValueType& val) {
addValueAggregated(now, val, 1);
}
template <typename VT, typename TT>
void MultiLevelTimeSeries<VT, TT>::addValue(TimeType now,
const ValueType& val,
int64_t times) {
addValueAggregated(now, val * times, times);
}
template <typename VT, typename TT>
void MultiLevelTimeSeries<VT, TT>::addValueAggregated(TimeType now,
const ValueType& sum,
int64_t nsamples) {
if (cachedTime_ != now) {
flush();
cachedTime_ = now;
}
cachedSum_ += sum;
cachedCount_ += nsamples;
}
template <typename VT, typename TT>
void MultiLevelTimeSeries<VT, TT>::update(TimeType now) {
flush();
for (int i = 0; i < levels_.size(); ++i) {
levels_[i].update(now);
}
}
template <typename VT, typename TT>
void MultiLevelTimeSeries<VT, TT>::flush() {
// update all the underlying levels
if (cachedCount_ > 0) {
for (int i = 0; i < levels_.size(); ++i) {
levels_[i].addValueAggregated(cachedTime_, cachedSum_, cachedCount_);
}
cachedCount_ = 0;
cachedSum_ = 0;
}
}
template <typename VT, typename TT>
void MultiLevelTimeSeries<VT, TT>::clear() {
for (auto & level : levels_) {
level.clear();
}
cachedTime_ = TimeType(0);
cachedSum_ = 0;
cachedCount_ = 0;
}
} // folly
#endif // FOLLY_STATS_MULTILEVELTIMESERIES_DEFS_H_
This diff is collapsed.
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
#include "folly/stats/BucketedTimeSeries.h" #include "folly/stats/BucketedTimeSeries.h"
#include "folly/stats/BucketedTimeSeries-defs.h" #include "folly/stats/BucketedTimeSeries-defs.h"
#include "folly/stats/MultiLevelTimeSeries.h"
#include "folly/stats/MultiLevelTimeSeries-defs.h"
#include <glog/logging.h> #include <glog/logging.h>
#include <gtest/gtest.h> #include <gtest/gtest.h>
...@@ -711,3 +713,199 @@ TEST(BucketedTimeSeries, rateByInterval) { ...@@ -711,3 +713,199 @@ TEST(BucketedTimeSeries, rateByInterval) {
EXPECT_EQ(1.0, b.countRate(seconds(0), kDuration * 2)); EXPECT_EQ(1.0, b.countRate(seconds(0), kDuration * 2));
EXPECT_EQ(1.0, b.countRate(seconds(0), kDuration * 10)); EXPECT_EQ(1.0, b.countRate(seconds(0), kDuration * 10));
} }
namespace IntMHTS {
enum Levels {
MINUTE,
HOUR,
ALLTIME,
NUM_LEVELS,
};
const seconds kMinuteHourDurations[] = {
seconds(60), seconds(3600), seconds(0)
};
};
TEST(MinuteHourTimeSeries, Basic) {
folly::MultiLevelTimeSeries<int> mhts(60, IntMHTS::NUM_LEVELS,
IntMHTS::kMinuteHourDurations);
EXPECT_EQ(mhts.numLevels(), IntMHTS::NUM_LEVELS);
EXPECT_EQ(mhts.numLevels(), 3);
EXPECT_EQ(mhts.sum(IntMHTS::MINUTE), 0);
EXPECT_EQ(mhts.sum(IntMHTS::HOUR), 0);
EXPECT_EQ(mhts.sum(IntMHTS::ALLTIME), 0);
EXPECT_EQ(mhts.avg(IntMHTS::MINUTE), 0);
EXPECT_EQ(mhts.avg(IntMHTS::HOUR), 0);
EXPECT_EQ(mhts.avg(IntMHTS::ALLTIME), 0);
EXPECT_EQ(mhts.rate(IntMHTS::MINUTE), 0);
EXPECT_EQ(mhts.rate(IntMHTS::HOUR), 0);
EXPECT_EQ(mhts.rate(IntMHTS::ALLTIME), 0);
EXPECT_EQ(mhts.getLevel(IntMHTS::MINUTE).elapsed().count(), 0);
EXPECT_EQ(mhts.getLevel(IntMHTS::HOUR).elapsed().count(), 0);
EXPECT_EQ(mhts.getLevel(IntMHTS::ALLTIME).elapsed().count(), 0);
seconds cur_time(0);
mhts.addValue(cur_time++, 10);
mhts.flush();
EXPECT_EQ(mhts.getLevel(IntMHTS::MINUTE).elapsed().count(), 1);
EXPECT_EQ(mhts.getLevel(IntMHTS::HOUR).elapsed().count(), 1);
EXPECT_EQ(mhts.getLevel(IntMHTS::ALLTIME).elapsed().count(), 1);
for (int i = 0; i < 299; ++i) {
mhts.addValue(cur_time++, 10);
}
mhts.flush();
EXPECT_EQ(mhts.getLevel(IntMHTS::MINUTE).elapsed().count(), 60);
EXPECT_EQ(mhts.getLevel(IntMHTS::HOUR).elapsed().count(), 300);
EXPECT_EQ(mhts.getLevel(IntMHTS::ALLTIME).elapsed().count(), 300);
EXPECT_EQ(mhts.sum(IntMHTS::MINUTE), 600);
EXPECT_EQ(mhts.sum(IntMHTS::HOUR), 300*10);
EXPECT_EQ(mhts.sum(IntMHTS::ALLTIME), 300*10);
EXPECT_EQ(mhts.avg(IntMHTS::MINUTE), 10);
EXPECT_EQ(mhts.avg(IntMHTS::HOUR), 10);
EXPECT_EQ(mhts.avg(IntMHTS::ALLTIME), 10);
EXPECT_EQ(mhts.rate(IntMHTS::MINUTE), 10);
EXPECT_EQ(mhts.rate(IntMHTS::HOUR), 10);
EXPECT_EQ(mhts.rate(IntMHTS::ALLTIME), 10);
for (int i = 0; i < 3600*3 - 300; ++i) {
mhts.addValue(cur_time++, 10);
}
mhts.flush();
EXPECT_EQ(mhts.getLevel(IntMHTS::MINUTE).elapsed().count(), 60);
EXPECT_EQ(mhts.getLevel(IntMHTS::HOUR).elapsed().count(), 3600);
EXPECT_EQ(mhts.getLevel(IntMHTS::ALLTIME).elapsed().count(), 3600*3);
EXPECT_EQ(mhts.sum(IntMHTS::MINUTE), 600);
EXPECT_EQ(mhts.sum(IntMHTS::HOUR), 3600*10);
EXPECT_EQ(mhts.sum(IntMHTS::ALLTIME), 3600*3*10);
EXPECT_EQ(mhts.avg(IntMHTS::MINUTE), 10);
EXPECT_EQ(mhts.avg(IntMHTS::HOUR), 10);
EXPECT_EQ(mhts.avg(IntMHTS::ALLTIME), 10);
EXPECT_EQ(mhts.rate(IntMHTS::MINUTE), 10);
EXPECT_EQ(mhts.rate(IntMHTS::HOUR), 10);
EXPECT_EQ(mhts.rate(IntMHTS::ALLTIME), 10);
for (int i = 0; i < 3600; ++i) {
mhts.addValue(cur_time++, 100);
}
mhts.flush();
EXPECT_EQ(mhts.sum(IntMHTS::MINUTE), 60*100);
EXPECT_EQ(mhts.sum(IntMHTS::HOUR), 3600*100);
EXPECT_EQ(mhts.sum(IntMHTS::ALLTIME),
3600*3*10 + 3600*100);
EXPECT_EQ(mhts.avg(IntMHTS::MINUTE), 100);
EXPECT_EQ(mhts.avg(IntMHTS::HOUR), 100);
EXPECT_EQ(mhts.avg(IntMHTS::ALLTIME), 32.5);
EXPECT_EQ(mhts.rate(IntMHTS::MINUTE), 100);
EXPECT_EQ(mhts.rate(IntMHTS::HOUR), 100);
EXPECT_EQ(mhts.rate(IntMHTS::ALLTIME), 32);
for (int i = 0; i < 1800; ++i) {
mhts.addValue(cur_time++, 120);
}
mhts.flush();
EXPECT_EQ(mhts.sum(IntMHTS::MINUTE), 60*120);
EXPECT_EQ(mhts.sum(IntMHTS::HOUR),
1800*100 + 1800*120);
EXPECT_EQ(mhts.sum(IntMHTS::ALLTIME),
3600*3*10 + 3600*100 + 1800*120);
for (int i = 0; i < 60; ++i) {
mhts.addValue(cur_time++, 1000);
}
mhts.flush();
EXPECT_EQ(mhts.sum(IntMHTS::MINUTE), 60*1000);
EXPECT_EQ(mhts.sum(IntMHTS::HOUR),
1740*100 + 1800*120 + 60*1000);
EXPECT_EQ(mhts.sum(IntMHTS::ALLTIME),
3600*3*10 + 3600*100 + 1800*120 + 60*1000);
mhts.clear();
EXPECT_EQ(mhts.sum(IntMHTS::ALLTIME), 0);
}
TEST(MinuteHourTimeSeries, QueryByInterval) {
folly::MultiLevelTimeSeries<int> mhts(60, IntMHTS::NUM_LEVELS,
IntMHTS::kMinuteHourDurations);
seconds curTime(0);
for (curTime = seconds(0); curTime < seconds(7200); curTime++) {
mhts.addValue(curTime, 1);
}
for (curTime = seconds(7200); curTime < seconds(7200 + 3540); curTime++) {
mhts.addValue(curTime, 10);
}
for (curTime = seconds(7200 + 3540); curTime < seconds(7200 + 3600);
curTime++) {
mhts.addValue(curTime, 100);
}
mhts.flush();
struct TimeInterval {
seconds start;
seconds end;
};
TimeInterval intervals[12] = {
{ curTime - seconds(60), curTime },
{ curTime - seconds(3600), curTime },
{ curTime - seconds(7200), curTime },
{ curTime - seconds(3600), curTime - seconds(60) },
{ curTime - seconds(7200), curTime - seconds(60) },
{ curTime - seconds(7200), curTime - seconds(3600) },
{ curTime - seconds(50), curTime - seconds(20) },
{ curTime - seconds(3020), curTime - seconds(20) },
{ curTime - seconds(7200), curTime - seconds(20) },
{ curTime - seconds(3000), curTime - seconds(1000) },
{ curTime - seconds(7200), curTime - seconds(1000) },
{ curTime - seconds(7200), curTime - seconds(3600) },
};
int expectedSums[12] = {
6000, 41400, 32400, 35400, 32130, 16200, 3000, 33600, 32310, 20000, 27900,
16200
};
int expectedCounts[12] = {
60, 3600, 7200, 3540, 7140, 3600, 30, 3000, 7180, 2000, 6200, 3600
};
for (int i = 0; i < 12; ++i) {
TimeInterval interval = intervals[i];
int s = mhts.sum(interval.start, interval.end);
EXPECT_EQ(expectedSums[i], s);
int c = mhts.count(interval.start, interval.end);
EXPECT_EQ(expectedCounts[i], c);
int a = mhts.avg<int>(interval.start, interval.end);
EXPECT_EQ(expectedCounts[i] ?
(expectedSums[i] / expectedCounts[i]) : 0,
a);
int r = mhts.rate<int>(interval.start, interval.end);
int expectedRate =
expectedSums[i] / (interval.end - interval.start).count();
EXPECT_EQ(expectedRate, r);
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment