/*
- * Copyright 2014 Facebook, Inc.
+ * Copyright 2017 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* limitations under the License.
*/
-#include "folly/stats/TimeseriesHistogram.h"
-#include "folly/stats/TimeseriesHistogram-defs.h"
+#include <folly/stats/TimeseriesHistogram.h>
+#include <folly/stats/TimeseriesHistogram-defs.h>
-#include <gtest/gtest.h>
+#include <random>
+
+#include <folly/portability/GTest.h>
using namespace std;
using namespace folly;
using std::chrono::seconds;
+namespace {
namespace IntMTMHTS {
enum Levels {
MINUTE,
typedef std::mt19937 RandomInt32;
+using StatsClock = folly::LegacyStatsClock<std::chrono::seconds>;
+StatsClock::time_point mkTimePoint(int value) {
+ return StatsClock::time_point(StatsClock::duration(value));
+}
+}
+
TEST(TimeseriesHistogram, Percentile) {
RandomInt32 random(5);
// [10, 109], 12 buckets including above and below
EXPECT_EQ(10, h.getMin());
EXPECT_EQ(110, h.getMax());
- for (int i = 0; i < h.getNumBuckets(); ++i) {
+ for (size_t i = 0; i < h.getNumBuckets(); ++i) {
EXPECT_EQ(4, h.getBucket(i).numLevels());
}
int maxVal = 120;
- h.addValue(seconds(0), 0);
- h.addValue(seconds(0), maxVal);
+ h.addValue(mkTimePoint(0), 0);
+ h.addValue(mkTimePoint(0), maxVal);
for (int i = 0; i < 98; i++) {
- h.addValue(seconds(0), random() % maxVal);
+ h.addValue(mkTimePoint(0), random() % maxVal);
}
- h.update(std::chrono::duration_cast<std::chrono::seconds>(
- std::chrono::system_clock::now().time_since_epoch()));
+ h.update(mkTimePoint(1500000000));
// bucket 0 stores everything below min, so its minimum
// is the lowest possible number
EXPECT_EQ(std::numeric_limits<int>::min(),
IntMTMHTS::kDurations));
int maxVal = 120;
- hist.addValue(seconds(0), 0);
- hist.addValue(seconds(0), maxVal);
+ hist.addValue(mkTimePoint(0), 0);
+ hist.addValue(mkTimePoint(0), maxVal);
for (int i = 0; i < 98; i++) {
- hist.addValue(seconds(0), random() % maxVal);
+ hist.addValue(mkTimePoint(0), random() % maxVal);
}
- hist.update(seconds(0));
+ hist.update(mkTimePoint(0));
const char* const kStringValues1[IntMTMHTS::NUM_LEVELS] = {
"-2147483648:12:4,10:8:13,20:8:24,30:6:34,40:13:46,50:8:54,60:7:64,"
CHECK_EQ(IntMTMHTS::NUM_LEVELS, hist.getNumLevels());
- for (int level = 0; level < hist.getNumLevels(); ++level) {
+ for (size_t level = 0; level < hist.getNumLevels(); ++level) {
EXPECT_EQ(kStringValues1[level], hist.getString(level));
}
CHECK_EQ(IntMTMHTS::NUM_LEVELS, hist.getNumLevels());
- for (int level = 0; level < hist.getNumLevels(); ++level) {
+ for (size_t level = 0; level < hist.getNumLevels(); ++level) {
EXPECT_EQ(kStringValues2[level], hist.getString(level));
}
}
for (int now = 0; now < 3600; now++) {
for (int i = 0; i < 100; i++) {
- hist.addValue(seconds(now), i, 2); // adds each item 2 times
+ hist.addValue(mkTimePoint(now), i, 2); // adds each item 2 times
}
}
// check clearing
hist.clear();
- for (int b = 0; b < hist.getNumBuckets(); ++b) {
+ for (size_t b = 0; b < hist.getNumBuckets(); ++b) {
EXPECT_EQ(0, hist.getBucket(b).count(IntMTMHTS::MINUTE));
EXPECT_EQ(0, hist.getBucket(b).count(IntMTMHTS::TEN_MINUTE));
EXPECT_EQ(0, hist.getBucket(b).count(IntMTMHTS::HOUR));
for (int now = 0; now < 3600; now++) {
for (int i = 0; i < 100; i++) {
- hist.addValue(seconds(now), i);
+ hist.addValue(mkTimePoint(now), i);
}
}
- hist.update(seconds(3599));
+ hist.update(mkTimePoint(3599));
for (int pct = 1; pct <= 100; pct++) {
int expected = (pct - 1) / 10 * 10;
EXPECT_EQ(expected, hist.getPercentileBucketMin(pct, IntMTMHTS::MINUTE));
EXPECT_EQ(expected, hist.getPercentileBucketMin(pct, IntMTMHTS::ALLTIME));
}
- for (int b = 1; (b + 1) < hist.getNumBuckets(); ++b) {
+ for (size_t b = 1; (b + 1) < hist.getNumBuckets(); ++b) {
EXPECT_EQ(600, hist.getBucket(b).count(IntMTMHTS::MINUTE));
EXPECT_EQ(6000, hist.getBucket(b).count(IntMTMHTS::TEN_MINUTE));
EXPECT_EQ(36000, hist.getBucket(b).count(IntMTMHTS::HOUR));
EXPECT_EQ(0, hist.getBucket(0).count(IntMTMHTS::MINUTE));
EXPECT_EQ(0, hist.getBucket(hist.getNumBuckets() - 1).count(
IntMTMHTS::MINUTE));
+
+ EXPECT_EQ(6000, hist.count(IntMTMHTS::MINUTE));
+ EXPECT_EQ(60000, hist.count(IntMTMHTS::TEN_MINUTE));
+ EXPECT_EQ(360000, hist.count(IntMTMHTS::HOUR));
+ EXPECT_EQ(360000, hist.count(IntMTMHTS::ALLTIME));
+
+ // Each second we added 4950 total over 100 data points
+ EXPECT_EQ(297000, hist.sum(IntMTMHTS::MINUTE));
+ EXPECT_EQ(2970000, hist.sum(IntMTMHTS::TEN_MINUTE));
+ EXPECT_EQ(17820000, hist.sum(IntMTMHTS::HOUR));
+ EXPECT_EQ(17820000, hist.sum(IntMTMHTS::ALLTIME));
+
+ EXPECT_EQ(49, hist.avg<int>(IntMTMHTS::MINUTE));
+ EXPECT_EQ(49, hist.avg<int>(IntMTMHTS::TEN_MINUTE));
+ EXPECT_EQ(49, hist.avg<int>(IntMTMHTS::HOUR));
+ EXPECT_EQ(49, hist.avg<int>(IntMTMHTS::ALLTIME));
+ EXPECT_EQ(49.5, hist.avg<double>(IntMTMHTS::MINUTE));
+ EXPECT_EQ(49.5, hist.avg<double>(IntMTMHTS::TEN_MINUTE));
+ EXPECT_EQ(49.5, hist.avg<double>(IntMTMHTS::HOUR));
+ EXPECT_EQ(49.5, hist.avg<double>(IntMTMHTS::ALLTIME));
+
+ EXPECT_EQ(4950, hist.rate<int>(IntMTMHTS::MINUTE));
+ EXPECT_EQ(4950, hist.rate<int>(IntMTMHTS::TEN_MINUTE));
+ EXPECT_EQ(4950, hist.rate<int>(IntMTMHTS::HOUR));
+ EXPECT_EQ(4950, hist.rate<int>(IntMTMHTS::ALLTIME));
+ EXPECT_EQ(4950, hist.rate<double>(IntMTMHTS::MINUTE));
+ EXPECT_EQ(4950, hist.rate<double>(IntMTMHTS::TEN_MINUTE));
+ EXPECT_EQ(4950, hist.rate<double>(IntMTMHTS::HOUR));
+ EXPECT_EQ(4950, hist.rate<double>(IntMTMHTS::ALLTIME));
+
+ EXPECT_EQ(1000, hist.count(mkTimePoint(10), mkTimePoint(20)));
+ EXPECT_EQ(49500, hist.sum(mkTimePoint(10), mkTimePoint(20)));
+ EXPECT_EQ(4950, hist.rate(mkTimePoint(10), mkTimePoint(20)));
+ EXPECT_EQ(49.5, hist.avg<double>(mkTimePoint(10), mkTimePoint(20)));
+
+ EXPECT_EQ(200, hist.count(mkTimePoint(3550), mkTimePoint(3552)));
+ EXPECT_EQ(9900, hist.sum(mkTimePoint(3550), mkTimePoint(3552)));
+ EXPECT_EQ(4950, hist.rate(mkTimePoint(3550), mkTimePoint(3552)));
+ EXPECT_EQ(49.5, hist.avg<double>(mkTimePoint(3550), mkTimePoint(3552)));
+
+ EXPECT_EQ(0, hist.count(mkTimePoint(4550), mkTimePoint(4552)));
+ EXPECT_EQ(0, hist.sum(mkTimePoint(4550), mkTimePoint(4552)));
+ EXPECT_EQ(0, hist.rate(mkTimePoint(4550), mkTimePoint(4552)));
+ EXPECT_EQ(0, hist.avg<double>(mkTimePoint(4550), mkTimePoint(4552)));
}
// -----------------
for (int now = 0; now < 3600; now++) {
for (int i = 0; i < 100; i++) {
- hist.addValue(seconds(now), i, 2); // adds each item 2 times
+ hist.addValue(mkTimePoint(now), i, 2); // adds each item 2 times
}
}
- hist.update(seconds(3599));
+ hist.update(mkTimePoint(3599));
for (int pct = 1; pct <= 100; pct++) {
int expected = (pct - 1) / 10 * 10;
EXPECT_EQ(expected, hist.getPercentileBucketMin(pct, IntMTMHTS::MINUTE));
EXPECT_EQ(expected, hist.getPercentileBucketMin(pct, IntMTMHTS::ALLTIME));
}
- for (int b = 1; (b + 1) < hist.getNumBuckets(); ++b) {
- EXPECT_EQ(600 * 2, hist.getBucket(b).count(IntMTMHTS::MINUTE));
- EXPECT_EQ(6000 * 2, hist.getBucket(b).count(IntMTMHTS::TEN_MINUTE));
- EXPECT_EQ(36000 * 2, hist.getBucket(b).count(IntMTMHTS::HOUR));
- EXPECT_EQ(36000 * 2, hist.getBucket(b).count(IntMTMHTS::ALLTIME));
+ for (size_t b = 1; (b + 1) < hist.getNumBuckets(); ++b) {
+ EXPECT_EQ(600 * 2, hist.getBucket(b).count(IntMTMHTS::MINUTE));
+ EXPECT_EQ(6000 * 2, hist.getBucket(b).count(IntMTMHTS::TEN_MINUTE));
+ EXPECT_EQ(36000 * 2, hist.getBucket(b).count(IntMTMHTS::HOUR));
+ EXPECT_EQ(36000 * 2, hist.getBucket(b).count(IntMTMHTS::ALLTIME));
}
EXPECT_EQ(0, hist.getBucket(0).count(IntMTMHTS::MINUTE));
EXPECT_EQ(0, hist.getBucket(hist.getNumBuckets() - 1).count(
for (int now = 0; now < 3600; now++) {
for (int i = 0; i < 50; i++) {
- hist.addValue(seconds(now), i * 2, 2); // adds each item 2 times
+ hist.addValue(mkTimePoint(now), i * 2, 2); // adds each item 2 times
}
}
- hist.update(seconds(3599));
+ hist.update(mkTimePoint(3599));
for (int pct = 1; pct <= 100; pct++) {
int expected = (pct - 1) / 10 * 10;
EXPECT_EQ(expected, hist.getPercentileBucketMin(pct, IntMTMHTS::MINUTE));
hist.getBucket(hist.getNumBuckets() - 1).count(
IntMTMHTS::ALLTIME));
- for (int b = 1; (b + 1) < hist.getNumBuckets(); ++b) {
+ for (size_t b = 1; (b + 1) < hist.getNumBuckets(); ++b) {
EXPECT_EQ(600, hist.getBucket(b).count(IntMTMHTS::MINUTE));
EXPECT_EQ(6000, hist.getBucket(b).count(IntMTMHTS::TEN_MINUTE));
EXPECT_EQ(36000, hist.getBucket(b).count(IntMTMHTS::HOUR));
}
for (int i = 0; i < 100; ++i) {
- hist.addValue(seconds(3599), 200 + i);
+ hist.addValue(mkTimePoint(3599), 200 + i);
}
- hist.update(seconds(3599));
+ hist.update(mkTimePoint(3599));
EXPECT_EQ(100,
hist.getBucket(hist.getNumBuckets() - 1).count(
IntMTMHTS::ALLTIME));
60, IntMHTS::NUM_LEVELS,
IntMHTS::kDurations));
- mhts.update(seconds(0));
+ mhts.update(mkTimePoint(0));
int curTime;
for (curTime = 0; curTime < 7200; curTime++) {
- mhts.addValue(seconds(curTime), 1);
+ mhts.addValue(mkTimePoint(curTime), 1);
}
for (curTime = 7200; curTime < 7200 + 3540; curTime++) {
- mhts.addValue(seconds(curTime), 10);
+ mhts.addValue(mkTimePoint(curTime), 10);
}
for (curTime = 7200 + 3540; curTime < 7200 + 3600; curTime++) {
- mhts.addValue(seconds(curTime), 100);
+ mhts.addValue(mkTimePoint(curTime), 100);
}
- mhts.update(seconds(7200 + 3600 - 1));
+ mhts.update(mkTimePoint(7200 + 3600 - 1));
struct TimeInterval {
- TimeInterval(int s, int e)
- : start(s), end(e) {}
+ TimeInterval(int s, int e) : start(mkTimePoint(s)), end(mkTimePoint(e)) {}
- std::chrono::seconds start;
- std::chrono::seconds end;
+ StatsClock::time_point start;
+ StatsClock::time_point end;
};
TimeInterval intervals[12] = {
{ curTime - 60, curTime },
// 3 levels
for (int i = 1; i <= 100; i++) {
EXPECT_EQ(96, mhts.getPercentileBucketMin(i, 0));
- EXPECT_EQ(96, mhts.getPercentileBucketMin(i, seconds(curTime - 60),
- seconds(curTime)));
- EXPECT_EQ(8, mhts.getPercentileBucketMin(i, seconds(curTime - 3540),
- seconds(curTime - 60)));
+ EXPECT_EQ(
+ 96,
+ mhts.getPercentileBucketMin(
+ i, mkTimePoint(curTime - 60), mkTimePoint(curTime)));
+ EXPECT_EQ(
+ 8,
+ mhts.getPercentileBucketMin(
+ i, mkTimePoint(curTime - 3540), mkTimePoint(curTime - 60)));
}
EXPECT_EQ(8, mhts.getPercentileBucketMin(1, 1));
for (int i = 0; i < 12; i++) {
const auto& itv = intervals[i];
- int c = mhts.count(itv.start, itv.end);
// Some of the older intervals that fall in the alltime bucket
// are off by 1 or 2 in their estimated counts.
size_t tolerance = 0;
- if (itv.start <= seconds(curTime - 7200)) {
+ if (itv.start <= mkTimePoint(curTime - 7200)) {
tolerance = 2;
- } else if (itv.start <= seconds(curTime - 3000)) {
+ } else if (itv.start <= mkTimePoint(curTime - 3000)) {
tolerance = 1;
}
size_t actualCount = (itv.end - itv.start).count();
const int kNumIters = 1000;
for (int jj = 0; jj < kNumIters; ++jj) {
- h.addValue(seconds(time(nullptr)), value);
+ h.addValue(mkTimePoint(1), value);
}
- h.update(seconds(time(nullptr)));
+ h.update(mkTimePoint(1));
// since we've only added one unique value, all percentiles should
// be that value
EXPECT_EQ(h.getPercentileEstimate(10, 0), value);
// Things get trickier if there are multiple unique values.
const int kNewValue = 750;
for (int kk = 0; kk < 2*kNumIters; ++kk) {
- h.addValue(seconds(time(nullptr)), kNewValue);
+ h.addValue(mkTimePoint(1), kNewValue);
}
- h.update(seconds(time(nullptr)));
+ h.update(mkTimePoint(1));
EXPECT_NEAR(h.getPercentileEstimate(50, 0), kNewValue+5, 5);
if (value >= 0 && value <= 1000) {
// only do further testing if value is within our bucket range,
}
}
}
-