#include "lib/pow2.h"
#include "lib/output_buffer.h"
#include "helper_thread.h"
+#include "smalloc.h"
+
+#define LOG_MSEC_SLACK 10
struct fio_mutex *stat_mutex;
}
}
+ /* steady state detection; move this behind json+? */
+ if (ts->ss) {
+ struct json_object *data;
+ struct json_array *iops, *bw;
+ struct steadystate_data *ss = ts->ss;
+ unsigned long long sum_iops, sum_bw;
+ double mean_iops, mean_bw;
+ int i, j, k;
+ char ss_buf[64];
+
+ snprintf(ss_buf, sizeof(ss_buf), "%s%s:%f%s",
+ ss->check_iops ? "iops" : "bw",
+ ss->check_slope ? "_slope" : "",
+ (float) ss->limit,
+ ss->pct ? "%" : "");
+
+ tmp = json_create_object();
+ json_object_add_value_object(root, "steadystate", tmp);
+ json_object_add_value_string(tmp, "ss", ss_buf);
+ json_object_add_value_int(tmp, "duration", (int)ss->dur);
+ json_object_add_value_int(tmp, "steadystate_ramptime", ss->ramp_time / 1000000L);
+ json_object_add_value_int(tmp, "attained", ss->attained);
+
+ snprintf(ss_buf, sizeof(ss_buf), "%f%s", (float) ss->criterion, ss->pct ? "%" : "");
+ json_object_add_value_string(tmp, "criterion", ss_buf);
+ json_object_add_value_float(tmp, "max_deviation", ss->deviation);
+ json_object_add_value_float(tmp, "slope", ss->slope);
+
+ data = json_create_object();
+ json_object_add_value_object(tmp, "data", data);
+ bw = json_create_array();
+ iops = json_create_array();
+
+ /*
+ ** if ss was attained or the buffer is not full,
+ ** ss->head points to the first element in the list.
+ ** otherwise it actually points to the second element
+ ** in the list
+ */
+ if (ss->attained || ss->sum_y == 0)
+ j = ss->head;
+ else
+ j = ss->head == 0 ? ss->dur - 1 : ss->head - 1;
+ for (i = 0, sum_iops = 0, sum_bw = 0; i < ss->dur; i++) {
+ k = (j + i) % ss->dur;
+ sum_bw += ss->bw_data[k];
+ sum_iops += ss->iops_data[k];
+ json_array_add_value_int(bw, ss->bw_data[k]);
+ json_array_add_value_int(iops, ss->iops_data[k]);
+ }
+ mean_bw = (double) sum_bw / ss->dur;
+ mean_iops = (double) sum_iops / ss->dur;
+ json_object_add_value_float(data, "bw_mean", mean_bw);
+ json_object_add_value_float(data, "iops_mean", mean_iops);
+ json_object_add_value_array(data, "iops", iops);
+ json_object_add_value_array(data, "bw", bw);
+ }
+
return root;
}
ts->block_infos[k] = td->ts.block_infos[k];
sum_thread_stats(ts, &td->ts, idx == 1);
+
+ if (td->o.ss_dur)
+ ts->ss = &td->ss;
+ else
+ ts->ss = NULL;
}
for (i = 0; i < nr_ts; i++) {
new_size = new_samples * log_entry_sz(iolog);
- cur_log = malloc(sizeof(*cur_log));
+ cur_log = smalloc(sizeof(*cur_log));
if (cur_log) {
INIT_FLIST_HEAD(&cur_log->list);
cur_log->log = malloc(new_size);
iolog->cur_log_max = new_samples;
return cur_log;
}
- free(cur_log);
+ sfree(cur_log);
}
return NULL;
dst = get_sample(iolog, cur_log, i);
memcpy(dst, src, log_entry_sz(iolog));
}
+ cur_log->nr_samples = iolog->pending->nr_samples;
iolog->pending->nr_samples = 0;
return cur_log;
{
regrow_log(td->slat_log);
regrow_log(td->clat_log);
+ regrow_log(td->clat_hist_log);
regrow_log(td->lat_log);
regrow_log(td->bw_log);
regrow_log(td->iops_log);
return cur_log;
/*
- * Out of space. If we're in IO offload mode, add a new log chunk
- * inline. If we're doing inline submissions, flag 'td' as needing
- * a log regrow and we'll take care of it on the submission side.
+ * Out of space. If we're in IO offload mode, or we're not doing
+ * per unit logging (hence logging happens outside of the IO thread
+ * as well), add a new log chunk inline. If we're doing inline
+ * submissions, flag 'td' as needing a log regrow and we'll take
+ * care of it on the submission side.
*/
- if (iolog->td->o.io_submit_mode == IO_MODE_OFFLOAD)
+ if (iolog->td->o.io_submit_mode == IO_MODE_OFFLOAD ||
+ !per_unit_log(iolog))
return regrow_log(iolog);
iolog->td->flags |= TD_F_REGROW_LOGS;
__add_stat_to_log(iolog, ddir, elapsed, log_max);
}
-static void add_log_sample(struct thread_data *td, struct io_log *iolog,
+static long add_log_sample(struct thread_data *td, struct io_log *iolog,
unsigned long val, enum fio_ddir ddir,
unsigned int bs, uint64_t offset)
{
unsigned long elapsed, this_window;
if (!ddir_rw(ddir))
- return;
+ return 0;
elapsed = mtime_since_now(&td->epoch);
*/
if (!iolog->avg_msec) {
__add_log_sample(iolog, val, ddir, bs, elapsed, offset);
- return;
+ return 0;
}
/*
* need to do.
*/
this_window = elapsed - iolog->avg_last;
- if (this_window < iolog->avg_msec)
- return;
+ if (elapsed < iolog->avg_last)
+ return iolog->avg_last - elapsed;
+ else if (this_window < iolog->avg_msec) {
+ int diff = iolog->avg_msec - this_window;
+
+ if (inline_log(iolog) || diff > LOG_MSEC_SLACK)
+ return diff;
+ }
_add_stat_to_log(iolog, elapsed, td->o.log_max != 0);
- iolog->avg_last = elapsed;
+ iolog->avg_last = elapsed - (this_window - iolog->avg_msec);
+ return iolog->avg_msec;
}
void finalize_logs(struct thread_data *td, bool unit_logs)
void add_clat_sample(struct thread_data *td, enum fio_ddir ddir,
unsigned long usec, unsigned int bs, uint64_t offset)
{
+ unsigned long elapsed, this_window;
struct thread_stat *ts = &td->ts;
+ struct io_log *iolog = td->clat_hist_log;
td_io_u_lock(td);
if (ts->clat_percentiles)
add_clat_percentile_sample(ts, usec, ddir);
+ if (iolog && iolog->hist_msec) {
+ struct io_hist *hw = &iolog->hist_window[ddir];
+
+ hw->samples++;
+ elapsed = mtime_since_now(&td->epoch);
+ if (!hw->hist_last)
+ hw->hist_last = elapsed;
+ this_window = elapsed - hw->hist_last;
+
+ if (this_window >= iolog->hist_msec) {
+ unsigned int *io_u_plat;
+ unsigned int *dst;
+
+ /*
+ * Make a byte-for-byte copy of the latency histogram
+ * stored in td->ts.io_u_plat[ddir], recording it in a
+ * log sample. Note that the matching call to free() is
+ * located in iolog.c after printing this sample to the
+ * log file.
+ */
+ io_u_plat = (unsigned int *) td->ts.io_u_plat[ddir];
+ dst = malloc(FIO_IO_U_PLAT_NR * sizeof(unsigned int));
+ memcpy(dst, io_u_plat,
+ FIO_IO_U_PLAT_NR * sizeof(unsigned int));
+ __add_log_sample(iolog, (unsigned long )dst, ddir, bs,
+ elapsed, offset);
+
+ /*
+ * Update the last time we recorded as being now, minus
+ * any drift in time we encountered before actually
+ * making the record.
+ */
+ hw->hist_last = elapsed - (this_window - iolog->hist_msec);
+ hw->samples = 0;
+ }
+ }
+
td_io_u_unlock(td);
}
struct thread_stat *ts = &td->ts;
unsigned long spent, rate;
enum fio_ddir ddir;
+ unsigned int next, next_log;
+
+ next_log = td->o.bw_avg_time;
spent = mtime_since(&td->bw_sample_time, t);
if (spent < td->o.bw_avg_time &&
- td->o.bw_avg_time - spent >= 10)
+ td->o.bw_avg_time - spent >= LOG_MSEC_SLACK)
return td->o.bw_avg_time - spent;
td_io_u_lock(td);
if (td->o.min_bs[ddir] == td->o.max_bs[ddir])
bs = td->o.min_bs[ddir];
- add_log_sample(td, td->bw_log, rate, ddir, bs, 0);
+ next = add_log_sample(td, td->bw_log, rate, ddir, bs, 0);
+ next_log = min(next_log, next);
}
td->stat_io_bytes[ddir] = td->this_io_bytes[ddir];
td_io_u_unlock(td);
if (spent <= td->o.bw_avg_time)
- return td->o.bw_avg_time;
+ return min(next_log, td->o.bw_avg_time);
- return td->o.bw_avg_time - (1 + spent - td->o.bw_avg_time);
+ next = td->o.bw_avg_time - (1 + spent - td->o.bw_avg_time);
+ return min(next, next_log);
}
void add_iops_sample(struct thread_data *td, struct io_u *io_u,
struct thread_stat *ts = &td->ts;
unsigned long spent, iops;
enum fio_ddir ddir;
+ unsigned int next, next_log;
+
+ next_log = td->o.iops_avg_time;
spent = mtime_since(&td->iops_sample_time, t);
if (spent < td->o.iops_avg_time &&
- td->o.iops_avg_time - spent >= 10)
+ td->o.iops_avg_time - spent >= LOG_MSEC_SLACK)
return td->o.iops_avg_time - spent;
td_io_u_lock(td);
if (td->o.min_bs[ddir] == td->o.max_bs[ddir])
bs = td->o.min_bs[ddir];
- add_log_sample(td, td->iops_log, iops, ddir, bs, 0);
+ next = add_log_sample(td, td->iops_log, iops, ddir, bs, 0);
+ next_log = min(next_log, next);
}
td->stat_io_blocks[ddir] = td->this_io_blocks[ddir];
td_io_u_unlock(td);
if (spent <= td->o.iops_avg_time)
- return td->o.iops_avg_time;
+ return min(next_log, td->o.iops_avg_time);
- return td->o.iops_avg_time - (1 + spent - td->o.iops_avg_time);
+ next = td->o.iops_avg_time - (1 + spent - td->o.iops_avg_time);
+ return min(next, next_log);
}
/*
fio_gettime(&now, NULL);
for_each_td(td, i) {
- if (!in_ramp_time(td) ||
+ if (in_ramp_time(td) ||
!(td->runstate == TD_RUNNING || td->runstate == TD_VERIFYING)) {
next = min(td->o.iops_avg_time, td->o.bw_avg_time);
continue;