Hi All, Attached is a patch for adding a new significant figures parameter to the normal output format. Somes when using large I/O sizes, the hardcoded 4 sig figs does not truly represent the throughput, due to rounding/truncating. So this parameter allows the value to be user settable, without having to inspect json outputs (intended for machine readable, not human readable). With this patch, the default behavior is exactly as it is today, meaning the default sig_figs is 4. If a user enables --sig_figs=5, then one extra digit will be displayed. Please let me know of any bugs/issues. Thanks. Regards, Jeff diff --git a/HOWTO b/HOWTO index dce96bc..811e6ca 100644 --- a/HOWTO +++ b/HOWTO @@ -2909,6 +2909,13 @@ Measurements and reporting completion latency below which 99.5% and 99.9% of the observed latencies fell, respectively. +.. option:: sig_figs=int + + If using :option:`--output-format` of `normal`, set the significant figures + to this value. Higher values will yield more precise IOPS and throughput + units, while lower values will round. Requires a minimum value of 1 and a + maximum value of 10. Defaults to 4. + Error handling ~~~~~~~~~~~~~~ diff --git a/cconv.c b/cconv.c index dc3c4e6..1a41dc3 100644 --- a/cconv.c +++ b/cconv.c @@ -270,6 +270,7 @@ void convert_thread_options_to_cpu(struct thread_options *o, o->clat_percentiles = le32_to_cpu(top->clat_percentiles); o->lat_percentiles = le32_to_cpu(top->lat_percentiles); o->percentile_precision = le32_to_cpu(top->percentile_precision); + o->sig_figs = le32_to_cpu(top->sig_figs); o->continue_on_error = le32_to_cpu(top->continue_on_error); o->cgroup_weight = le32_to_cpu(top->cgroup_weight); o->cgroup_nodelete = le32_to_cpu(top->cgroup_nodelete); @@ -458,6 +459,7 @@ void convert_thread_options_to_net(struct thread_options_pack *top, top->clat_percentiles = cpu_to_le32(o->clat_percentiles); top->lat_percentiles = cpu_to_le32(o->lat_percentiles); top->percentile_precision = cpu_to_le32(o->percentile_precision); + top->sig_figs = cpu_to_le32(o->sig_figs); top->continue_on_error = cpu_to_le32(o->continue_on_error); top->cgroup_weight = cpu_to_le32(o->cgroup_weight); top->cgroup_nodelete = cpu_to_le32(o->cgroup_nodelete); diff --git a/client.c b/client.c index 779fb9d..11fa262 100644 --- a/client.c +++ b/client.c @@ -942,6 +942,8 @@ static void convert_ts(struct thread_stat *dst, struct thread_stat *src) dst->kb_base = le32_to_cpu(src->kb_base); dst->unit_base = le32_to_cpu(src->unit_base); + dst->sig_figs = le32_to_cpu(src->sig_figs); + dst->latency_depth = le32_to_cpu(src->latency_depth); dst->latency_target = le64_to_cpu(src->latency_target); dst->latency_window = le64_to_cpu(src->latency_window); @@ -982,6 +984,7 @@ static void convert_gs(struct group_run_stats *dst, struct group_run_stats *src) dst->kb_base = le32_to_cpu(src->kb_base); dst->unit_base = le32_to_cpu(src->unit_base); + dst->sig_figs = le32_to_cpu(src->sig_figs); dst->groupid = le32_to_cpu(src->groupid); dst->unified_rw_rep = le32_to_cpu(src->unified_rw_rep); } @@ -1167,6 +1170,7 @@ static void convert_jobs_eta(struct jobs_eta *je) je->nr_threads = le32_to_cpu(je->nr_threads); je->is_pow2 = le32_to_cpu(je->is_pow2); je->unit_base = le32_to_cpu(je->unit_base); + je->sig_figs = le32_to_cpu(je->sig_figs); } void fio_client_sum_jobs_eta(struct jobs_eta *dst, struct jobs_eta *je) diff --git a/eta.c b/eta.c index baaa681..1b0b000 100644 --- a/eta.c +++ b/eta.c @@ -537,9 +537,9 @@ void display_thread_status(struct jobs_eta *je) char *tr, *mr; mr = num2str(je->m_rate[0] + je->m_rate[1] + je->m_rate[2], - 4, 0, je->is_pow2, N2S_BYTEPERSEC); + je->sig_figs, 0, je->is_pow2, N2S_BYTEPERSEC); tr = num2str(je->t_rate[0] + je->t_rate[1] + je->t_rate[2], - 4, 0, je->is_pow2, N2S_BYTEPERSEC); + je->sig_figs, 0, je->is_pow2, N2S_BYTEPERSEC); p += sprintf(p, ", %s-%s", mr, tr); free(tr); diff --git a/fio.1 b/fio.1 index bd7670a..caa8a76 100644 --- a/fio.1 +++ b/fio.1 @@ -2586,6 +2586,12 @@ numbers, and list the numbers in ascending order. For example, `\-\-percentile_list=99.5:99.9' will cause fio to report the values of completion latency below which 99.5% and 99.9% of the observed latencies fell, respectively. +.TP +.BI sig_figs \fR=\fPint +If using \fB\-\-output\-format\fR of `normal', set the significant figures +to this value. Higher values will yield more precise IOPS and throughput +units, while lower values will round. Requires a minimum value of 1 and a +maximum value of 10. Defaults to 4. .SS "Error handling" .TP .BI exitall_on_error diff --git a/gclient.c b/gclient.c index daa9153..ab7aa10 100644 --- a/gclient.c +++ b/gclient.c @@ -379,24 +379,24 @@ static void gfio_update_client_eta(struct fio_client *client, struct jobs_eta *j sprintf(output, "%3.1f%% done", perc); } - iops_str[0] = num2str(je->iops[0], 4, 1, 0, N2S_PERSEC); - iops_str[1] = num2str(je->iops[1], 4, 1, 0, N2S_PERSEC); - iops_str[2] = num2str(je->iops[2], 4, 1, 0, N2S_PERSEC); + iops_str[0] = num2str(je->iops[0], je->sig_figs, 1, 0, N2S_PERSEC); + iops_str[1] = num2str(je->iops[1], je->sig_figs, 1, 0, N2S_PERSEC); + iops_str[2] = num2str(je->iops[2], je->sig_figs, 1, 0, N2S_PERSEC); - rate_str[0] = num2str(je->rate[0], 4, 10, i2p, N2S_BYTEPERSEC); - rate_alt[0] = num2str(je->rate[0], 4, 10, !i2p, N2S_BYTEPERSEC); + rate_str[0] = num2str(je->rate[0], je->sig_figs, 10, i2p, N2S_BYTEPERSEC); + rate_alt[0] = num2str(je->rate[0], je->sig_figs, 10, !i2p, N2S_BYTEPERSEC); snprintf(tmp, sizeof(tmp), "%s (%s)", rate_str[0], rate_alt[0]); gtk_entry_set_text(GTK_ENTRY(ge->eta.read_bw), tmp); gtk_entry_set_text(GTK_ENTRY(ge->eta.read_iops), iops_str[0]); - rate_str[1] = num2str(je->rate[1], 4, 10, i2p, N2S_BYTEPERSEC); - rate_alt[1] = num2str(je->rate[1], 4, 10, !i2p, N2S_BYTEPERSEC); + rate_str[1] = num2str(je->rate[1], je->sig_figs, 10, i2p, N2S_BYTEPERSEC); + rate_alt[1] = num2str(je->rate[1], je->sig_figs, 10, !i2p, N2S_BYTEPERSEC); snprintf(tmp, sizeof(tmp), "%s (%s)", rate_str[1], rate_alt[1]); gtk_entry_set_text(GTK_ENTRY(ge->eta.write_bw), tmp); gtk_entry_set_text(GTK_ENTRY(ge->eta.write_iops), iops_str[1]); - rate_str[2] = num2str(je->rate[2], 4, 10, i2p, N2S_BYTEPERSEC); - rate_alt[2] = num2str(je->rate[2], 4, 10, !i2p, N2S_BYTEPERSEC); + rate_str[2] = num2str(je->rate[2], je->sig_figs, 10, i2p, N2S_BYTEPERSEC); + rate_alt[2] = num2str(je->rate[2], je->sig_figs, 10, !i2p, N2S_BYTEPERSEC); snprintf(tmp, sizeof(tmp), "%s (%s)", rate_str[2], rate_alt[2]); gtk_entry_set_text(GTK_ENTRY(ge->eta.trim_bw), tmp); gtk_entry_set_text(GTK_ENTRY(ge->eta.trim_iops), iops_str[2]); @@ -463,24 +463,24 @@ static void gfio_update_all_eta(struct jobs_eta *je) sprintf(output, "%3.1f%% done", perc); } - iops_str[0] = num2str(je->iops[0], 4, 1, 0, N2S_PERSEC); - iops_str[1] = num2str(je->iops[1], 4, 1, 0, N2S_PERSEC); - iops_str[2] = num2str(je->iops[2], 4, 1, 0, N2S_PERSEC); + iops_str[0] = num2str(je->iops[0], je->sig_figs, 1, 0, N2S_PERSEC); + iops_str[1] = num2str(je->iops[1], je->sig_figs, 1, 0, N2S_PERSEC); + iops_str[2] = num2str(je->iops[2], je->sig_figs, 1, 0, N2S_PERSEC); - rate_str[0] = num2str(je->rate[0], 4, 10, i2p, N2S_BYTEPERSEC); - rate_alt[0] = num2str(je->rate[0], 4, 10, !i2p, N2S_BYTEPERSEC); + rate_str[0] = num2str(je->rate[0], je->sig_figs, 10, i2p, N2S_BYTEPERSEC); + rate_alt[0] = num2str(je->rate[0], je->sig_figs, 10, !i2p, N2S_BYTEPERSEC); snprintf(tmp, sizeof(tmp), "%s (%s)", rate_str[0], rate_alt[0]); gtk_entry_set_text(GTK_ENTRY(ui->eta.read_bw), tmp); gtk_entry_set_text(GTK_ENTRY(ui->eta.read_iops), iops_str[0]); - rate_str[1] = num2str(je->rate[1], 4, 10, i2p, N2S_BYTEPERSEC); - rate_alt[1] = num2str(je->rate[1], 4, 10, !i2p, N2S_BYTEPERSEC); + rate_str[1] = num2str(je->rate[1], je->sig_figs, 10, i2p, N2S_BYTEPERSEC); + rate_alt[1] = num2str(je->rate[1], je->sig_figs, 10, !i2p, N2S_BYTEPERSEC); snprintf(tmp, sizeof(tmp), "%s (%s)", rate_str[1], rate_alt[1]); gtk_entry_set_text(GTK_ENTRY(ui->eta.write_bw), tmp); gtk_entry_set_text(GTK_ENTRY(ui->eta.write_iops), iops_str[1]); - rate_str[2] = num2str(je->rate[2], 4, 10, i2p, N2S_BYTEPERSEC); - rate_alt[2] = num2str(je->rate[2], 4, 10, !i2p, N2S_BYTEPERSEC); + rate_str[2] = num2str(je->rate[2], je->sig_figs, 10, i2p, N2S_BYTEPERSEC); + rate_alt[2] = num2str(je->rate[2], je->sig_figs, 10, !i2p, N2S_BYTEPERSEC); snprintf(tmp, sizeof(tmp), "%s (%s)", rate_str[2], rate_alt[2]); gtk_entry_set_text(GTK_ENTRY(ui->eta.trim_bw), tmp); gtk_entry_set_text(GTK_ENTRY(ui->eta.trim_iops), iops_str[2]); @@ -587,10 +587,10 @@ static void gfio_add_job_op(struct fio_client *client, struct fio_net_cmd *cmd) multitext_add_entry(&ge->eta.iotype, tmp); i2p = is_power_of_2(o->kb_base); - c1 = num2str(o->min_bs[DDIR_READ], 4, 1, i2p, N2S_BYTE); - c2 = num2str(o->max_bs[DDIR_READ], 4, 1, i2p, N2S_BYTE); - c3 = num2str(o->min_bs[DDIR_WRITE], 4, 1, i2p, N2S_BYTE); - c4 = num2str(o->max_bs[DDIR_WRITE], 4, 1, i2p, N2S_BYTE); + c1 = num2str(o->min_bs[DDIR_READ], o->sig_figs, 1, i2p, N2S_BYTE); + c2 = num2str(o->max_bs[DDIR_READ], o->sig_figs, 1, i2p, N2S_BYTE); + c3 = num2str(o->min_bs[DDIR_WRITE], o->sig_figs, 1, i2p, N2S_BYTE); + c4 = num2str(o->max_bs[DDIR_WRITE], o->sig_figs, 1, i2p, N2S_BYTE); sprintf(tmp, "%s-%s,%s-%s", c1, c2, c3, c4); free(c1); @@ -1183,7 +1183,7 @@ static void gfio_show_ddir_status(struct gfio_client *gc, GtkWidget *mbox, bw = (1000 * ts->io_bytes[ddir]) / runt; iops = (1000 * (uint64_t)ts->total_io_u[ddir]) / runt; - iops_p = num2str(iops, 4, 1, 0, N2S_PERSEC); + iops_p = num2str(iops, ts->sig_figs, 1, 0, N2S_PERSEC); box = gtk_hbox_new(FALSE, 3); gtk_box_pack_start(GTK_BOX(mbox), box, TRUE, FALSE, 3); @@ -1198,14 +1198,14 @@ static void gfio_show_ddir_status(struct gfio_client *gc, GtkWidget *mbox, gtk_box_pack_start(GTK_BOX(main_vbox), box, TRUE, FALSE, 3); label = new_info_label_in_frame(box, "IO"); - io_p = num2str(ts->io_bytes[ddir], 4, 1, i2p, N2S_BYTE); - io_palt = num2str(ts->io_bytes[ddir], 4, 1, !i2p, N2S_BYTE); + io_p = num2str(ts->io_bytes[ddir], ts->sig_figs, 1, i2p, N2S_BYTE); + io_palt = num2str(ts->io_bytes[ddir], ts->sig_figs, 1, !i2p, N2S_BYTE); snprintf(tmp, sizeof(tmp), "%s (%s)", io_p, io_palt); gtk_label_set_text(GTK_LABEL(label), tmp); label = new_info_label_in_frame(box, "Bandwidth"); - bw_p = num2str(bw, 4, 1, i2p, ts->unit_base); - bw_palt = num2str(bw, 4, 1, !i2p, ts->unit_base); + bw_p = num2str(bw, ts->sig_figs, 1, i2p, ts->unit_base); + bw_palt = num2str(bw, ts->sig_figs, 1, !i2p, ts->unit_base); snprintf(tmp, sizeof(tmp), "%s (%s)", bw_p, bw_palt); gtk_label_set_text(GTK_LABEL(label), tmp); diff --git a/init.c b/init.c index 736c6ff..b7e9c0e 100644 --- a/init.c +++ b/init.c @@ -1589,14 +1589,14 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num, char *c5 = NULL, *c6 = NULL; int i2p = is_power_of_2(o->kb_base); - c1 = num2str(o->min_bs[DDIR_READ], 4, 1, i2p, N2S_BYTE); - c2 = num2str(o->max_bs[DDIR_READ], 4, 1, i2p, N2S_BYTE); - c3 = num2str(o->min_bs[DDIR_WRITE], 4, 1, i2p, N2S_BYTE); - c4 = num2str(o->max_bs[DDIR_WRITE], 4, 1, i2p, N2S_BYTE); + c1 = num2str(o->min_bs[DDIR_READ], o->sig_figs, 1, i2p, N2S_BYTE); + c2 = num2str(o->max_bs[DDIR_READ], o->sig_figs, 1, i2p, N2S_BYTE); + c3 = num2str(o->min_bs[DDIR_WRITE], o->sig_figs, 1, i2p, N2S_BYTE); + c4 = num2str(o->max_bs[DDIR_WRITE], o->sig_figs, 1, i2p, N2S_BYTE); if (!o->bs_is_seq_rand) { - c5 = num2str(o->min_bs[DDIR_TRIM], 4, 1, i2p, N2S_BYTE); - c6 = num2str(o->max_bs[DDIR_TRIM], 4, 1, i2p, N2S_BYTE); + c5 = num2str(o->min_bs[DDIR_TRIM], o->sig_figs, 1, i2p, N2S_BYTE); + c6 = num2str(o->max_bs[DDIR_TRIM], o->sig_figs, 1, i2p, N2S_BYTE); } log_info("%s: (g=%d): rw=%s, ", td->o.name, diff --git a/options.c b/options.c index a0fcd8f..ae29aeb 100644 --- a/options.c +++ b/options.c @@ -4127,6 +4127,19 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { .category = FIO_OPT_C_STAT, .group = FIO_OPT_G_INVALID, }, + { + .name = "sig_figs", + .lname = "Significant figures", + .type = FIO_OPT_INT, + .off1 = offsetof(struct thread_options, sig_figs), + .maxval = 10, + .minval = 1, + .help = "Significant figures for output-format set to normal", + .def = "4", + .interval = 1, + .category = FIO_OPT_C_STAT, + .group = FIO_OPT_G_INVALID, + }, #ifdef FIO_HAVE_DISK_UTIL { diff --git a/server.c b/server.c index e6ea4cd..967cebe 100644 --- a/server.c +++ b/server.c @@ -1538,6 +1538,8 @@ void fio_server_send_ts(struct thread_stat *ts, struct group_run_stats *rs) p.ts.latency_window = cpu_to_le64(ts->latency_window); p.ts.latency_percentile.u.i = cpu_to_le64(fio_double_to_uint64(ts->latency_percentile.u.f)); + p.ts.sig_figs = cpu_to_le32(ts->sig_figs); + p.ts.nr_block_infos = cpu_to_le64(ts->nr_block_infos); for (i = 0; i < p.ts.nr_block_infos; i++) p.ts.block_infos[i] = cpu_to_le32(ts->block_infos[i]); diff --git a/stat.c b/stat.c index 89e2e6c..48d8e7d 100644 --- a/stat.c +++ b/stat.c @@ -299,14 +299,14 @@ void show_group_stats(struct group_run_stats *rs, struct buf_output *out) if (!rs->max_run[i]) continue; - io = num2str(rs->iobytes[i], 4, 1, i2p, N2S_BYTE); - ioalt = num2str(rs->iobytes[i], 4, 1, !i2p, N2S_BYTE); - agg = num2str(rs->agg[i], 4, 1, i2p, rs->unit_base); - aggalt = num2str(rs->agg[i], 4, 1, !i2p, rs->unit_base); - min = num2str(rs->min_bw[i], 4, 1, i2p, rs->unit_base); - minalt = num2str(rs->min_bw[i], 4, 1, !i2p, rs->unit_base); - max = num2str(rs->max_bw[i], 4, 1, i2p, rs->unit_base); - maxalt = num2str(rs->max_bw[i], 4, 1, !i2p, rs->unit_base); + io = num2str(rs->iobytes[i], rs->sig_figs, 1, i2p, N2S_BYTE); + ioalt = num2str(rs->iobytes[i], rs->sig_figs, 1, !i2p, N2S_BYTE); + agg = num2str(rs->agg[i], rs->sig_figs, 1, i2p, rs->unit_base); + aggalt = num2str(rs->agg[i], rs->sig_figs, 1, !i2p, rs->unit_base); + min = num2str(rs->min_bw[i], rs->sig_figs, 1, i2p, rs->unit_base); + minalt = num2str(rs->min_bw[i], rs->sig_figs, 1, !i2p, rs->unit_base); + max = num2str(rs->max_bw[i], rs->sig_figs, 1, i2p, rs->unit_base); + maxalt = num2str(rs->max_bw[i], rs->sig_figs, 1, !i2p, rs->unit_base); log_buf(out, "%s: bw=%s (%s), %s-%s (%s-%s), io=%s (%s), run=%llu-%llumsec\n", rs->unified_rw_rep ? " MIXED" : str[i], agg, aggalt, min, max, minalt, maxalt, io, ioalt, @@ -435,12 +435,12 @@ static void show_ddir_status(struct group_run_stats *rs, struct thread_stat *ts, runt = ts->runtime[ddir]; bw = (1000 * ts->io_bytes[ddir]) / runt; - io_p = num2str(ts->io_bytes[ddir], 4, 1, i2p, N2S_BYTE); - bw_p = num2str(bw, 4, 1, i2p, ts->unit_base); - bw_p_alt = num2str(bw, 4, 1, !i2p, ts->unit_base); + io_p = num2str(ts->io_bytes[ddir], ts->sig_figs, 1, i2p, N2S_BYTE); + bw_p = num2str(bw, ts->sig_figs, 1, i2p, ts->unit_base); + bw_p_alt = num2str(bw, ts->sig_figs, 1, !i2p, ts->unit_base); iops = (1000 * (uint64_t)ts->total_io_u[ddir]) / runt; - iops_p = num2str(iops, 4, 1, 0, N2S_NONE); + iops_p = num2str(iops, ts->sig_figs, 1, 0, N2S_NONE); log_buf(out, " %s: IOPS=%s, BW=%s (%s)(%s/%llumsec)\n", rs->unified_rw_rep ? "mixed" : str[ddir], @@ -738,9 +738,9 @@ static void show_ss_normal(struct thread_stat *ts, struct buf_output *out) bw_mean = steadystate_bw_mean(ts); iops_mean = steadystate_iops_mean(ts); - p1 = num2str(bw_mean / ts->kb_base, 4, ts->kb_base, i2p, ts->unit_base); - p1alt = num2str(bw_mean / ts->kb_base, 4, ts->kb_base, !i2p, ts->unit_base); - p2 = num2str(iops_mean, 4, 1, 0, N2S_NONE); + p1 = num2str(bw_mean / ts->kb_base, ts->sig_figs, ts->kb_base, i2p, ts->unit_base); + p1alt = num2str(bw_mean / ts->kb_base, ts->sig_figs, ts->kb_base, !i2p, ts->unit_base); + p2 = num2str(iops_mean, ts->sig_figs, 1, 0, N2S_NONE); log_buf(out, " steadystate : attained=%s, bw=%s (%s), iops=%s, %s%s=%.3f%s\n", ts->ss_state & __FIO_SS_ATTAINED ? "yes" : "no", @@ -1690,6 +1690,7 @@ void __show_run_stats(void) ts->kb_base = td->o.kb_base; ts->unit_base = td->o.unit_base; + ts->sig_figs = td->o.sig_figs; ts->unified_rw_rep = td->o.unified_rw_rep; } else if (ts->kb_base != td->o.kb_base && !kb_base_warned) { log_info("fio: kb_base differs for jobs in group, using" @@ -1752,6 +1753,7 @@ void __show_run_stats(void) rs = &runstats[ts->groupid]; rs->kb_base = ts->kb_base; rs->unit_base = ts->unit_base; + rs->sig_figs = ts->sig_figs; rs->unified_rw_rep += ts->unified_rw_rep; for (j = 0; j < DDIR_RWDIR_CNT; j++) { diff --git a/stat.h b/stat.h index 6ddcad2..ba66c40 100644 --- a/stat.h +++ b/stat.h @@ -11,6 +11,7 @@ struct group_run_stats { uint64_t agg[DDIR_RWDIR_CNT]; uint32_t kb_base; uint32_t unit_base; + uint32_t sig_figs; uint32_t groupid; uint32_t unified_rw_rep; } __attribute__((packed)); @@ -221,6 +222,8 @@ struct thread_stat { fio_fp64_t latency_percentile; uint64_t latency_window; + uint32_t sig_figs; + uint64_t ss_dur; uint32_t ss_state; uint32_t ss_head; @@ -257,6 +260,8 @@ struct jobs_eta { uint32_t is_pow2; uint32_t unit_base; + uint32_t sig_figs; + uint32_t files_open; /* diff --git a/thread_options.h b/thread_options.h index 5a037bf..ca549b5 100644 --- a/thread_options.h +++ b/thread_options.h @@ -309,6 +309,8 @@ struct thread_options { unsigned long long latency_window; fio_fp64_t latency_percentile; + unsigned int sig_figs; + unsigned block_error_hist; unsigned int replay_align; @@ -584,6 +586,8 @@ struct thread_options_pack { uint64_t latency_window; fio_fp64_t latency_percentile; + uint32_t sig_figs; + uint32_t block_error_hist; uint32_t replay_align; -- To unsubscribe from this list: send the line "unsubscribe fio" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html