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 */
57static 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 */
72static 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 */
86static 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 */
97void 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 */
126static 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 */
161void 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 */
188uint64_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 */
201uint64_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 */
214uint64_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 */
225uint64_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