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