xref: /qemu/util/timed-average.c (revision 6402cbbb)
1 /*
2  * QEMU timed average computation
3  *
4  * Copyright (C) Nodalink, EURL. 2014
5  * Copyright (C) Igalia, S.L. 2015
6  *
7  * Authors:
8  *   Benoît Canet <benoit.canet@nodalink.com>
9  *   Alberto Garcia <berto@igalia.com>
10  *
11  * This program is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation, either version 2 of the License, or
14  * (at your option) version 3 or any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
23  */
24 
25 #include "qemu/osdep.h"
26 
27 #include "qemu/timed-average.h"
28 
29 /* This module computes an average of a set of values within a time
30  * window.
31  *
32  * Algorithm:
33  *
34  * - Create two windows with a certain expiration period, and
35  *   offsetted by period / 2.
36  * - Each time you want to account a new value, do it in both windows.
37  * - The minimum / maximum / average values are always returned from
38  *   the oldest window.
39  *
40  * Example:
41  *
42  *        t=0          |t=0.5           |t=1          |t=1.5            |t=2
43  *        wnd0: [0,0.5)|wnd0: [0.5,1.5) |             |wnd0: [1.5,2.5)  |
44  *        wnd1: [0,1)  |                |wnd1: [1,2)  |                 |
45  *
46  * Values are returned from:
47  *
48  *        wnd0---------|wnd1------------|wnd0---------|wnd1-------------|
49  */
50 
51 /* Update the expiration of a time window
52  *
53  * @w:      the window used
54  * @now:    the current time in nanoseconds
55  * @period: the expiration period in nanoseconds
56  */
57 static void update_expiration(TimedAverageWindow *w, int64_t now,
58                               int64_t period)
59 {
60     /* time elapsed since the last theoretical expiration */
61     int64_t elapsed = (now - w->expiration) % period;
62     /* time remaininging until the next expiration */
63     int64_t remaining = period - elapsed;
64     /* compute expiration */
65     w->expiration = now + remaining;
66 }
67 
68 /* Reset a window
69  *
70  * @w: the window to reset
71  */
72 static void window_reset(TimedAverageWindow *w)
73 {
74     w->min = UINT64_MAX;
75     w->max = 0;
76     w->sum = 0;
77     w->count = 0;
78 }
79 
80 /* Get the current window (that is, the one with the earliest
81  * expiration time).
82  *
83  * @ta:  the TimedAverage structure
84  * @ret: a pointer to the current window
85  */
86 static TimedAverageWindow *current_window(TimedAverage *ta)
87 {
88      return &ta->windows[ta->current];
89 }
90 
91 /* Initialize a TimedAverage structure
92  *
93  * @ta:         the TimedAverage structure
94  * @clock_type: the type of clock to use
95  * @period:     the time window period in nanoseconds
96  */
97 void timed_average_init(TimedAverage *ta, QEMUClockType clock_type,
98                         uint64_t period)
99 {
100     int64_t now = qemu_clock_get_ns(clock_type);
101 
102     /* Returned values are from the oldest window, so they belong to
103      * the interval [ta->period/2,ta->period). By adjusting the
104      * requested period by 4/3, we guarantee that they're in the
105      * interval [2/3 period,4/3 period), closer to the requested
106      * period on average */
107     ta->period = (uint64_t) period * 4 / 3;
108     ta->clock_type = clock_type;
109     ta->current = 0;
110 
111     window_reset(&ta->windows[0]);
112     window_reset(&ta->windows[1]);
113 
114     /* Both windows are offsetted by half a period */
115     ta->windows[0].expiration = now + ta->period / 2;
116     ta->windows[1].expiration = now + ta->period;
117 }
118 
119 /* Check if the time windows have expired, updating their counters and
120  * expiration time if that's the case.
121  *
122  * @ta: the TimedAverage structure
123  * @elapsed: if non-NULL, the elapsed time (in ns) within the current
124  *           window will be stored here
125  */
126 static void check_expirations(TimedAverage *ta, uint64_t *elapsed)
127 {
128     int64_t now = qemu_clock_get_ns(ta->clock_type);
129     int i;
130 
131     assert(ta->period != 0);
132 
133     /* Check if the windows have expired */
134     for (i = 0; i < 2; i++) {
135         TimedAverageWindow *w = &ta->windows[i];
136         if (w->expiration <= now) {
137             window_reset(w);
138             update_expiration(w, now, ta->period);
139         }
140     }
141 
142     /* Make ta->current point to the oldest window */
143     if (ta->windows[0].expiration < ta->windows[1].expiration) {
144         ta->current = 0;
145     } else {
146         ta->current = 1;
147     }
148 
149     /* Calculate the elapsed time within the current window */
150     if (elapsed) {
151         int64_t remaining = ta->windows[ta->current].expiration - now;
152         *elapsed = ta->period - remaining;
153     }
154 }
155 
156 /* Account a value
157  *
158  * @ta:    the TimedAverage structure
159  * @value: the value to account
160  */
161 void timed_average_account(TimedAverage *ta, uint64_t value)
162 {
163     int i;
164     check_expirations(ta, NULL);
165 
166     /* Do the accounting in both windows at the same time */
167     for (i = 0; i < 2; i++) {
168         TimedAverageWindow *w = &ta->windows[i];
169 
170         w->sum += value;
171         w->count++;
172 
173         if (value < w->min) {
174             w->min = value;
175         }
176 
177         if (value > w->max) {
178             w->max = value;
179         }
180     }
181 }
182 
183 /* Get the minimum value
184  *
185  * @ta:  the TimedAverage structure
186  * @ret: the minimum value
187  */
188 uint64_t timed_average_min(TimedAverage *ta)
189 {
190     TimedAverageWindow *w;
191     check_expirations(ta, NULL);
192     w = current_window(ta);
193     return w->min < UINT64_MAX ? w->min : 0;
194 }
195 
196 /* Get the average value
197  *
198  * @ta:  the TimedAverage structure
199  * @ret: the average value
200  */
201 uint64_t timed_average_avg(TimedAverage *ta)
202 {
203     TimedAverageWindow *w;
204     check_expirations(ta, NULL);
205     w = current_window(ta);
206     return w->count > 0 ? w->sum / w->count : 0;
207 }
208 
209 /* Get the maximum value
210  *
211  * @ta:  the TimedAverage structure
212  * @ret: the maximum value
213  */
214 uint64_t timed_average_max(TimedAverage *ta)
215 {
216     check_expirations(ta, NULL);
217     return current_window(ta)->max;
218 }
219 
220 /* Get the sum of all accounted values
221  * @ta:      the TimedAverage structure
222  * @elapsed: if non-NULL, the elapsed time (in ns) will be stored here
223  * @ret:     the sum of all accounted values
224  */
225 uint64_t timed_average_sum(TimedAverage *ta, uint64_t *elapsed)
226 {
227     TimedAverageWindow *w;
228     check_expirations(ta, elapsed);
229     w = current_window(ta);
230     return w->sum;
231 }
232