849cc922ce47b4895a869bc60f1e24dc3b8d29b0
[folly.git] / folly / stats / MultiLevelTimeSeries.h
1 /*
2  * Copyright 2013 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 #ifndef FOLLY_STATS_MULTILEVELTIMESERIES_H_
18 #define FOLLY_STATS_MULTILEVELTIMESERIES_H_
19
20 #include <chrono>
21 #include <string>
22 #include <vector>
23
24 #include <glog/logging.h>
25 #include "folly/stats/BucketedTimeSeries.h"
26
27 namespace folly {
28
29 /*
30  * This class represents a timeseries which keeps several levels of data
31  * granularity (similar in principle to the loads reported by the UNIX
32  * 'uptime' command).  It uses several instances (one per level) of
33  * BucketedTimeSeries as the underlying storage.
34  *
35  * This can easily be used to track sums (and thus rates or averages) over
36  * several predetermined time periods, as well as all-time sums.  For example,
37  * you would use to it to track query rate or response speed over the last
38  * 5, 15, 30, and 60 minutes.
39  *
40  * The MultiLevelTimeSeries takes a list of level durations as an input; the
41  * durations must be strictly increasing.  Furthermore a special level can be
42  * provided with a duration of '0' -- this will be an "all-time" level.  If
43  * an all-time level is provided, it MUST be the last level present.
44  *
45  * The class assumes that time advances forward --  you can't retroactively add
46  * values for events in the past -- the 'now' argument is provided for better
47  * efficiency and ease of unittesting.
48  *
49  * The class is not thread-safe -- use your own synchronization!
50  */
51 template <typename VT, typename TT=std::chrono::seconds>
52 class MultiLevelTimeSeries {
53  public:
54   typedef VT ValueType;
55   typedef TT TimeType;
56   typedef folly::BucketedTimeSeries<ValueType, TimeType> Level;
57
58   /*
59    * Create a new MultiLevelTimeSeries.
60    *
61    * This creates a new MultiLevelTimeSeries that tracks time series data at the
62    * specified time durations (level). The time series data tracked at each
63    * level is then further divided by numBuckets for memory efficiency.
64    *
65    * The durations must be strictly increasing. Furthermore a special level can
66    * be provided with a duration of '0' -- this will be an "all-time" level. If
67    * an all-time level is provided, it MUST be the last level present.
68    */
69   MultiLevelTimeSeries(size_t numBuckets,
70                        size_t numLevels,
71                        const TimeType levelDurations[]);
72
73   /*
74    * Return the number of buckets used to track time series at each level.
75    */
76   size_t numBuckets() const { return numBuckets_; }
77
78   /*
79    * Return the number of levels tracked by MultiLevelTimeSeries.
80    */
81   size_t numLevels() const { return levels_.size(); }
82
83   /*
84    * Get the BucketedTimeSeries backing the specified level.
85    *
86    * Note: you should generally call update() or flush() before accessing the
87    * data. Otherwise you may be reading stale data if update() or flush() has
88    * not been called recently.
89    */
90   const Level& getLevel(int level) const {
91     CHECK(level >= 0);
92     CHECK_LT(level, levels_.size());
93     return levels_[level];
94   }
95
96   /*
97    * Get the highest granularity level that is still large enough to contain
98    * data going back to the specified start time.
99    *
100    * Note: you should generally call update() or flush() before accessing the
101    * data. Otherwise you may be reading stale data if update() or flush() has
102    * not been called recently.
103    */
104   const Level& getLevel(TimeType start) const {
105     for (const auto& level : levels_) {
106       if (level.isAllTime()) {
107         return level;
108       }
109       // Note that we use duration() here rather than elapsed().
110       // If duration is large enough to contain the start time then this level
111       // is good enough, even if elapsed() indicates that no data was recorded
112       // before the specified start time.
113       if (level.getLatestTime() - level.duration() <= start) {
114         return level;
115       }
116     }
117     // We should always have an all-time level, so this is never reached.
118     LOG(FATAL) << "No level of timeseries covers internval"
119                << " from " << start.count() << " to now";
120     return levels_.back();
121   }
122
123   /*
124    * Return the sum of all the data points currently tracked at this level.
125    *
126    * Note: you should generally call update() or flush() before accessing the
127    * data. Otherwise you may be reading stale data if update() or flush() has
128    * not been called recently.
129    */
130   ValueType sum(int level) const {
131     return getLevel(level).sum();
132   }
133
134   /*
135    * Return the average (sum / count) of all the data points currently tracked
136    * at this level.
137    *
138    * The return type may be specified to control whether floating-point or
139    * integer division should be performed.
140    *
141    * Note: you should generally call update() or flush() before accessing the
142    * data. Otherwise you may be reading stale data if update() or flush() has
143    * not been called recently.
144    */
145   template <typename ReturnType=double>
146   ReturnType avg(int level) const {
147     return getLevel(level).template avg<ReturnType>();
148   }
149
150   /*
151    * Return the rate (sum divided by elaspsed time) of the all data points
152    * currently tracked at this level.
153    *
154    * Note: you should generally call update() or flush() before accessing the
155    * data. Otherwise you may be reading stale data if update() or flush() has
156    * not been called recently.
157    */
158   template <typename ReturnType=double, typename Interval=TimeType>
159   ValueType rate(int level) const {
160     return getLevel(level).template rate<ReturnType, Interval>();
161   }
162
163   /*
164    * Return the number of data points currently tracked at this level.
165    *
166    * Note: you should generally call update() or flush() before accessing the
167    * data. Otherwise you may be reading stale data if update() or flush() has
168    * not been called recently.
169    */
170   int64_t count(int level) const {
171     return getLevel(level).count();
172   }
173
174   /*
175    * Return the count divided by the elapsed time tracked at this level.
176    *
177    * Note: you should generally call update() or flush() before accessing the
178    * data. Otherwise you may be reading stale data if update() or flush() has
179    * not been called recently.
180    */
181   template <typename ReturnType=double, typename Interval=TimeType>
182   ReturnType countRate(int level) const {
183     return getLevel(level).template countRate<ReturnType, Interval>();
184   }
185
186   /*
187    * Estimate the sum of the data points that occurred in the specified time
188    * period at this level.
189    *
190    * The range queried is [start, end).
191    * That is, start is inclusive, and end is exclusive.
192    *
193    * Note that data outside of the timeseries duration will no longer be
194    * available for use in the estimation.  Specifying a start time earlier than
195    * getEarliestTime() will not have much effect, since only data points after
196    * that point in time will be counted.
197    *
198    * Note that the value returned is an estimate, and may not be precise.
199    *
200    * Note: you should generally call update() or flush() before accessing the
201    * data. Otherwise you may be reading stale data if update() or flush() has
202    * not been called recently.
203    */
204   ValueType sum(TimeType start, TimeType end) const {
205     return getLevel(start).sum(start, end);
206   }
207
208   /*
209    * Estimate the average value during the specified time period.
210    *
211    * The same caveats documented in the sum(TimeType start, TimeType end)
212    * comments apply here as well.
213    *
214    * Note: you should generally call update() or flush() before accessing the
215    * data. Otherwise you may be reading stale data if update() or flush() has
216    * not been called recently.
217    */
218   template <typename ReturnType=double>
219   ReturnType avg(TimeType start, TimeType end) const {
220     return getLevel(start).template avg<ReturnType>(start, end);
221   }
222
223   /*
224    * Estimate the rate during the specified time period.
225    *
226    * The same caveats documented in the sum(TimeType start, TimeType end)
227    * comments apply here as well.
228    *
229    * Note: you should generally call update() or flush() before accessing the
230    * data. Otherwise you may be reading stale data if update() or flush() has
231    * not been called recently.
232    */
233   template <typename ReturnType=double>
234   ReturnType rate(TimeType start, TimeType end) const {
235     return getLevel(start).template rate<ReturnType>(start, end);
236   }
237
238   /*
239    * Estimate the count during the specified time period.
240    *
241    * The same caveats documented in the sum(TimeType start, TimeType end)
242    * comments apply here as well.
243    *
244    * Note: you should generally call update() or flush() before accessing the
245    * data. Otherwise you may be reading stale data if update() or flush() has
246    * not been called recently.
247    */
248   int64_t count(TimeType start, TimeType end) const {
249     return getLevel(start).count(start, end);
250   }
251
252   /*
253    * Adds the value 'val' at time 'now' to all levels.
254    *
255    * Data points added at the same time point is cached internally here and not
256    * propagated to the underlying levels until either flush() is called or when
257    * update from a different time comes.
258    *
259    * This function expects time to always move forwards: it cannot be used to
260    * add historical data points that have occurred in the past.  If now is
261    * older than the another timestamp that has already been passed to
262    * addValue() or update(), now will be ignored and the latest timestamp will
263    * be used.
264    */
265   void addValue(TimeType now, const ValueType& val);
266
267   /*
268    * Adds the value 'val' at time 'now' to all levels.
269    */
270   void addValue(TimeType now, const ValueType& val, int64_t times);
271
272   /*
273    * Adds the value 'val' at time 'now' to all levels as the sum of 'nsamples'
274    * samples.
275    */
276   void addValueAggregated(TimeType now, const ValueType& sum, int64_t nsamples);
277
278   /*
279    * Update all the levels to the specified time, doing all the necessary
280    * work to rotate the buckets and remove any stale data points.
281    *
282    * When reading data from the timeseries, you should make sure to manually
283    * call update() before accessing the data. Otherwise you may be reading
284    * stale data if update() has not been called recently.
285    */
286   void update(TimeType now);
287
288   /*
289    * Reset all the timeseries to an empty state as if no data points have ever
290    * been added to it.
291    */
292   void clear();
293
294   /*
295    * Flush all cached updates.
296    */
297   void flush();
298
299  private:
300   size_t numBuckets_;
301   std::vector<Level> levels_;
302
303   // Updates within the same time interval are cached
304   // They are flushed out when updates from a different time comes,
305   // or flush() is called.
306   TimeType cachedTime_;
307   ValueType cachedSum_;
308   int cachedCount_;
309 };
310
311 } // folly
312
313 #endif // FOLLY_STATS_MULTILEVELTIMESERIES_H_