update TimeseriesHistogram::rate to return a template type
[folly.git] / folly / stats / TimeseriesHistogram.h
1 /*
2  * Copyright 2016 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 <string>
20 #include <folly/stats/Histogram.h>
21 #include <folly/stats/MultiLevelTimeSeries.h>
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 <class T, class TT=std::chrono::seconds,
51           class C=folly::MultiLevelTimeSeries<T, TT>>
52 class TimeseriesHistogram {
53  private:
54    // NOTE: T must be equivalent to _signed_ numeric type for our math.
55    static_assert(std::numeric_limits<T>::is_signed, "");
56
57  public:
58   // values to be inserted into container
59   typedef T ValueType;
60   // the container type we use internally for each bucket
61   typedef C ContainerType;
62   // The time type.
63   typedef TT TimeType;
64
65   /*
66    * Create a TimeSeries histogram and initialize the bucketing and levels.
67    *
68    * The buckets are created by chopping the range [min, max) into pieces
69    * of size bucketSize, with the last bucket being potentially shorter.  Two
70    * additional buckets are always created -- the "under" bucket for the range
71    * (-inf, min) and the "over" bucket for the range [max, +inf).
72    *
73    * @param bucketSize the width of each bucket
74    * @param min the smallest value for the bucket range.
75    * @param max the largest value for the bucket range
76    * @param defaultContainer a pre-initialized timeseries with the desired
77    *                         number of levels and their durations.
78    */
79   TimeseriesHistogram(ValueType bucketSize, ValueType min, ValueType max,
80                       const ContainerType& defaultContainer);
81
82   /* Return the bucket size of each bucket in the histogram. */
83   ValueType getBucketSize() const { return buckets_.getBucketSize(); }
84
85   /* Return the min value at which bucketing begins. */
86   ValueType getMin() const { return buckets_.getMin(); }
87
88   /* Return the max value at which bucketing ends. */
89   ValueType getMax() const { return buckets_.getMax(); }
90
91   /* Return the number of levels of the Timeseries object in each bucket */
92   int getNumLevels() const {
93     return buckets_.getByIndex(0).numLevels();
94   }
95
96   /* Return the number of buckets */
97   int getNumBuckets() const { return buckets_.getNumBuckets(); }
98
99   /*
100    * Return the threshold of the bucket for the given index in range
101    * [0..numBuckets).  The bucket will have range [thresh, thresh + bucketSize)
102    * or [thresh, max), whichever is shorter.
103    */
104   ValueType getBucketMin(int bucketIdx) const {
105     return buckets_.getBucketMin(bucketIdx);
106   }
107
108   /* Return the actual timeseries in the given bucket (for reading only!) */
109   const ContainerType& getBucket(int bucketIdx) const {
110     return buckets_.getByIndex(bucketIdx);
111   }
112
113   /* Total count of values at the given timeseries level (all buckets). */
114   int64_t count(int level) const {
115     int64_t total = 0;
116     for (unsigned int b = 0; b < buckets_.getNumBuckets(); ++b) {
117       total += buckets_.getByIndex(b).count(level);
118     }
119     return total;
120   }
121
122   /* Total count of values added during the given interval (all buckets). */
123   int64_t count(TimeType start, TimeType end) const {
124     int64_t total = 0;
125     for (unsigned int b = 0; b < buckets_.getNumBuckets(); ++b) {
126       total += buckets_.getByIndex(b).count(start, end);
127     }
128     return total;
129   }
130
131   /* Total sum of values at the given timeseries level (all buckets). */
132   ValueType sum(int level) const {
133     ValueType total = ValueType();
134     for (unsigned int b = 0; b < buckets_.getNumBuckets(); ++b) {
135       total += buckets_.getByIndex(b).sum(level);
136     }
137     return total;
138   }
139
140   /* Total sum of values added during the given interval (all buckets). */
141   ValueType sum(TimeType start, TimeType end) const {
142     ValueType total = ValueType();
143     for (unsigned int b = 0; b < buckets_.getNumBuckets(); ++b) {
144       total += buckets_.getByIndex(b).sum(start, end);
145     }
146     return total;
147   }
148
149   /* Average of values at the given timeseries level (all buckets). */
150   template <typename ReturnType = double>
151   ReturnType avg(int level) const {
152     auto total = ValueType();
153     int64_t nsamples = 0;
154     computeAvgData(&total, &nsamples, level);
155     return folly::detail::avgHelper<ReturnType>(total, nsamples);
156   }
157
158   /* Average of values added during the given interval (all buckets). */
159   template <typename ReturnType = double>
160   ReturnType avg(TimeType start, TimeType end) const {
161     auto total = ValueType();
162     int64_t nsamples = 0;
163     computeAvgData(&total, &nsamples, start, end);
164     return folly::detail::avgHelper<ReturnType>(total, nsamples);
165   }
166
167   /*
168    * Rate at the given timeseries level (all buckets).
169    * This is the sum of all values divided by the time interval (in seconds).
170    */
171   template <typename ReturnType = double>
172   ReturnType rate(int level) const {
173     auto total = ValueType();
174     TimeType elapsed(0);
175     computeRateData(&total, &elapsed, level);
176     return folly::detail::rateHelper<ReturnType, TimeType, TimeType>(
177         total, elapsed);
178   }
179
180   /*
181    * Rate for the given interval (all buckets).
182    * This is the sum of all values divided by the time interval (in seconds).
183    */
184   template <typename ReturnType = double>
185   ReturnType rate(TimeType start, TimeType end) const {
186     auto total = ValueType();
187     TimeType elapsed(0);
188     computeRateData(&total, &elapsed, start, end);
189     return folly::detail::rateHelper<ReturnType, TimeType, TimeType>(
190         total, elapsed);
191   }
192
193   /*
194    * Update every underlying timeseries object with the given timestamp. You
195    * must call this directly before querying to ensure that the data in all
196    * buckets is decayed properly.
197    */
198   void update(TimeType now);
199
200   /* clear all the data from the histogram. */
201   void clear();
202
203   /* Add a value into the histogram with timestamp 'now' */
204   void addValue(TimeType now, const ValueType& value);
205   /* Add a value the given number of times with timestamp 'now' */
206   void addValue(TimeType now, const ValueType& value, int64_t times);
207
208   /*
209    * Add all of the values from the specified histogram.
210    *
211    * All of the values will be added to the current time-slot.
212    *
213    * One use of this is for thread-local caching of frequently updated
214    * histogram data.  For example, each thread can store a thread-local
215    * Histogram that is updated frequently, and only add it to the global
216    * TimeseriesHistogram once a second.
217    */
218   void addValues(TimeType now, const folly::Histogram<ValueType>& values);
219
220   /*
221    * Return an estimate of the value at the given percentile in the histogram
222    * in the given timeseries level.  The percentile is estimated as follows:
223    *
224    * - We retrieve a count of the values in each bucket (at the given level)
225    * - We determine via the counts which bucket the given percentile falls in.
226    * - We assume the average value in the bucket is also its median
227    * - We then linearly interpolate within the bucket, by assuming that the
228    *   distribution is uniform in the two value ranges [left, median) and
229    *   [median, right) where [left, right) is the bucket value range.
230    *
231    * Caveats:
232    * - If the histogram is empty, this always returns ValueType(), usually 0.
233    * - For the 'under' and 'over' special buckets, their range is unbounded
234    *   on one side.  In order for the interpolation to work, we assume that
235    *   the average value in the bucket is equidistant from the two edges of
236    *   the bucket.  In other words, we assume that the distance between the
237    *   average and the known bound is equal to the distance between the average
238    *   and the unknown bound.
239    */
240   ValueType getPercentileEstimate(double pct, int level) const;
241   /*
242    * Return an estimate of the value at the given percentile in the histogram
243    * in the given historical interval.  Please see the documentation for
244    * getPercentileEstimate(int pct, int level) for the explanation of the
245    * estimation algorithm.
246    */
247   ValueType getPercentileEstimate(double pct, TimeType start, TimeType end)
248     const;
249
250   /*
251    * Return the bucket index that the given percentile falls into (in the
252    * given timeseries level).  This index can then be used to retrieve either
253    * the bucket threshold, or other data from inside the bucket.
254    */
255   int getPercentileBucketIdx(double pct, int level) const;
256   /*
257    * Return the bucket index that the given percentile falls into (in the
258    * given historical interval).  This index can then be used to retrieve either
259    * the bucket threshold, or other data from inside the bucket.
260    */
261   int getPercentileBucketIdx(double pct, TimeType start, TimeType end) const;
262
263   /* Get the bucket threshold for the bucket containing the given pct. */
264   int getPercentileBucketMin(double pct, int level) const {
265     return getBucketMin(getPercentileBucketIdx(pct, level));
266   }
267   /* Get the bucket threshold for the bucket containing the given pct. */
268   int getPercentileBucketMin(double pct, TimeType start, TimeType end) const {
269     return getBucketMin(getPercentileBucketIdx(pct, start, end));
270   }
271
272   /*
273    * Print out serialized data from all buckets at the given level.
274    * Format is: BUCKET [',' BUCKET ...]
275    * Where: BUCKET == bucketMin ':' count ':' avg
276    */
277   std::string getString(int level) const;
278
279   /*
280    * Print out serialized data for all buckets in the historical interval.
281    * For format, please see getString(int level).
282    */
283   std::string getString(TimeType start, TimeType end) const;
284
285  private:
286   typedef ContainerType Bucket;
287   struct CountFromLevel {
288     explicit CountFromLevel(int level) : level_(level) {}
289
290     uint64_t operator()(const ContainerType& bucket) const {
291       return bucket.count(level_);
292     }
293
294    private:
295     int level_;
296   };
297   struct CountFromInterval {
298     explicit CountFromInterval(TimeType start, TimeType end)
299       : start_(start),
300         end_(end) {}
301
302     uint64_t operator()(const ContainerType& bucket) const {
303       return bucket.count(start_, end_);
304     }
305
306    private:
307     TimeType start_;
308     TimeType end_;
309   };
310
311   struct AvgFromLevel {
312     explicit AvgFromLevel(int level) : level_(level) {}
313
314     ValueType operator()(const ContainerType& bucket) const {
315       return bucket.template avg<ValueType>(level_);
316     }
317
318    private:
319     int level_;
320   };
321
322   template <typename ReturnType>
323   struct AvgFromInterval {
324     explicit AvgFromInterval(TimeType start, TimeType end)
325       : start_(start),
326         end_(end) {}
327
328     ReturnType operator()(const ContainerType& bucket) const {
329       return bucket.template avg<ReturnType>(start_, end_);
330     }
331
332    private:
333     TimeType start_;
334     TimeType end_;
335   };
336
337   /*
338    * Special logic for the case of only one unique value registered
339    * (this can happen when clients don't pick good bucket ranges or have
340    * other bugs).  It's a lot easier for clients to track down these issues
341    * if they are getting the correct value.
342    */
343   void maybeHandleSingleUniqueValue(const ValueType& value);
344
345   void computeAvgData(ValueType* total, int64_t* nsamples, int level) const;
346   void computeAvgData(
347       ValueType* total,
348       int64_t* nsamples,
349       TimeType start,
350       TimeType end) const;
351   void computeRateData(ValueType* total, TimeType* elapsed, int level) const;
352   void computeRateData(
353       ValueType* total,
354       TimeType* elapsed,
355       TimeType start,
356       TimeType end) const;
357
358   folly::detail::HistogramBuckets<ValueType, ContainerType> buckets_;
359   bool haveNotSeenValue_;
360   bool singleUniqueValue_;
361   ValueType firstValue_;
362 };
363 }  // folly