steadystate: add free helper
[fio.git] / steadystate.c
1 #include <stdlib.h>
2
3 #include "fio.h"
4 #include "steadystate.h"
5 #include "helper_thread.h"
6
7 bool steadystate_enabled = false;
8
9 void steadystate_free(struct thread_data *td)
10 {
11         free(td->ss.iops_data);
12         free(td->ss.bw_data);
13         td->ss.iops_data = NULL;
14         td->ss.bw_data = NULL;
15 }
16
17 static void steadystate_alloc(struct thread_data *td)
18 {
19         td->ss.bw_data = calloc(td->ss.dur, sizeof(uint64_t));
20         td->ss.iops_data = calloc(td->ss.dur, sizeof(uint64_t));
21
22         td->ss.state |= FIO_SS_DATA;
23 }
24
25 void steadystate_setup(void)
26 {
27         int i, prev_groupid;
28         struct thread_data *td, *prev_td;
29
30         if (!steadystate_enabled)
31                 return;
32
33         /*
34          * if group reporting is enabled, identify the last td
35          * for each group and use it for storing steady state
36          * data
37          */
38         prev_groupid = -1;
39         prev_td = NULL;
40         for_each_td(td, i) {
41                 if (!td->ss.dur)
42                         continue;
43
44                 if (!td->o.group_reporting) {
45                         steadystate_alloc(td);
46                         continue;
47                 }
48
49                 if (prev_groupid != td->groupid) {
50                         if (prev_td != NULL) {
51                                 steadystate_alloc(prev_td);
52                         }
53                         prev_groupid = td->groupid;
54                 }
55                 prev_td = td;
56         }
57
58         if (prev_td != NULL && prev_td->o.group_reporting) {
59                 steadystate_alloc(prev_td);
60         }
61 }
62
63 static bool steadystate_slope(uint64_t iops, uint64_t bw,
64                               struct thread_data *td)
65 {
66         int i, j;
67         double result;
68         struct steadystate_data *ss = &td->ss;
69         uint64_t new_val;
70
71         ss->bw_data[ss->tail] = bw;
72         ss->iops_data[ss->tail] = iops;
73
74         if (ss->state & FIO_SS_IOPS)
75                 new_val = iops;
76         else
77                 new_val = bw;
78
79         if (ss->state & FIO_SS_BUFFER_FULL || ss->tail - ss->head == ss->dur - 1) {
80                 if (!(ss->state & FIO_SS_BUFFER_FULL)) {
81                         /* first time through */
82                         for(i = 0, ss->sum_y = 0; i < ss->dur; i++) {
83                                 if (ss->state & FIO_SS_IOPS)
84                                         ss->sum_y += ss->iops_data[i];
85                                 else
86                                         ss->sum_y += ss->bw_data[i];
87                                 j = (ss->head + i) % ss->dur;
88                                 if (ss->state & FIO_SS_IOPS)
89                                         ss->sum_xy += i * ss->iops_data[j];
90                                 else
91                                         ss->sum_xy += i * ss->bw_data[j];
92                         }
93                         ss->state |= FIO_SS_BUFFER_FULL;
94                 } else {                /* easy to update the sums */
95                         ss->sum_y -= ss->oldest_y;
96                         ss->sum_y += new_val;
97                         ss->sum_xy = ss->sum_xy - ss->sum_y + ss->dur * new_val;
98                 }
99
100                 if (ss->state & FIO_SS_IOPS)
101                         ss->oldest_y = ss->iops_data[ss->head];
102                 else
103                         ss->oldest_y = ss->bw_data[ss->head];
104
105                 /*
106                  * calculate slope as (sum_xy - sum_x * sum_y / n) / (sum_(x^2)
107                  * - (sum_x)^2 / n) This code assumes that all x values are
108                  * equally spaced when they are often off by a few milliseconds.
109                  * This assumption greatly simplifies the calculations.
110                  */
111                 ss->slope = (ss->sum_xy - (double) ss->sum_x * ss->sum_y / ss->dur) /
112                                 (ss->sum_x_sq - (double) ss->sum_x * ss->sum_x / ss->dur);
113                 if (ss->state & FIO_SS_PCT)
114                         ss->criterion = 100.0 * ss->slope / (ss->sum_y / ss->dur);
115                 else
116                         ss->criterion = ss->slope;
117
118                 dprint(FD_STEADYSTATE, "sum_y: %llu, sum_xy: %llu, slope: %f, "
119                                         "criterion: %f, limit: %f\n",
120                                         (unsigned long long) ss->sum_y,
121                                         (unsigned long long) ss->sum_xy,
122                                         ss->slope, ss->criterion, ss->limit);
123
124                 result = ss->criterion * (ss->criterion < 0.0 ? -1.0 : 1.0);
125                 if (result < ss->limit)
126                         return true;
127         }
128
129         ss->tail = (ss->tail + 1) % ss->dur;
130         if (ss->tail <= ss->head)
131                 ss->head = (ss->head + 1) % ss->dur;
132
133         return false;
134 }
135
136 static bool steadystate_deviation(uint64_t iops, uint64_t bw,
137                                   struct thread_data *td)
138 {
139         int i;
140         double diff;
141         double mean;
142
143         struct steadystate_data *ss = &td->ss;
144
145         ss->bw_data[ss->tail] = bw;
146         ss->iops_data[ss->tail] = iops;
147
148         if (ss->state & FIO_SS_BUFFER_FULL || ss->tail - ss->head == ss->dur - 1) {
149                 if (!(ss->state & FIO_SS_BUFFER_FULL)) {
150                         /* first time through */
151                         for(i = 0, ss->sum_y = 0; i < ss->dur; i++)
152                                 if (ss->state & FIO_SS_IOPS)
153                                         ss->sum_y += ss->iops_data[i];
154                                 else
155                                         ss->sum_y += ss->bw_data[i];
156                         ss->state |= FIO_SS_BUFFER_FULL;
157                 } else {                /* easy to update the sum */
158                         ss->sum_y -= ss->oldest_y;
159                         if (ss->state & FIO_SS_IOPS)
160                                 ss->sum_y += ss->iops_data[ss->tail];
161                         else
162                                 ss->sum_y += ss->bw_data[ss->tail];
163                 }
164
165                 if (ss->state & FIO_SS_IOPS)
166                         ss->oldest_y = ss->iops_data[ss->head];
167                 else
168                         ss->oldest_y = ss->bw_data[ss->head];
169
170                 mean = (double) ss->sum_y / ss->dur;
171                 ss->deviation = 0.0;
172
173                 for (i = 0; i < ss->dur; i++) {
174                         if (ss->state & FIO_SS_IOPS)
175                                 diff = ss->iops_data[i] - mean;
176                         else
177                                 diff = ss->bw_data[i] - mean;
178                         ss->deviation = max(ss->deviation, diff * (diff < 0.0 ? -1.0 : 1.0));
179                 }
180
181                 if (ss->state & FIO_SS_PCT)
182                         ss->criterion = 100.0 * ss->deviation / mean;
183                 else
184                         ss->criterion = ss->deviation;
185
186                 dprint(FD_STEADYSTATE, "sum_y: %llu, mean: %f, max diff: %f, "
187                                         "objective: %f, limit: %f\n",
188                                         (unsigned long long) ss->sum_y, mean,
189                                         ss->deviation, ss->criterion, ss->limit);
190
191                 if (ss->criterion < ss->limit)
192                         return true;
193         }
194
195         ss->tail = (ss->tail + 1) % ss->dur;
196         if (ss->tail <= ss->head)
197                 ss->head = (ss->head + 1) % ss->dur;
198
199         return false;
200 }
201
202 void steadystate_check(void)
203 {
204         int i, j, ddir, prev_groupid, group_ramp_time_over = 0;
205         unsigned long rate_time;
206         struct thread_data *td, *td2;
207         struct timespec now;
208         uint64_t group_bw = 0, group_iops = 0;
209         uint64_t td_iops, td_bytes;
210         bool ret;
211
212         prev_groupid = -1;
213         for_each_td(td, i) {
214                 struct steadystate_data *ss = &td->ss;
215
216                 if (!ss->dur || td->runstate <= TD_SETTING_UP ||
217                     td->runstate >= TD_EXITED || !ss->state ||
218                     ss->state & FIO_SS_ATTAINED)
219                         continue;
220
221                 td_iops = 0;
222                 td_bytes = 0;
223                 if (!td->o.group_reporting ||
224                     (td->o.group_reporting && td->groupid != prev_groupid)) {
225                         group_bw = 0;
226                         group_iops = 0;
227                         group_ramp_time_over = 0;
228                 }
229                 prev_groupid = td->groupid;
230
231                 fio_gettime(&now, NULL);
232                 if (ss->ramp_time && !(ss->state & FIO_SS_RAMP_OVER)) {
233                         /*
234                          * Begin recording data one second after ss->ramp_time
235                          * has elapsed
236                          */
237                         if (utime_since(&td->epoch, &now) >= (ss->ramp_time + 1000000L))
238                                 ss->state |= FIO_SS_RAMP_OVER;
239                 }
240
241                 td_io_u_lock(td);
242                 for (ddir = 0; ddir < DDIR_RWDIR_CNT; ddir++) {
243                         td_iops += td->io_blocks[ddir];
244                         td_bytes += td->io_bytes[ddir];
245                 }
246                 td_io_u_unlock(td);
247
248                 rate_time = mtime_since(&ss->prev_time, &now);
249                 memcpy(&ss->prev_time, &now, sizeof(now));
250
251                 /*
252                  * Begin monitoring when job starts but don't actually use
253                  * data in checking stopping criterion until ss->ramp_time is
254                  * over. This ensures that we will have a sane value in
255                  * prev_iops/bw the first time through after ss->ramp_time
256                  * is done.
257                  */
258                 if (ss->state & FIO_SS_RAMP_OVER) {
259                         group_bw += 1000 * (td_bytes - ss->prev_bytes) / rate_time;
260                         group_iops += 1000 * (td_iops - ss->prev_iops) / rate_time;
261                         ++group_ramp_time_over;
262                 }
263                 ss->prev_iops = td_iops;
264                 ss->prev_bytes = td_bytes;
265
266                 if (td->o.group_reporting && !(ss->state & FIO_SS_DATA))
267                         continue;
268
269                 /*
270                  * Don't begin checking criterion until ss->ramp_time is over
271                  * for at least one thread in group
272                  */
273                 if (!group_ramp_time_over)
274                         continue;
275
276                 dprint(FD_STEADYSTATE, "steadystate_check() thread: %d, "
277                                         "groupid: %u, rate_msec: %ld, "
278                                         "iops: %llu, bw: %llu, head: %d, tail: %d\n",
279                                         i, td->groupid, rate_time,
280                                         (unsigned long long) group_iops,
281                                         (unsigned long long) group_bw,
282                                         ss->head, ss->tail);
283
284                 if (ss->state & FIO_SS_SLOPE)
285                         ret = steadystate_slope(group_iops, group_bw, td);
286                 else
287                         ret = steadystate_deviation(group_iops, group_bw, td);
288
289                 if (ret) {
290                         if (td->o.group_reporting) {
291                                 for_each_td(td2, j) {
292                                         if (td2->groupid == td->groupid) {
293                                                 td2->ss.state |= FIO_SS_ATTAINED;
294                                                 fio_mark_td_terminate(td2);
295                                         }
296                                 }
297                         } else {
298                                 ss->state |= FIO_SS_ATTAINED;
299                                 fio_mark_td_terminate(td);
300                         }
301                 }
302         }
303 }
304
305 int td_steadystate_init(struct thread_data *td)
306 {
307         struct steadystate_data *ss = &td->ss;
308         struct thread_options *o = &td->o;
309         struct thread_data *td2;
310         int j;
311
312         memset(ss, 0, sizeof(*ss));
313
314         if (o->ss_dur) {
315                 steadystate_enabled = true;
316                 o->ss_dur /= 1000000L;
317
318                 /* put all steady state info in one place */
319                 ss->dur = o->ss_dur;
320                 ss->limit = o->ss_limit.u.f;
321                 ss->ramp_time = o->ss_ramp_time;
322
323                 ss->state = o->ss_state;
324                 if (!td->ss.ramp_time)
325                         ss->state |= FIO_SS_RAMP_OVER;
326
327                 ss->sum_x = o->ss_dur * (o->ss_dur - 1) / 2;
328                 ss->sum_x_sq = (o->ss_dur - 1) * (o->ss_dur) * (2*o->ss_dur - 1) / 6;
329         }
330
331         /* make sure that ss options are consistent within reporting group */
332         for_each_td(td2, j) {
333                 if (td2->groupid == td->groupid) {
334                         struct steadystate_data *ss2 = &td2->ss;
335
336                         if (ss2->dur != ss->dur ||
337                             ss2->limit != ss->limit ||
338                             ss2->ramp_time != ss->ramp_time ||
339                             ss2->state != ss->state ||
340                             ss2->sum_x != ss->sum_x ||
341                             ss2->sum_x_sq != ss->sum_x_sq) {
342                                 td_verror(td, EINVAL, "job rejected: steadystate options must be consistent within reporting groups");
343                                 return 1;
344                         }
345                 }
346         }
347
348         return 0;
349 }
350
351 uint64_t steadystate_bw_mean(struct thread_stat *ts)
352 {
353         int i;
354         uint64_t sum;
355
356         for (i = 0, sum = 0; i < ts->ss_dur; i++)
357                 sum += ts->ss_bw_data[i];
358
359         return sum / ts->ss_dur;
360 }
361
362 uint64_t steadystate_iops_mean(struct thread_stat *ts)
363 {
364         int i;
365         uint64_t sum;
366
367         for (i = 0, sum = 0; i < ts->ss_dur; i++)
368                 sum += ts->ss_iops_data[i];
369
370         return sum / ts->ss_dur;
371 }