proxygen
TimeseriesHistogram.h
Go to the documentation of this file.
1 /*
2  * Copyright 2013-present Facebook, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #pragma once
18 
19 #include <folly/stats/Histogram.h>
21 #include <string>
22 
23 namespace folly {
24 
25 /*
26  * TimeseriesHistogram tracks data distributions as they change over time.
27  *
28  * Specifically, it is a bucketed histogram with different value ranges assigned
29  * to each bucket. Within each bucket is a MultiLevelTimeSeries from
30  * 'folly/stats/MultiLevelTimeSeries.h'. This means that each bucket contains a
31  * different set of data for different historical time periods, and one can
32  * query data distributions over different trailing time windows.
33  *
34  * For example, this can answer questions: "What is the data distribution over
35  * the last minute? Over the last 10 minutes? Since I last cleared this
36  * histogram?"
37  *
38  * The class can also estimate percentiles and answer questions like: "What was
39  * the 99th percentile data value over the last 10 minutes?"
40  *
41  * Note: that depending on the size of your buckets and the smoothness
42  * of your data distribution, the estimate may be way off from the actual
43  * value. In particular, if the given percentile falls outside of the bucket
44  * range (i.e. your buckets range in 0 - 100,000 but the 99th percentile is
45  * around 115,000) this estimate may be very wrong.
46  *
47  * The memory usage for a typical histogram is roughly 3k * (# of buckets). All
48  * insertion operations are amortized O(1), and all queries are O(# of buckets).
49  */
50 template <
51  class T,
52  class CT = LegacyStatsClock<std::chrono::seconds>,
55  private:
56  // NOTE: T must be equivalent to _signed_ numeric type for our math.
57  static_assert(std::numeric_limits<T>::is_signed, "");
58 
59  public:
60  // Values to be inserted into container
61  using ValueType = T;
62  // The container type we use internally for each bucket
63  using ContainerType = C;
64  // Clock, duration, and time point types
65  using Clock = CT;
66  using Duration = typename Clock::duration;
67  using TimePoint = typename Clock::time_point;
68 
69  /*
70  * Create a TimeSeries histogram and initialize the bucketing and levels.
71  *
72  * The buckets are created by chopping the range [min, max) into pieces
73  * of size bucketSize, with the last bucket being potentially shorter. Two
74  * additional buckets are always created -- the "under" bucket for the range
75  * (-inf, min) and the "over" bucket for the range [max, +inf).
76  *
77  * @param bucketSize the width of each bucket
78  * @param min the smallest value for the bucket range.
79  * @param max the largest value for the bucket range
80  * @param defaultContainer a pre-initialized timeseries with the desired
81  * number of levels and their durations.
82  */
84  ValueType bucketSize,
85  ValueType min,
86  ValueType max,
87  const ContainerType& defaultContainer);
88 
89  /* Return the bucket size of each bucket in the histogram. */
91  return buckets_.getBucketSize();
92  }
93 
94  /* Return the min value at which bucketing begins. */
95  ValueType getMin() const {
96  return buckets_.getMin();
97  }
98 
99  /* Return the max value at which bucketing ends. */
100  ValueType getMax() const {
101  return buckets_.getMax();
102  }
103 
104  /* Return the number of levels of the Timeseries object in each bucket */
105  size_t getNumLevels() const {
106  return buckets_.getByIndex(0).numLevels();
107  }
108 
109  /* Return the number of buckets */
110  size_t getNumBuckets() const {
111  return buckets_.getNumBuckets();
112  }
113 
114  /*
115  * Return the threshold of the bucket for the given index in range
116  * [0..numBuckets). The bucket will have range [thresh, thresh + bucketSize)
117  * or [thresh, max), whichever is shorter.
118  */
119  ValueType getBucketMin(size_t bucketIdx) const {
120  return buckets_.getBucketMin(bucketIdx);
121  }
122 
123  /* Return the actual timeseries in the given bucket (for reading only!) */
124  const ContainerType& getBucket(size_t bucketIdx) const {
125  return buckets_.getByIndex(bucketIdx);
126  }
127 
128  /* Total count of values at the given timeseries level (all buckets). */
129  uint64_t count(size_t level) const {
130  uint64_t total = 0;
131  for (size_t b = 0; b < buckets_.getNumBuckets(); ++b) {
132  total += buckets_.getByIndex(b).count(level);
133  }
134  return total;
135  }
136 
137  /* Total count of values added during the given interval (all buckets). */
139  uint64_t total = 0;
140  for (size_t b = 0; b < buckets_.getNumBuckets(); ++b) {
141  total += buckets_.getByIndex(b).count(start, end);
142  }
143  return total;
144  }
145 
146  /* Total sum of values at the given timeseries level (all buckets). */
147  ValueType sum(size_t level) const {
148  ValueType total = ValueType();
149  for (size_t b = 0; b < buckets_.getNumBuckets(); ++b) {
150  total += buckets_.getByIndex(b).sum(level);
151  }
152  return total;
153  }
154 
155  /* Total sum of values added during the given interval (all buckets). */
157  ValueType total = ValueType();
158  for (size_t b = 0; b < buckets_.getNumBuckets(); ++b) {
159  total += buckets_.getByIndex(b).sum(start, end);
160  }
161  return total;
162  }
163 
164  /* Average of values at the given timeseries level (all buckets). */
165  template <typename ReturnType = double>
166  ReturnType avg(size_t level) const {
167  auto total = ValueType();
168  uint64_t nsamples = 0;
169  computeAvgData(&total, &nsamples, level);
170  return folly::detail::avgHelper<ReturnType>(total, nsamples);
171  }
172 
173  /* Average of values added during the given interval (all buckets). */
174  template <typename ReturnType = double>
175  ReturnType avg(TimePoint start, TimePoint end) const {
176  auto total = ValueType();
177  uint64_t nsamples = 0;
178  computeAvgData(&total, &nsamples, start, end);
179  return folly::detail::avgHelper<ReturnType>(total, nsamples);
180  }
181 
182  /*
183  * Rate at the given timeseries level (all buckets).
184  * This is the sum of all values divided by the time interval (in seconds).
185  */
186  template <typename ReturnType = double>
187  ReturnType rate(size_t level) const {
188  auto total = ValueType();
189  Duration elapsed(0);
190  computeRateData(&total, &elapsed, level);
191  return folly::detail::rateHelper<ReturnType, Duration, Duration>(
192  total, elapsed);
193  }
194 
195  /*
196  * Rate for the given interval (all buckets).
197  * This is the sum of all values divided by the time interval (in seconds).
198  */
199  template <typename ReturnType = double>
200  ReturnType rate(TimePoint start, TimePoint end) const {
201  auto total = ValueType();
202  Duration elapsed(0);
203  computeRateData(&total, &elapsed, start, end);
204  return folly::detail::rateHelper<ReturnType, Duration, Duration>(
205  total, elapsed);
206  }
207 
208  /*
209  * Update every underlying timeseries object with the given timestamp. You
210  * must call this directly before querying to ensure that the data in all
211  * buckets is decayed properly.
212  */
213  void update(TimePoint now);
214 
215  /* clear all the data from the histogram. */
216  void clear();
217 
218  /* Add a value into the histogram with timestamp 'now' */
219  void addValue(TimePoint now, const ValueType& value);
220  /* Add a value the given number of times with timestamp 'now' */
221  void addValue(TimePoint now, const ValueType& value, uint64_t times);
222 
223  /*
224  * Add all of the values from the specified histogram.
225  *
226  * All of the values will be added to the current time-slot.
227  *
228  * One use of this is for thread-local caching of frequently updated
229  * histogram data. For example, each thread can store a thread-local
230  * Histogram that is updated frequently, and only add it to the global
231  * TimeseriesHistogram once a second.
232  */
234 
235  /*
236  * Return an estimate of the value at the given percentile in the histogram
237  * in the given timeseries level. The percentile is estimated as follows:
238  *
239  * - We retrieve a count of the values in each bucket (at the given level)
240  * - We determine via the counts which bucket the given percentile falls in.
241  * - We assume the average value in the bucket is also its median
242  * - We then linearly interpolate within the bucket, by assuming that the
243  * distribution is uniform in the two value ranges [left, median) and
244  * [median, right) where [left, right) is the bucket value range.
245  *
246  * Caveats:
247  * - If the histogram is empty, this always returns ValueType(), usually 0.
248  * - For the 'under' and 'over' special buckets, their range is unbounded
249  * on one side. In order for the interpolation to work, we assume that
250  * the average value in the bucket is equidistant from the two edges of
251  * the bucket. In other words, we assume that the distance between the
252  * average and the known bound is equal to the distance between the average
253  * and the unknown bound.
254  */
255  ValueType getPercentileEstimate(double pct, size_t level) const;
256  /*
257  * Return an estimate of the value at the given percentile in the histogram
258  * in the given historical interval. Please see the documentation for
259  * getPercentileEstimate(double pct, size_t level) for the explanation of the
260  * estimation algorithm.
261  */
263  const;
264 
265  /*
266  * Return the bucket index that the given percentile falls into (in the
267  * given timeseries level). This index can then be used to retrieve either
268  * the bucket threshold, or other data from inside the bucket.
269  */
270  size_t getPercentileBucketIdx(double pct, size_t level) const;
271  /*
272  * Return the bucket index that the given percentile falls into (in the
273  * given historical interval). This index can then be used to retrieve either
274  * the bucket threshold, or other data from inside the bucket.
275  */
276  size_t getPercentileBucketIdx(double pct, TimePoint start, TimePoint end)
277  const;
278 
279  /* Get the bucket threshold for the bucket containing the given pct. */
280  ValueType getPercentileBucketMin(double pct, size_t level) const {
281  return getBucketMin(getPercentileBucketIdx(pct, level));
282  }
283  /* Get the bucket threshold for the bucket containing the given pct. */
285  const {
286  return getBucketMin(getPercentileBucketIdx(pct, start, end));
287  }
288 
289  /*
290  * Print out serialized data from all buckets at the given level.
291  * Format is: BUCKET [',' BUCKET ...]
292  * Where: BUCKET == bucketMin ':' count ':' avg
293  */
294  std::string getString(size_t level) const;
295 
296  /*
297  * Print out serialized data for all buckets in the historical interval.
298  * For format, please see getString(size_t level).
299  */
300  std::string getString(TimePoint start, TimePoint end) const;
301 
302  /*
303  * Legacy APIs that accept a Duration parameters rather than TimePoint.
304  *
305  * These treat the Duration as relative to the clock epoch.
306  * Prefer using the correct TimePoint-based APIs instead. These APIs will
307  * eventually be deprecated and removed.
308  */
309  void update(Duration now) {
310  update(TimePoint(now));
311  }
312  void addValue(Duration now, const ValueType& value) {
313  addValue(TimePoint(now), value);
314  }
315  void addValue(Duration now, const ValueType& value, uint64_t times) {
316  addValue(TimePoint(now), value, times);
317  }
318  void addValues(Duration now, const folly::Histogram<ValueType>& values) {
319  addValues(TimePoint(now), values);
320  }
321 
322  private:
324  struct CountFromLevel {
325  explicit CountFromLevel(size_t level) : level_(level) {}
326 
327  uint64_t operator()(const ContainerType& bucket) const {
328  return bucket.count(level_);
329  }
330 
331  private:
332  size_t level_;
333  };
335  explicit CountFromInterval(TimePoint start, TimePoint end)
336  : start_(start), end_(end) {}
337 
338  uint64_t operator()(const ContainerType& bucket) const {
339  return bucket.count(start_, end_);
340  }
341 
342  private:
345  };
346 
347  struct AvgFromLevel {
348  explicit AvgFromLevel(size_t level) : level_(level) {}
349 
350  ValueType operator()(const ContainerType& bucket) const {
351  return bucket.template avg<ValueType>(level_);
352  }
353 
354  private:
355  size_t level_;
356  };
357 
358  template <typename ReturnType>
360  explicit AvgFromInterval(TimePoint start, TimePoint end)
361  : start_(start), end_(end) {}
362 
363  ReturnType operator()(const ContainerType& bucket) const {
364  return bucket.template avg<ReturnType>(start_, end_);
365  }
366 
367  private:
370  };
371 
372  /*
373  * Special logic for the case of only one unique value registered
374  * (this can happen when clients don't pick good bucket ranges or have
375  * other bugs). It's a lot easier for clients to track down these issues
376  * if they are getting the correct value.
377  */
378  void maybeHandleSingleUniqueValue(const ValueType& value);
379 
380  void computeAvgData(ValueType* total, uint64_t* nsamples, size_t level) const;
381  void computeAvgData(
382  ValueType* total,
383  uint64_t* nsamples,
384  TimePoint start,
385  TimePoint end) const;
386  void computeRateData(ValueType* total, Duration* elapsed, size_t level) const;
387  void computeRateData(
388  ValueType* total,
389  Duration* elapsed,
390  TimePoint start,
391  TimePoint end) const;
392 
397 };
398 } // namespace folly
folly::detail::HistogramBuckets< ValueType, ContainerType > buckets_
void computeAvgData(ValueType *total, uint64_t *nsamples, size_t level) const
uint64_t count(size_t level) const
char b
LogLevel max
Definition: LogLevel.cpp:31
std::string getString(size_t level) const
ValueType getBucketMin(size_t bucketIdx) const
std::chrono::steady_clock::time_point now()
ReturnType rate(size_t level) const
ValueType getBucketSize() const
Definition: Histogram.h:64
uint64_t operator()(const ContainerType &bucket) const
ReturnType operator()(const ContainerType &bucket) const
ValueType sum(TimePoint start, TimePoint end) const
folly::std T
—— Concurrent Priority Queue Implementation ——
Definition: AtomicBitSet.h:29
ReturnType avg(size_t level) const
void addValue(Duration now, const ValueType &value)
CountFromInterval(TimePoint start, TimePoint end)
void addValues(TimePoint now, const folly::Histogram< ValueType > &values)
void maybeHandleSingleUniqueValue(const ValueType &value)
ValueType getBucketSize() const
uint64_t count(TimePoint start, TimePoint end) const
LogLevel min
Definition: LogLevel.cpp:30
size_t getNumBuckets() const
Definition: Histogram.h:85
ValueType getBucketMin(size_t idx) const
Definition: Histogram.h:124
AvgFromInterval(TimePoint start, TimePoint end)
ValueType getMax() const
Definition: Histogram.h:74
typename Clock::duration Duration
auto end(TestAdlIterable &instance)
Definition: ForeachTest.cpp:62
uint64_t operator()(const ContainerType &bucket) const
ValueType getPercentileEstimate(double pct, size_t level) const
ReturnType avg(TimePoint start, TimePoint end) const
BucketType & getByIndex(size_t idx)
Definition: Histogram.h:108
ValueType operator()(const ContainerType &bucket) const
#define C(name, bit)
Definition: CpuId.h:204
void addValue(Duration now, const ValueType &value, uint64_t times)
auto start
void addValues(Duration now, const folly::Histogram< ValueType > &values)
ValueType getPercentileBucketMin(double pct, size_t level) const
void computeRateData(ValueType *total, Duration *elapsed, size_t level) const
ValueType getMin() const
Definition: Histogram.h:69
const char * string
Definition: Conv.cpp:212
uintptr_t start_
uint64_t value(const typename LockFreeRingBuffer< T, Atom >::Cursor &rbcursor)
size_t getPercentileBucketIdx(double pct, size_t level) const
void addValue(TimePoint now, const ValueType &value)
Future< Unit > times(const int n, F &&thunk)
Definition: Future-inl.h:2348
ValueType sum(size_t level) const
const ContainerType & getBucket(size_t bucketIdx) const
typename Clock::time_point TimePoint
TimeseriesHistogram(ValueType bucketSize, ValueType min, ValueType max, const ContainerType &defaultContainer)
ReturnType rate(TimePoint start, TimePoint end) const
uintptr_t end_
ValueType getPercentileBucketMin(double pct, TimePoint start, TimePoint end) const
std::vector< int > values(1'000)