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 | |