Skip to content

Commit b30d7a9

Browse files
Corey-Zumardcrankshaw
authored andcommitted
Fix histogram overflow errors (#272)
* Recalculate mean on insertion, improve histogram data precision and value capacity * Format code
1 parent cb8f47b commit b30d7a9

3 files changed

Lines changed: 92 additions & 38 deletions

File tree

src/libclipper/include/clipper/metrics.hpp

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -249,9 +249,18 @@ class ReservoirSampler {
249249
ReservoirSampler(ReservoirSampler &&other) = delete;
250250
ReservoirSampler &operator=(ReservoirSampler &&other) = delete;
251251

252-
void sample(const int64_t value);
252+
/**
253+
* Inserts a value into the reservoir, potentially
254+
* removing an old value if the reservoir is at maximum
255+
* capacity
256+
*
257+
* @param value The new value to be inserted
258+
* @return The value that was removed, or zero if no value was removed
259+
*/
260+
int64_t sample(const int64_t value);
253261
void clear();
254262
const std::vector<int64_t> snapshot() const;
263+
size_t current_size() const;
255264

256265
private:
257266
size_t sample_size_;
@@ -264,17 +273,17 @@ class HistogramStats {
264273
/** Constructs a HistogramStats object with all values zero **/
265274
explicit HistogramStats(){};
266275
explicit HistogramStats(size_t data_size, int64_t min, int64_t max,
267-
double mean, double std_dev, double p50, double p95,
268-
double p99);
276+
long double mean, long double std_dev,
277+
long double p50, long double p95, long double p99);
269278

270279
size_t data_size_ = 0;
271280
int64_t min_ = 0;
272281
int64_t max_ = 0;
273-
double mean_ = 0;
274-
double std_dev_ = 0;
275-
double p50_ = 0;
276-
double p95_ = 0;
277-
double p99_ = 0;
282+
long double mean_ = 0;
283+
long double std_dev_ = 0;
284+
long double p50_ = 0;
285+
long double p95_ = 0;
286+
long double p99_ = 0;
278287
};
279288

280289
class Histogram : public Metric {
@@ -290,10 +299,10 @@ class Histogram : public Metric {
290299

291300
void insert(const int64_t value);
292301
const HistogramStats compute_stats();
293-
static double percentile(std::vector<int64_t> snapshot, double rank);
302+
static long double percentile(std::vector<int64_t> snapshot, double rank);
294303
// This method obtains a snapshot from the histogram's reservoir sampler
295304
// and then calculates the percentile
296-
double percentile(double rank);
305+
long double percentile(double rank);
297306

298307
// Metric implementation
299308
MetricType type() const override;
@@ -303,8 +312,20 @@ class Histogram : public Metric {
303312
void clear() override;
304313

305314
private:
315+
/**
316+
* @param old_reservoir_size The old size of the reservoir
317+
* @param new_reservoir_size The new size of the reservoir
318+
* @param new_value The value that was just inserted into the reservoir
319+
* @param old_value The value that was just removed from the reservoir (use
320+
* zero if nothing was removed)
321+
*/
322+
void update_mean(const size_t old_reservoir_size,
323+
const size_t new_reservoir_size, const int64_t new_value,
324+
const int64_t old_value);
325+
306326
std::string name_;
307327
std::string unit_;
328+
long double mean_ = 0;
308329
ReservoirSampler sampler_;
309330
std::mutex sampler_lock_;
310331
};

src/libclipper/src/metrics.cpp

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,8 @@ void Meter::clear() {
401401
ReservoirSampler::ReservoirSampler(size_t sample_size)
402402
: sample_size_(sample_size) {}
403403

404-
void ReservoirSampler::sample(const int64_t value) {
404+
int64_t ReservoirSampler::sample(const int64_t value) {
405+
int64_t replaced_value = 0;
405406
if (n_ < sample_size_) {
406407
reservoir_.push_back(value);
407408
} else {
@@ -410,12 +411,16 @@ void ReservoirSampler::sample(const int64_t value) {
410411
}
411412
size_t j = rand() % (n_ + 1);
412413
if (j < sample_size_) {
414+
replaced_value = reservoir_[j];
413415
reservoir_[j] = value;
414416
}
415417
}
416418
n_++;
419+
return replaced_value;
417420
}
418421

422+
size_t ReservoirSampler::current_size() const { return reservoir_.size(); }
423+
419424
const std::vector<int64_t> ReservoirSampler::snapshot() const {
420425
return reservoir_;
421426
}
@@ -426,8 +431,9 @@ void ReservoirSampler::clear() {
426431
}
427432

428433
HistogramStats::HistogramStats(size_t data_size, int64_t min, int64_t max,
429-
double mean, double std_dev, double p50,
430-
double p95, double p99)
434+
long double mean, long double std_dev,
435+
long double p50, long double p95,
436+
long double p99)
431437
: data_size_(data_size),
432438
min_(min),
433439
max_(max),
@@ -443,10 +449,21 @@ Histogram::Histogram(const std::string name, const std::string unit,
443449

444450
void Histogram::insert(const int64_t value) {
445451
std::lock_guard<std::mutex> guard(sampler_lock_);
446-
sampler_.sample(value);
452+
size_t old_reservoir_size = sampler_.current_size();
453+
int64_t replaced_value = sampler_.sample(value);
454+
size_t new_reservoir_size = sampler_.current_size();
455+
update_mean(old_reservoir_size, new_reservoir_size, value, replaced_value);
456+
}
457+
458+
void Histogram::update_mean(const size_t old_reservoir_size,
459+
const size_t new_reservoir_size,
460+
const int64_t new_value, const int64_t old_value) {
461+
int64_t old_sum = static_cast<int64_t>(mean_ * old_reservoir_size);
462+
mean_ = static_cast<long double>(old_sum - old_value + new_value) /
463+
static_cast<long double>(new_reservoir_size);
447464
}
448465

449-
double Histogram::percentile(std::vector<int64_t> snapshot, double rank) {
466+
long double Histogram::percentile(std::vector<int64_t> snapshot, double rank) {
450467
if (rank < 0 || rank > 1) {
451468
throw std::invalid_argument("Percentile rank must be in [0,1]!");
452469
}
@@ -468,7 +485,7 @@ double Histogram::percentile(std::vector<int64_t> snapshot, double rank) {
468485
x = sample_size;
469486
}
470487
size_t index = std::floor(x) - 1;
471-
double v = snapshot[index];
488+
long double v = snapshot[index];
472489
double remainder = x - std::floor(x);
473490
if (remainder == 0) {
474491
return v;
@@ -477,7 +494,7 @@ double Histogram::percentile(std::vector<int64_t> snapshot, double rank) {
477494
}
478495
}
479496

480-
double Histogram::percentile(double rank) {
497+
long double Histogram::percentile(double rank) {
481498
std::lock_guard<std::mutex> guard(sampler_lock_);
482499
std::vector<int64_t> snapshot = sampler_.snapshot();
483500
if (snapshot.size() == 0) {
@@ -499,22 +516,19 @@ const HistogramStats Histogram::compute_stats() {
499516
std::sort(snapshot.begin(), snapshot.end());
500517
int64_t min = snapshot.front();
501518
int64_t max = snapshot.back();
502-
double p50 = percentile(snapshot, .5);
503-
double p95 = percentile(snapshot, .95);
504-
double p99 = percentile(snapshot, .99);
505-
double mean = static_cast<double>(
506-
std::accumulate(snapshot.begin(), snapshot.end(), 0)) /
507-
static_cast<double>(snapshot_size);
508-
double var = 0;
519+
long double p50 = percentile(snapshot, .5);
520+
long double p95 = percentile(snapshot, .95);
521+
long double p99 = percentile(snapshot, .99);
522+
long double var = 0;
509523
if (snapshot_size > 1) {
510524
for (auto elem : snapshot) {
511-
double incr = std::pow((static_cast<double>(elem) - mean), 2);
525+
long double incr = std::pow(elem - mean_, static_cast<long double>(2));
512526
var += incr;
513527
}
514528
var = var / static_cast<double>(snapshot_size);
515529
}
516-
double std_dev = std::sqrt(var);
517-
return HistogramStats(snapshot_size, min, max, mean, std_dev, p50, p95, p99);
530+
long double std_dev = std::sqrt(var);
531+
return HistogramStats(snapshot_size, min, max, mean_, std_dev, p50, p95, p99);
518532
}
519533

520534
MetricType Histogram::type() const { return MetricType::Histogram; }

src/libclipper/test/metrics_test.cpp

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include <cmath>
2+
#include <limits>
23
#include <string>
34

45
#include <gtest/gtest.h>
@@ -117,7 +118,7 @@ TEST(MetricsTests, HistogramPercentileFunctionCorrectness) {
117118
ASSERT_EQ(Histogram::percentile(vec4, p5), Histogram::percentile(vec5, p5));
118119
}
119120

120-
TEST(MetricsTests, HistogramStatsCorrectness) {
121+
TEST(MetricsTests, HistogramStatsAreCorrectWithNumericallySmallElements) {
121122
int64_t arr[] = {16, 53, 104, 113, 185, 202};
122123
size_t sample_size = 6;
123124
Histogram histogram("Test Histogram", "milliseconds", sample_size);
@@ -134,6 +135,23 @@ TEST(MetricsTests, HistogramStatsCorrectness) {
134135
ASSERT_EQ(stats.p99_, 202);
135136
}
136137

138+
TEST(MetricsTests, HistogramMinMaxMeanAreCorrectWithNumericallyLargeElements) {
139+
size_t sample_size = 6;
140+
Histogram histogram("Test Histogram", "milliseconds", sample_size);
141+
long max_long = std::numeric_limits<long>::max();
142+
int64_t sum = 0;
143+
for (int i = 0; i < sample_size; i++) {
144+
histogram.insert(max_long - i);
145+
sum += max_long - i;
146+
}
147+
long double expected_mean =
148+
static_cast<long double>(sum) / static_cast<long double>(sample_size);
149+
HistogramStats stats = histogram.compute_stats();
150+
ASSERT_DOUBLE_EQ(stats.mean_, expected_mean);
151+
ASSERT_DOUBLE_EQ(stats.max_, max_long);
152+
ASSERT_DOUBLE_EQ(stats.min_, max_long - sample_size + 1);
153+
}
154+
137155
TEST(MetricsTests, MetricsRegistryReportingFormatCorrectness) {
138156
MetricsRegistry &registry = MetricsRegistry::get_metrics();
139157
const std::string hist1_name("Hist 1");
@@ -177,23 +195,24 @@ TEST(MetricsTests, MetricsRegistryReportingFormatCorrectness) {
177195
ASSERT_EQ(hist1_tree.get<size_t>("size"), hist1_stats.data_size_);
178196
ASSERT_EQ(hist1_tree.get<int64_t>("min"), hist1_stats.min_);
179197
ASSERT_EQ(hist1_tree.get<int64_t>("max"), hist1_stats.max_);
180-
ASSERT_EQ(hist1_tree.get<double>("mean"), hist1_stats.mean_);
181-
ASSERT_EQ(hist1_tree.get<double>("std_dev"), hist1_stats.std_dev_);
182-
ASSERT_EQ(hist1_tree.get<double>("p50"), hist1_stats.p50_);
183-
ASSERT_EQ(hist1_tree.get<double>("p95"), hist1_stats.p95_);
184-
ASSERT_EQ(hist1_tree.get<double>("p99"), hist1_stats.p99_);
198+
ASSERT_DOUBLE_EQ(hist1_tree.get<long double>("mean"), hist1_stats.mean_);
199+
ASSERT_EQ(hist1_tree.get<long double>("std_dev"), hist1_stats.std_dev_);
200+
ASSERT_EQ(hist1_tree.get<long double>("p50"), hist1_stats.p50_);
201+
ASSERT_EQ(hist1_tree.get<long double>("p95"), hist1_stats.p95_);
202+
ASSERT_EQ(hist1_tree.get<long double>("p99"), hist1_stats.p99_);
185203

186204
boost::property_tree::ptree hist2_tree =
187205
hists_tree.back().second.get_child(hist2_name);
188206
HistogramStats hist2_stats = hist2->compute_stats();
189207
ASSERT_EQ(hist2_tree.get<size_t>("size"), hist2_stats.data_size_);
190208
ASSERT_EQ(hist2_tree.get<int64_t>("min"), hist2_stats.min_);
191209
ASSERT_EQ(hist2_tree.get<int64_t>("max"), hist2_stats.max_);
192-
ASSERT_EQ(hist2_tree.get<double>("mean"), hist2_stats.mean_);
193-
ASSERT_EQ(hist2_tree.get<double>("std_dev"), hist2_stats.std_dev_);
194-
ASSERT_EQ(hist2_tree.get<double>("p50"), hist2_stats.p50_);
195-
ASSERT_EQ(hist2_tree.get<double>("p95"), hist2_stats.p95_);
196-
ASSERT_EQ(hist2_tree.get<double>("p99"), hist2_stats.p99_);
210+
ASSERT_DOUBLE_EQ(hist2_tree.get<long double>("mean"), hist2_stats.mean_);
211+
ASSERT_DOUBLE_EQ(hist2_tree.get<long double>("std_dev"),
212+
hist2_stats.std_dev_);
213+
ASSERT_DOUBLE_EQ(hist2_tree.get<long double>("p50"), hist2_stats.p50_);
214+
ASSERT_DOUBLE_EQ(hist2_tree.get<long double>("p95"), hist2_stats.p95_);
215+
ASSERT_DOUBLE_EQ(hist2_tree.get<long double>("p99"), hist2_stats.p99_);
197216

198217
boost::property_tree::ptree counters_tree =
199218
report_tree.get_child(get_metrics_category_name(MetricType::Counter));

0 commit comments

Comments
 (0)