4 #include "steadystate.h"
6 bool steadystate_enabled = false;
7 unsigned int ss_check_interval = 1000;
9 void steadystate_free(struct thread_data *td)
11 free(td->ss.iops_data);
13 td->ss.iops_data = NULL;
14 td->ss.bw_data = NULL;
17 static void steadystate_alloc(struct thread_data *td)
19 int intervals = td->ss.dur / (ss_check_interval / 1000L);
21 td->ss.bw_data = calloc(intervals, sizeof(uint64_t));
22 td->ss.iops_data = calloc(intervals, sizeof(uint64_t));
24 td->ss.state |= FIO_SS_DATA;
27 void steadystate_setup(void)
29 struct thread_data *prev_td;
32 if (!steadystate_enabled)
36 * if group reporting is enabled, identify the last td
37 * for each group and use it for storing steady state
46 if (!td->o.group_reporting) {
47 steadystate_alloc(td);
51 if (prev_groupid != td->groupid) {
53 steadystate_alloc(prev_td);
54 prev_groupid = td->groupid;
59 if (prev_td && prev_td->o.group_reporting)
60 steadystate_alloc(prev_td);
63 static bool steadystate_slope(uint64_t iops, uint64_t bw,
64 struct thread_data *td)
68 struct steadystate_data *ss = &td->ss;
70 int intervals = ss->dur / (ss_check_interval / 1000L);
72 ss->bw_data[ss->tail] = bw;
73 ss->iops_data[ss->tail] = iops;
75 if (ss->state & FIO_SS_IOPS)
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];
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];
92 ss->sum_xy += i * ss->bw_data[j];
94 ss->state |= FIO_SS_BUFFER_FULL;
95 } else { /* easy to update the sums */
96 ss->sum_y -= ss->oldest_y;
98 ss->sum_xy = ss->sum_xy - ss->sum_y + intervals * new_val;
101 if (ss->state & FIO_SS_IOPS)
102 ss->oldest_y = ss->iops_data[ss->head];
104 ss->oldest_y = ss->bw_data[ss->head];
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.
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);
117 ss->criterion = ss->slope;
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);
125 result = ss->criterion * (ss->criterion < 0.0 ? -1.0 : 1.0);
126 if (result < ss->limit)
130 ss->tail = (ss->tail + 1) % intervals;
131 if (ss->tail <= ss->head)
132 ss->head = (ss->head + 1) % intervals;
137 static bool steadystate_deviation(uint64_t iops, uint64_t bw,
138 struct thread_data *td)
144 struct steadystate_data *ss = &td->ss;
145 int intervals = ss->dur / (ss_check_interval / 1000L);
147 ss->bw_data[ss->tail] = bw;
148 ss->iops_data[ss->tail] = iops;
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];
157 ss->sum_y += ss->bw_data[i];
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];
165 ss->sum_y += ss->bw_data[ss->tail];
168 if (ss->state & FIO_SS_IOPS)
169 ss->oldest_y = ss->iops_data[ss->head];
171 ss->oldest_y = ss->bw_data[ss->head];
173 mean = (double) ss->sum_y / intervals;
176 for (i = 0; i < intervals; i++) {
177 if (ss->state & FIO_SS_IOPS)
178 diff = ss->iops_data[i] - mean;
180 diff = ss->bw_data[i] - mean;
181 ss->deviation = max(ss->deviation, diff * (diff < 0.0 ? -1.0 : 1.0));
184 if (ss->state & FIO_SS_PCT)
185 ss->criterion = 100.0 * ss->deviation / mean;
187 ss->criterion = ss->deviation;
189 dprint(FD_STEADYSTATE, "intervals: %d, sum_y: %llu, mean: %f, max diff: %f, "
190 "objective: %f, limit: %f\n",
192 (unsigned long long) ss->sum_y, mean,
193 ss->deviation, ss->criterion, ss->limit);
195 if (ss->criterion < ss->limit)
199 ss->tail = (ss->tail + 1) % intervals;
200 if (ss->tail == ss->head)
201 ss->head = (ss->head + 1) % intervals;
206 int steadystate_check(void)
208 int ddir, prev_groupid, group_ramp_time_over = 0;
209 unsigned long rate_time;
211 uint64_t group_bw = 0, group_iops = 0;
212 uint64_t td_iops, td_bytes;
217 const bool needs_lock = td_async_processing(td);
218 struct steadystate_data *ss = &td->ss;
220 if (!ss->dur || td->runstate <= TD_SETTING_UP ||
221 td->runstate >= TD_EXITED || !ss->state ||
222 ss->state & FIO_SS_ATTAINED)
227 if (!td->o.group_reporting ||
228 (td->o.group_reporting && td->groupid != prev_groupid)) {
231 group_ramp_time_over = 0;
233 prev_groupid = td->groupid;
235 fio_gettime(&now, NULL);
236 if (ss->ramp_time && !(ss->state & FIO_SS_RAMP_OVER)) {
238 * Begin recording data one check interval after ss->ramp_time
241 if (utime_since(&td->epoch, &now) >= (ss->ramp_time + ss_check_interval * 1000L))
242 ss->state |= FIO_SS_RAMP_OVER;
248 for (ddir = 0; ddir < DDIR_RWDIR_CNT; ddir++) {
249 td_iops += td->io_blocks[ddir];
250 td_bytes += td->io_bytes[ddir];
254 __td_io_u_unlock(td);
256 rate_time = mtime_since(&ss->prev_time, &now);
257 memcpy(&ss->prev_time, &now, sizeof(now));
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;
266 ss->prev_iops = td_iops;
267 ss->prev_bytes = td_bytes;
269 if (td->o.group_reporting && !(ss->state & FIO_SS_DATA))
273 * Don't begin checking criterion until ss->ramp_time is over
274 * for at least one thread in group
276 if (!group_ramp_time_over)
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,
287 if (ss->state & FIO_SS_SLOPE)
288 ret = steadystate_slope(group_iops, group_bw, td);
290 ret = steadystate_deviation(group_iops, group_bw, td);
293 if (td->o.group_reporting) {
295 if (td2->groupid == td->groupid) {
296 td2->ss.state |= FIO_SS_ATTAINED;
297 fio_mark_td_terminate(td2);
301 ss->state |= FIO_SS_ATTAINED;
302 fio_mark_td_terminate(td);
309 int td_steadystate_init(struct thread_data *td)
311 struct steadystate_data *ss = &td->ss;
312 struct thread_options *o = &td->o;
315 memset(ss, 0, sizeof(*ss));
318 steadystate_enabled = true;
319 o->ss_dur /= 1000000L;
321 /* put all steady state info in one place */
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;
327 ss->state = o->ss_state;
328 if (!td->ss.ramp_time)
329 ss->state |= FIO_SS_RAMP_OVER;
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;
336 /* make sure that ss options are consistent within reporting group */
338 if (td2->groupid == td->groupid) {
339 struct steadystate_data *ss2 = &td2->ss;
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");
356 uint64_t steadystate_bw_mean(struct thread_stat *ts)
360 int intervals = ts->ss_dur / (ss_check_interval / 1000L);
365 for (i = 0, sum = 0; i < intervals; i++)
366 sum += ts->ss_bw_data[i];
368 return sum / intervals;
371 uint64_t steadystate_iops_mean(struct thread_stat *ts)
375 int intervals = ts->ss_dur / (ss_check_interval / 1000L);
380 for (i = 0, sum = 0; i < intervals; i++)
381 sum += ts->ss_iops_data[i];
383 return sum / intervals;