Recent changes (master)

Jens Axboe
2020-02-05T13:00:15Z

The following changes since commit ab45cf076766ce1ed49d28380c059343305cde4a:

Merge branch 'stat-averaging-interval-start-fix' of https://github.com/maciejsszmigiero/fio (2020-01-28 14:15:35 -0700)

are available in the Git repository at:

git://git.kernel.dk/fio.git master

for you to fetch changes up to f4cd67d8dc96ca947b294f6a5c9fdced2b64215d:

Merge branch 'latency-rebase-again' of https://github.com/vincentkfu/fio (2020-02-04 10:05:09 -0700)

----------------------------------------------------------------
Jens Axboe (2):
Merge branch 'filestat2' of https://github.com/kusumi/fio
Merge branch 'latency-rebase-again' of https://github.com/vincentkfu/fio

Tomohiro Kusumi (1):
engines/filestat: change "lstat" bool option to "stat_type" str option

Vincent Fu (10):
fio: groundwork for adding slat, lat percentiles
fio: report percentiles for slat, clat, lat
gfio: display slat, clat, and lat percentiles
docs: updates for slat, clat, lat percentile reporting
stat: make priority summary statistics consistent with percentiles
fio: better distinguish between high and low priority
stat: fix high/low prio unified rw bug
t/latency_percentiles: test latency percentile reporting
t/run-fio-tests: add latency_percentiles.py
t/run-fio-tests: increase time allowed for tests to pass

HOWTO | 30 +-
cconv.c | 2 +
client.c | 14 +-
engines/filestat.c | 53 +-
fio.1 | 28 +-
gclient.c | 27 +-
init.c | 19 +-
options.c | 31 +-
server.c | 14 +-
server.h | 2 +-
stat.c | 440 +++++++--------
stat.h | 22 +-
t/latency_percentiles.py | 1324 ++++++++++++++++++++++++++++++++++++++++++++++
t/run-fio-tests.py | 14 +-
thread_options.h | 11 +-
15 files changed, 1672 insertions(+), 359 deletions(-)
create mode 100755 t/latency_percentiles.py

---

Diff of recent changes:

diff --git a/HOWTO b/HOWTO
index f19f9226..430c7b62 100644
--- a/HOWTO
+++ b/HOWTO
@@ -2261,9 +2261,10 @@ with the caveat that when used on the command line, they must come after the
multiple paths exist between the client and the server or in certain loopback
configurations.

-.. option:: lstat=bool : [filestat]
+.. option:: stat_type=str : [filestat]

- Use lstat(2) to measure lookup/getattr performance. Default is 0.
+ Specify stat system call type to measure lookup/getattr performance.
+ Default is **stat** for :manpage:`stat(2)`.

.. option:: readfua=bool : [sg]

@@ -3346,27 +3347,28 @@ Measurements and reporting
Disable measurements of throughput/bandwidth numbers. See
:option:`disable_lat`.

+.. option:: slat_percentiles=bool
+
+ Report submission latency percentiles. Submission latency is not recorded
+ for synchronous ioengines.
+
.. option:: clat_percentiles=bool

- Enable the reporting of percentiles of completion latencies. This
- option is mutually exclusive with :option:`lat_percentiles`.
+ Report completion latency percentiles.

.. option:: lat_percentiles=bool

- Enable the reporting of percentiles of I/O latencies. This is similar
- to :option:`clat_percentiles`, except that this includes the
- submission latency. This option is mutually exclusive with
- :option:`clat_percentiles`.
+ Report total latency percentiles. Total latency is the sum of submission
+ latency and completion latency.

.. option:: percentile_list=float_list

- Overwrite the default list of percentiles for completion latencies and
- the block error histogram. Each number is a floating number in the
- range (0,100], and the maximum length of the list is 20. Use ``:`` to
- separate the numbers, and list the numbers in ascending order. For
+ Overwrite the default list of percentiles for latencies and the block error
+ histogram. Each number is a floating point number in the range (0,100], and
+ the maximum length of the list is 20. Use ``:`` to separate the numbers. 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.
+ latency durations below which 99.5% and 99.9% of the observed latencies fell,
+ respectively.

.. option:: significant_figures=int

diff --git a/cconv.c b/cconv.c
index 04854b0e..48218dc4 100644
--- a/cconv.c
+++ b/cconv.c
@@ -271,6 +271,7 @@ void convert_thread_options_to_cpu(struct thread_options *o,
o->trim_zero = le32_to_cpu(top->trim_zero);
o->clat_percentiles = le32_to_cpu(top->clat_percentiles);
o->lat_percentiles = le32_to_cpu(top->lat_percentiles);
+ o->slat_percentiles = le32_to_cpu(top->slat_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);
@@ -469,6 +470,7 @@ void convert_thread_options_to_net(struct thread_options_pack *top,
top->trim_zero = cpu_to_le32(o->trim_zero);
top->clat_percentiles = cpu_to_le32(o->clat_percentiles);
top->lat_percentiles = cpu_to_le32(o->lat_percentiles);
+ top->slat_percentiles = cpu_to_le32(o->slat_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);
diff --git a/client.c b/client.c
index 4aed39e7..b7575596 100644
--- a/client.c
+++ b/client.c
@@ -944,7 +944,7 @@ static void convert_io_stat(struct io_stat *dst, struct io_stat *src)

static void convert_ts(struct thread_stat *dst, struct thread_stat *src)
{
- int i, j;
+ int i, j, k;

dst->error = le32_to_cpu(src->error);
dst->thread_number = le32_to_cpu(src->thread_number);
@@ -969,6 +969,7 @@ static void convert_ts(struct thread_stat *dst, struct thread_stat *src)
dst->majf = le64_to_cpu(src->majf);
dst->clat_percentiles = le32_to_cpu(src->clat_percentiles);
dst->lat_percentiles = le32_to_cpu(src->lat_percentiles);
+ dst->slat_percentiles = le32_to_cpu(src->slat_percentiles);
dst->percentile_precision = le64_to_cpu(src->percentile_precision);

for (i = 0; i < FIO_IO_U_LIST_MAX_LEN; i++) {
@@ -991,9 +992,10 @@ static void convert_ts(struct thread_stat *dst, struct thread_stat *src)
for (i = 0; i < FIO_IO_U_LAT_M_NR; i++)
dst->io_u_lat_m[i] = le64_to_cpu(src->io_u_lat_m[i]);

- for (i = 0; i < DDIR_RWDIR_CNT; i++)
- for (j = 0; j < FIO_IO_U_PLAT_NR; j++)
- dst->io_u_plat[i][j] = le64_to_cpu(src->io_u_plat[i][j]);
+ for (i = 0; i < FIO_LAT_CNT; i++)
+ for (j = 0; j < DDIR_RWDIR_CNT; j++)
+ for (k = 0; k < FIO_IO_U_PLAT_NR; k++)
+ dst->io_u_plat[i][j][k] = le64_to_cpu(src->io_u_plat[i][j][k]);

for (j = 0; j < FIO_IO_U_PLAT_NR; j++)
dst->io_u_sync_plat[j] = le64_to_cpu(src->io_u_sync_plat[j]);
@@ -1035,10 +1037,10 @@ static void convert_ts(struct thread_stat *dst, struct thread_stat *src)
for (i = 0; i < DDIR_RWDIR_CNT; i++) {
for (j = 0; j < FIO_IO_U_PLAT_NR; j++) {
dst->io_u_plat_high_prio[i][j] = le64_to_cpu(src->io_u_plat_high_prio[i][j]);
- dst->io_u_plat_prio[i][j] = le64_to_cpu(src->io_u_plat_prio[i][j]);
+ dst->io_u_plat_low_prio[i][j] = le64_to_cpu(src->io_u_plat_low_prio[i][j]);
}
convert_io_stat(&dst->clat_high_prio_stat[i], &src->clat_high_prio_stat[i]);
- convert_io_stat(&dst->clat_prio_stat[i], &src->clat_prio_stat[i]);
+ convert_io_stat(&dst->clat_low_prio_stat[i], &src->clat_low_prio_stat[i]);
}

dst->ss_dur = le64_to_cpu(src->ss_dur);
diff --git a/engines/filestat.c b/engines/filestat.c
index 6c87c4c2..68a340bd 100644
--- a/engines/filestat.c
+++ b/engines/filestat.c
@@ -19,17 +19,39 @@ struct fc_data {

struct filestat_options {
void *pad;
- unsigned int lstat;
+ unsigned int stat_type;
+};
+
+enum {
+ FIO_FILESTAT_STAT = 1,
+ FIO_FILESTAT_LSTAT = 2,
+ /*FIO_FILESTAT_STATX = 3,*/
};

static struct fio_option options[] = {
{
- .name = "lstat",
- .lname = "lstat",
- .type = FIO_OPT_BOOL,
- .off1 = offsetof(struct filestat_options, lstat),
- .help = "Use lstat(2) to measure lookup/getattr performance",
- .def = "0",
+ .name = "stat_type",
+ .lname = "stat_type",
+ .type = FIO_OPT_STR,
+ .off1 = offsetof(struct filestat_options, stat_type),
+ .help = "Specify stat system call type to measure lookup/getattr performance",
+ .def = "stat",
+ .posval = {
+ { .ival = "stat",
+ .oval = FIO_FILESTAT_STAT,
+ .help = "Use stat(2)",
+ },
+ { .ival = "lstat",
+ .oval = FIO_FILESTAT_LSTAT,
+ .help = "Use lstat(2)",
+ },
+ /*
+ { .ival = "statx",
+ .oval = FIO_FILESTAT_STATX,
+ .help = "Use statx(2)",
+ },
+ */
+ },
.category = FIO_OPT_C_ENGINE,
.group = FIO_OPT_G_FILESTAT,
},
@@ -60,17 +82,24 @@ static int stat_file(struct thread_data *td, struct fio_file *f)
if (do_lat)
fio_gettime(&start, NULL);

- if (o->lstat)
- ret = lstat(f->file_name, &statbuf);
- else
+ switch (o->stat_type){
+ case FIO_FILESTAT_STAT:
ret = stat(f->file_name, &statbuf);
+ break;
+ case FIO_FILESTAT_LSTAT:
+ ret = lstat(f->file_name, &statbuf);
+ break;
+ default:
+ ret = -1;
+ break;
+ }

if (ret == -1) {
char buf[FIO_VERROR_SIZE];
int e = errno;

- snprintf(buf, sizeof(buf), "%sstat(%s)",
- o->lstat ? "l" : "", f->file_name);
+ snprintf(buf, sizeof(buf), "stat(%s) type=%u", f->file_name,
+ o->stat_type);
td_verror(td, e, buf);
return 1;
}
diff --git a/fio.1 b/fio.1
index a58632b4..1db12c2f 100644
--- a/fio.1
+++ b/fio.1
@@ -2032,8 +2032,9 @@ on the client site it will be used in the rdma_resolve_add()
function. This can be useful when multiple paths exist between the
client and the server or in certain loopback configurations.
.TP
-.BI (filestat)lstat \fR=\fPbool
-Use \fBlstat\fR\|(2) to measure lookup/getattr performance. Default: 0.
+.BI (filestat)stat_type \fR=\fPstr
+Specify stat system call type to measure lookup/getattr performance.
+Default is \fBstat\fR for \fBstat\fR\|(2).
.TP
.BI (sg)readfua \fR=\fPbool
With readfua option set to 1, read operations include the force
@@ -3000,23 +3001,24 @@ Disable measurements of submission latency numbers. See
Disable measurements of throughput/bandwidth numbers. See
\fBdisable_lat\fR.
.TP
+.BI slat_percentiles \fR=\fPbool
+Report submission latency percentiles. Submission latency is not recorded
+for synchronous ioengines.
+.TP
.BI clat_percentiles \fR=\fPbool
-Enable the reporting of percentiles of completion latencies. This option is
-mutually exclusive with \fBlat_percentiles\fR.
+Report completion latency percentiles.
.TP
.BI lat_percentiles \fR=\fPbool
-Enable the reporting of percentiles of I/O latencies. This is similar to
-\fBclat_percentiles\fR, except that this includes the submission latency.
-This option is mutually exclusive with \fBclat_percentiles\fR.
+Report total latency percentiles. Total latency is the sum of submission
+latency and completion latency.
.TP
.BI percentile_list \fR=\fPfloat_list
-Overwrite the default list of percentiles for completion latencies and the
-block error histogram. Each number is a floating number in the range
+Overwrite the default list of percentiles for latencies and the
+block error histogram. Each number is a floating point number in the range
(0,100], and the maximum length of the list is 20. Use ':' to separate the
-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.
+numbers. For example, `\-\-percentile_list=99.5:99.9' will cause fio to
+report the latency durations below which 99.5% and 99.9% of the observed
+latencies fell, respectively.
.TP
.BI significant_figures \fR=\fPint
If using \fB\-\-output\-format\fR of `normal', set the significant figures
diff --git a/gclient.c b/gclient.c
index d8dc62d2..d2044f32 100644
--- a/gclient.c
+++ b/gclient.c
@@ -1097,10 +1097,9 @@ static struct graph *setup_clat_graph(char *title, unsigned long long *ovals,

static void gfio_show_clat_percentiles(struct gfio_client *gc,
GtkWidget *vbox, struct thread_stat *ts,
- int ddir)
+ int ddir, uint64_t *io_u_plat,
+ unsigned long long nr, const char *type)
{
- uint64_t *io_u_plat = ts->io_u_plat[ddir];
- unsigned long long nr = ts->clat_stat[ddir].samples;
fio_fp64_t *plist = ts->percentile_list;
unsigned int len, scale_down;
unsigned long long *ovals, minv, maxv;
@@ -1128,10 +1127,7 @@ static void gfio_show_clat_percentiles(struct gfio_client *gc,
base = "nsec";
}

- if (ts->clat_percentiles)
- sprintf(tmp, "Completion percentiles (%s)", base);
- else
- sprintf(tmp, "Latency percentiles (%s)", base);
+ sprintf(tmp, "%s latency percentiles (%s)", type, base);

tree_view = gfio_output_clat_percentiles(ovals, plist, len, base, scale_down);
ge->clat_graph = setup_clat_graph(tmp, ovals, plist, len, 700.0, 300.0);
@@ -1285,8 +1281,21 @@ static void gfio_show_ddir_status(struct gfio_client *gc, GtkWidget *mbox,
gfio_show_lat(vbox, "Total latency", min[2], max[2], mean[2], dev[2]);
}

- if (ts->clat_percentiles)
- gfio_show_clat_percentiles(gc, main_vbox, ts, ddir);
+ if (ts->slat_percentiles && flags & GFIO_SLAT)
+ gfio_show_clat_percentiles(gc, main_vbox, ts, ddir,
+ ts->io_u_plat[FIO_SLAT][ddir],
+ ts->slat_stat[ddir].samples,
+ "Submission");
+ if (ts->clat_percentiles && flags & GFIO_CLAT)
+ gfio_show_clat_percentiles(gc, main_vbox, ts, ddir,
+ ts->io_u_plat[FIO_CLAT][ddir],
+ ts->clat_stat[ddir].samples,
+ "Completion");
+ if (ts->lat_percentiles && flags & GFIO_LAT)
+ gfio_show_clat_percentiles(gc, main_vbox, ts, ddir,
+ ts->io_u_plat[FIO_LAT][ddir],
+ ts->lat_stat[ddir].samples,
+ "Total");

free(io_p);
free(bw_p);
diff --git a/init.c b/init.c
index dca44bca..b5315334 100644
--- a/init.c
+++ b/init.c
@@ -944,24 +944,12 @@ static int fixup_options(struct thread_data *td)
ret |= 1;
}

- if (fio_option_is_set(o, clat_percentiles) &&
- !fio_option_is_set(o, lat_percentiles)) {
- o->lat_percentiles = !o->clat_percentiles;
- } else if (fio_option_is_set(o, lat_percentiles) &&
- !fio_option_is_set(o, clat_percentiles)) {
- o->clat_percentiles = !o->lat_percentiles;
- } else if (fio_option_is_set(o, lat_percentiles) &&
- fio_option_is_set(o, clat_percentiles) &&
- o->lat_percentiles && o->clat_percentiles) {
- log_err("fio: lat_percentiles and clat_percentiles are "
- "mutually exclusive\n");
- ret |= 1;
- }
-
if (o->disable_lat)
o->lat_percentiles = 0;
if (o->disable_clat)
o->clat_percentiles = 0;
+ if (o->disable_slat)
+ o->slat_percentiles = 0;

/*
* Fix these up to be nsec internally
@@ -1509,6 +1497,7 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num,

td->ts.clat_percentiles = o->clat_percentiles;
td->ts.lat_percentiles = o->lat_percentiles;
+ td->ts.slat_percentiles = o->slat_percentiles;
td->ts.percentile_precision = o->percentile_precision;
memcpy(td->ts.percentile_list, o->percentile_list, sizeof(o->percentile_list));
td->ts.sig_figs = o->sig_figs;
@@ -1520,7 +1509,7 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num,
td->ts.bw_stat[i].min_val = ULONG_MAX;
td->ts.iops_stat[i].min_val = ULONG_MAX;
td->ts.clat_high_prio_stat[i].min_val = ULONG_MAX;
- td->ts.clat_prio_stat[i].min_val = ULONG_MAX;
+ td->ts.clat_low_prio_stat[i].min_val = ULONG_MAX;
}
td->ts.sync_stat.min_val = ULONG_MAX;
td->ddir_seq_nr = o->ddir_seq_nr;
diff --git a/options.c b/options.c
index 287f0435..4714a3a1 100644
--- a/options.c
+++ b/options.c
@@ -1408,13 +1408,20 @@ static int str_gtod_reduce_cb(void *data, int *il)
struct thread_data *td = cb_data_to_td(data);
int val = *il;

- td->o.disable_lat = !!val;
- td->o.disable_clat = !!val;
- td->o.disable_slat = !!val;
- td->o.disable_bw = !!val;
- td->o.clat_percentiles = !val;
- if (val)
+ /*
+ * Only modfiy options if gtod_reduce==1
+ * Otherwise leave settings alone.
+ */
+ if (val) {
+ td->o.disable_lat = 1;
+ td->o.disable_clat = 1;
+ td->o.disable_slat = 1;
+ td->o.disable_bw = 1;
+ td->o.clat_percentiles = 0;
+ td->o.lat_percentiles = 0;
+ td->o.slat_percentiles = 0;
td->ts_cache_mask = 63;
+ }

return 0;
}
@@ -4312,7 +4319,6 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
.off1 = offsetof(struct thread_options, clat_percentiles),
.help = "Enable the reporting of completion latency percentiles",
.def = "1",
- .inverse = "lat_percentiles",
.category = FIO_OPT_C_STAT,
.group = FIO_OPT_G_INVALID,
},
@@ -4323,7 +4329,16 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
.off1 = offsetof(struct thread_options, lat_percentiles),
.help = "Enable the reporting of IO latency percentiles",
.def = "0",
- .inverse = "clat_percentiles",
+ .category = FIO_OPT_C_STAT,
+ .group = FIO_OPT_G_INVALID,
+ },
+ {
+ .name = "slat_percentiles",
+ .lname = "Submission latency percentiles",
+ .type = FIO_OPT_BOOL,
+ .off1 = offsetof(struct thread_options, slat_percentiles),
+ .help = "Enable the reporting of submission latency percentiles",
+ .def = "0",
.category = FIO_OPT_C_STAT,
.group = FIO_OPT_G_INVALID,
},
diff --git a/server.c b/server.c
index a5af5e74..248a2d44 100644
--- a/server.c
+++ b/server.c
@@ -1463,7 +1463,7 @@ static void convert_gs(struct group_run_stats *dst, struct group_run_stats *src)
void fio_server_send_ts(struct thread_stat *ts, struct group_run_stats *rs)
{
struct cmd_ts_pdu p;
- int i, j;
+ int i, j, k;
void *ss_buf;
uint64_t *ss_iops, *ss_bw;

@@ -1499,6 +1499,7 @@ void fio_server_send_ts(struct thread_stat *ts, struct group_run_stats *rs)
p.ts.majf = cpu_to_le64(ts->majf);
p.ts.clat_percentiles = cpu_to_le32(ts->clat_percentiles);
p.ts.lat_percentiles = cpu_to_le32(ts->lat_percentiles);
+ p.ts.slat_percentiles = cpu_to_le32(ts->slat_percentiles);
p.ts.percentile_precision = cpu_to_le64(ts->percentile_precision);

for (i = 0; i < FIO_IO_U_LIST_MAX_LEN; i++) {
@@ -1521,9 +1522,10 @@ void fio_server_send_ts(struct thread_stat *ts, struct group_run_stats *rs)
for (i = 0; i < FIO_IO_U_LAT_M_NR; i++)
p.ts.io_u_lat_m[i] = cpu_to_le64(ts->io_u_lat_m[i]);

- for (i = 0; i < DDIR_RWDIR_CNT; i++)
- for (j = 0; j < FIO_IO_U_PLAT_NR; j++)
- p.ts.io_u_plat[i][j] = cpu_to_le64(ts->io_u_plat[i][j]);
+ for (i = 0; i < FIO_LAT_CNT; i++)
+ for (j = 0; j < DDIR_RWDIR_CNT; j++)
+ for (k = 0; k < FIO_IO_U_PLAT_NR; k++)
+ p.ts.io_u_plat[i][j][k] = cpu_to_le64(ts->io_u_plat[i][j][k]);

for (j = 0; j < FIO_IO_U_PLAT_NR; j++)
p.ts.io_u_sync_plat[j] = cpu_to_le64(ts->io_u_sync_plat[j]);
@@ -1577,10 +1579,10 @@ void fio_server_send_ts(struct thread_stat *ts, struct group_run_stats *rs)
for (i = 0; i < DDIR_RWDIR_CNT; i++) {
for (j = 0; j < FIO_IO_U_PLAT_NR; j++) {
p.ts.io_u_plat_high_prio[i][j] = cpu_to_le64(ts->io_u_plat_high_prio[i][j]);
- p.ts.io_u_plat_prio[i][j] = cpu_to_le64(ts->io_u_plat_prio[i][j]);
+ p.ts.io_u_plat_low_prio[i][j] = cpu_to_le64(ts->io_u_plat_low_prio[i][j]);
}
convert_io_stat(&p.ts.clat_high_prio_stat[i], &ts->clat_high_prio_stat[i]);
- convert_io_stat(&p.ts.clat_prio_stat[i], &ts->clat_prio_stat[i]);
+ convert_io_stat(&p.ts.clat_low_prio_stat[i], &ts->clat_low_prio_stat[i]);
}

convert_gs(&p.rs, rs);
diff --git a/server.h b/server.h
index 6ac75366..279b6917 100644
--- a/server.h
+++ b/server.h
@@ -48,7 +48,7 @@ struct fio_net_cmd_reply {
};

enum {
- FIO_SERVER_VER = 81,
+ FIO_SERVER_VER = 82,

FIO_SERVER_MAX_FRAGMENT_PDU = 1024,
FIO_SERVER_MAX_CMD_MB = 2048,
diff --git a/stat.c b/stat.c
index cc1c360e..69d57b69 100644
--- a/stat.c
+++ b/stat.c
@@ -483,26 +483,38 @@ static void show_ddir_status(struct group_run_stats *rs, struct thread_stat *ts,
if (calc_lat(&ts->lat_stat[ddir], &min, &max, &mean, &dev))
display_lat(" lat", min, max, mean, dev, out);
if (calc_lat(&ts->clat_high_prio_stat[ddir], &min, &max, &mean, &dev))
- display_lat("prio_clat", min, max, mean, dev, out);
+ display_lat(ts->lat_percentiles ? "prio_lat" : "prio_clat",
+ min, max, mean, dev, out);
+
+ if (ts->slat_percentiles && ts->slat_stat[ddir].samples > 0)
+ show_clat_percentiles(ts->io_u_plat[FIO_SLAT][ddir],
+ ts->slat_stat[ddir].samples,
+ ts->percentile_list,
+ ts->percentile_precision, "slat", out);
+ if (ts->clat_percentiles && ts->clat_stat[ddir].samples > 0)
+ show_clat_percentiles(ts->io_u_plat[FIO_CLAT][ddir],
+ ts->clat_stat[ddir].samples,
+ ts->percentile_list,
+ ts->percentile_precision, "clat", out);
+ if (ts->lat_percentiles && ts->lat_stat[ddir].samples > 0)
+ show_clat_percentiles(ts->io_u_plat[FIO_LAT][ddir],
+ ts->lat_stat[ddir].samples,
+ ts->percentile_list,
+ ts->percentile_precision, "lat", out);

if (ts->clat_percentiles || ts->lat_percentiles) {
- const char *name = ts->clat_percentiles ? "clat" : " lat";
+ const char *name = ts->lat_percentiles ? "lat" : "clat";
char prio_name[32];
uint64_t samples;

- if (ts->clat_percentiles)
- samples = ts->clat_stat[ddir].samples;
- else
+ if (ts->lat_percentiles)
samples = ts->lat_stat[ddir].samples;
-
- show_clat_percentiles(ts->io_u_plat[ddir],
- samples,
- ts->percentile_list,
- ts->percentile_precision, name, out);
+ else
+ samples = ts->clat_stat[ddir].samples;

/* Only print this if some high and low priority stats were collected */
if (ts->clat_high_prio_stat[ddir].samples > 0 &&
- ts->clat_prio_stat[ddir].samples > 0)
+ ts->clat_low_prio_stat[ddir].samples > 0)
{
sprintf(prio_name, "high prio (%.2f%%) %s",
100. * (double) ts->clat_high_prio_stat[ddir].samples / (double) samples,
@@ -513,14 +525,15 @@ static void show_ddir_status(struct group_run_stats *rs, struct thread_stat *ts,
ts->percentile_precision, prio_name, out);

sprintf(prio_name, "low prio (%.2f%%) %s",
- 100. * (double) ts->clat_prio_stat[ddir].samples / (double) samples,
+ 100. * (double) ts->clat_low_prio_stat[ddir].samples / (double) samples,
name);
- show_clat_percentiles(ts->io_u_plat_prio[ddir],
- ts->clat_prio_stat[ddir].samples,
+ show_clat_percentiles(ts->io_u_plat_low_prio[ddir],
+ ts->clat_low_prio_stat[ddir].samples,
ts->percentile_list,
ts->percentile_precision, prio_name, out);
}
}
+
if (calc_lat(&ts->bw_stat[ddir], &min, &max, &mean, &dev)) {
double p_of_agg = 100.0, fkb_base = (double)rs->kb_base;
const char *bw_str;
@@ -1170,12 +1183,17 @@ static void show_ddir_status_terse(struct thread_stat *ts,
else
log_buf(out, ";%llu;%llu;%f;%f", 0ULL, 0ULL, 0.0, 0.0);

- if (ts->clat_percentiles || ts->lat_percentiles) {
- len = calc_clat_percentiles(ts->io_u_plat[ddir],
+ if (ts->lat_percentiles)
+ len = calc_clat_percentiles(ts->io_u_plat[FIO_LAT][ddir],
+ ts->lat_stat[ddir].samples,
+ ts->percentile_list, &ovals, &maxv,
+ &minv);
+ else if (ts->clat_percentiles)
+ len = calc_clat_percentiles(ts->io_u_plat[FIO_CLAT][ddir],
ts->clat_stat[ddir].samples,
ts->percentile_list, &ovals, &maxv,
&minv);
- } else
+ else
len = 0;

for (i = 0; i < FIO_IO_U_LIST_MAX_LEN; i++) {
@@ -1221,18 +1239,63 @@ static void show_ddir_status_terse(struct thread_stat *ts,
}
}

+static struct json_object *add_ddir_lat_json(struct thread_stat *ts, uint32_t percentiles,
+ struct io_stat *lat_stat, uint64_t *io_u_plat)
+{
+ char buf[120];
+ double mean, dev;
+ unsigned int i, len;
+ struct json_object *lat_object, *percentile_object, *clat_bins_object;
+ unsigned long long min, max, maxv, minv, *ovals = NULL;
+
+ if (!calc_lat(lat_stat, &min, &max, &mean, &dev)) {
+ min = max = 0;
+ mean = dev = 0.0;
+ }
+ lat_object = json_create_object();
+ json_object_add_value_int(lat_object, "min", min);
+ json_object_add_value_int(lat_object, "max", max);
+ json_object_add_value_float(lat_object, "mean", mean);
+ json_object_add_value_float(lat_object, "stddev", dev);
+ json_object_add_value_int(lat_object, "N", lat_stat->samples);
+
+ if (percentiles && lat_stat->samples) {
+ len = calc_clat_percentiles(io_u_plat, lat_stat->samples,
+ ts->percentile_list, &ovals, &maxv, &minv);
+
+ if (len > FIO_IO_U_LIST_MAX_LEN)
+ len = FIO_IO_U_LIST_MAX_LEN;
+
+ percentile_object = json_create_object();
+ json_object_add_value_object(lat_object, "percentile", percentile_object);
+ for (i = 0; i < len; i++) {
+ snprintf(buf, sizeof(buf), "%f", ts->percentile_list[i].u.f);
+ json_object_add_value_int(percentile_object, buf, ovals[i]);
+ }
+ free(ovals);
+
+ if (output_format & FIO_OUTPUT_JSON_PLUS) {
+ clat_bins_object = json_create_object();
+ json_object_add_value_object(lat_object, "bins", clat_bins_object);
+
+ for(i = 0; i < FIO_IO_U_PLAT_NR; i++)
+ if (io_u_plat[i]) {
+ snprintf(buf, sizeof(buf), "%llu", plat_idx_to_val(i));
+ json_object_add_value_int(clat_bins_object, buf, io_u_plat[i]);
+ }
+ }
+ }
+
+ return lat_object;
+}
+
static void add_ddir_status_json(struct thread_stat *ts,
struct group_run_stats *rs, int ddir, struct json_object *parent)
{
- unsigned long long min, max, minv, maxv;
+ unsigned long long min, max;
unsigned long long bw_bytes, bw;
- unsigned long long *ovals = NULL;
double mean, dev, iops;
- unsigned int len;
- int i;
- struct json_object *dir_object, *tmp_object, *percentile_object = NULL,
- *clat_bins_object = NULL;
- char buf[120];
+ struct json_object *dir_object, *tmp_object;
double p_of_agg = 100.0;

assert(ddir_rw(ddir) || ddir_sync(ddir));
@@ -1266,222 +1329,47 @@ static void add_ddir_status_json(struct thread_stat *ts,
json_object_add_value_int(dir_object, "short_ios", ts->short_io_u[ddir]);
json_object_add_value_int(dir_object, "drop_ios", ts->drop_io_u[ddir]);

- if (!calc_lat(&ts->slat_stat[ddir], &min, &max, &mean, &dev)) {
- min = max = 0;
- mean = dev = 0.0;
- }
- tmp_object = json_create_object();
+ tmp_object = add_ddir_lat_json(ts, ts->slat_percentiles,
+ &ts->slat_stat[ddir], ts->io_u_plat[FIO_SLAT][ddir]);
json_object_add_value_object(dir_object, "slat_ns", tmp_object);
- json_object_add_value_int(tmp_object, "min", min);
- json_object_add_value_int(tmp_object, "max", max);
- json_object_add_value_float(tmp_object, "mean", mean);
- json_object_add_value_float(tmp_object, "stddev", dev);
-
- if (!calc_lat(&ts->clat_stat[ddir], &min, &max, &mean, &dev)) {
- min = max = 0;
- mean = dev = 0.0;
- }
- tmp_object = json_create_object();
+
+ tmp_object = add_ddir_lat_json(ts, ts->clat_percentiles,
+ &ts->clat_stat[ddir], ts->io_u_plat[FIO_CLAT][ddir]);
json_object_add_value_object(dir_object, "clat_ns", tmp_object);
- json_object_add_value_int(tmp_object, "min", min);
- json_object_add_value_int(tmp_object, "max", max);
- json_object_add_value_float(tmp_object, "mean", mean);
- json_object_add_value_float(tmp_object, "stddev", dev);
- } else {
- if (!calc_lat(&ts->sync_stat, &min, &max, &mean, &dev)) {
- min = max = 0;
- mean = dev = 0.0;
- }

- tmp_object = json_create_object();
+ tmp_object = add_ddir_lat_json(ts, ts->lat_percentiles,
+ &ts->lat_stat[ddir], ts->io_u_plat[FIO_LAT][ddir]);
json_object_add_value_object(dir_object, "lat_ns", tmp_object);
+ } else {
json_object_add_value_int(dir_object, "total_ios", ts->total_io_u[DDIR_SYNC]);
- json_object_add_value_int(tmp_object, "min", min);
- json_object_add_value_int(tmp_object, "max", max);
- json_object_add_value_float(tmp_object, "mean", mean);
- json_object_add_value_float(tmp_object, "stddev", dev);
- }
-
- if (ts->clat_percentiles || ts->lat_percentiles) {
- if (ddir_rw(ddir)) {
- uint64_t samples;
-
- if (ts->clat_percentiles)
- samples = ts->clat_stat[ddir].samples;
- else
- samples = ts->lat_stat[ddir].samples;
-
- len = calc_clat_percentiles(ts->io_u_plat[ddir],
- samples, ts->percentile_list, &ovals,
- &maxv, &minv);
- } else {
- len = calc_clat_percentiles(ts->io_u_sync_plat,
- ts->sync_stat.samples,
- ts->percentile_list, &ovals, &maxv,
- &minv);
- }
-
- if (len > FIO_IO_U_LIST_MAX_LEN)
- len = FIO_IO_U_LIST_MAX_LEN;
- } else
- len = 0;
-
- if (ts->clat_percentiles) {
- percentile_object = json_create_object();
- json_object_add_value_object(tmp_object, "percentile", percentile_object);
- for (i = 0; i < len; i++) {
- snprintf(buf, sizeof(buf), "%f",
- ts->percentile_list[i].u.f);
- json_object_add_value_int(percentile_object, buf,
- ovals[i]);
- }
- }
-
- free(ovals);
-
- if (output_format & FIO_OUTPUT_JSON_PLUS && ts->clat_percentiles) {
- clat_bins_object = json_create_object();
- json_object_add_value_object(tmp_object, "bins",
- clat_bins_object);
-
- for(i = 0; i < FIO_IO_U_PLAT_NR; i++) {
- if (ddir_rw(ddir)) {
- if (ts->io_u_plat[ddir][i]) {
- snprintf(buf, sizeof(buf), "%llu", plat_idx_to_val(i));
- json_object_add_value_int(clat_bins_object, buf, ts->io_u_plat[ddir][i]);
- }
- } else {
- if (ts->io_u_sync_plat[i]) {
- snprintf(buf, sizeof(buf), "%llu", plat_idx_to_val(i));
- json_object_add_value_int(clat_bins_object, buf, ts->io_u_sync_plat[i]);
- }
- }
- }
+ tmp_object = add_ddir_lat_json(ts, ts->lat_percentiles | ts->clat_percentiles,
+ &ts->sync_stat, ts->io_u_sync_plat);
+ json_object_add_value_object(dir_object, "lat_ns", tmp_object);
}

+ if (!ddir_rw(ddir))
+ return;

/* Only print PRIO latencies if some high priority samples were gathered */
if (ts->clat_high_prio_stat[ddir].samples > 0) {
- /* START OF HIGH PRIO CLAT */
- if (!calc_lat(&ts->clat_high_prio_stat[ddir], &min, &max, &mean, &dev)) {
- min = max = 0;
- mean = dev = 0.0;
- }
- tmp_object = json_create_object();
- json_object_add_value_object(dir_object, "clat_prio",
- tmp_object);
- json_object_add_value_int(tmp_object, "samples",
- ts->clat_high_prio_stat[ddir].samples);
- json_object_add_value_int(tmp_object, "min", min);
- json_object_add_value_int(tmp_object, "max", max);
- json_object_add_value_float(tmp_object, "mean", mean);
- json_object_add_value_float(tmp_object, "stddev", dev);
-
- if (ts->clat_percentiles) {
- len = calc_clat_percentiles(ts->io_u_plat_high_prio[ddir],
- ts->clat_high_prio_stat[ddir].samples,
- ts->percentile_list, &ovals, &maxv,
- &minv);
- } else
- len = 0;
-
- percentile_object = json_create_object();
- json_object_add_value_object(tmp_object, "percentile", percentile_object);
- for (i = 0; i < FIO_IO_U_LIST_MAX_LEN; i++) {
- if (i >= len) {
- json_object_add_value_int(percentile_object, "0.00", 0);
- continue;
- }
- snprintf(buf, sizeof(buf), "%f", ts->percentile_list[i].u.f);
- json_object_add_value_int(percentile_object, (const char *)buf, ovals[i]);
- }
-
- if (output_format & FIO_OUTPUT_JSON_PLUS) {
- clat_bins_object = json_create_object();
- json_object_add_value_object(tmp_object, "bins", clat_bins_object);
- for(i = 0; i < FIO_IO_U_PLAT_NR; i++) {
- snprintf(buf, sizeof(buf), "%d", i);
- json_object_add_value_int(clat_bins_object, (const char *)buf,
- ts->io_u_plat_high_prio[ddir][i]);
- }
- json_object_add_value_int(clat_bins_object, "FIO_IO_U_PLAT_BITS",
- FIO_IO_U_PLAT_BITS);
- json_object_add_value_int(clat_bins_object, "FIO_IO_U_PLAT_VAL",
- FIO_IO_U_PLAT_VAL);
- json_object_add_value_int(clat_bins_object, "FIO_IO_U_PLAT_NR",
- FIO_IO_U_PLAT_NR);
- }
- /* END OF HIGH PRIO CLAT */
-
- /* START OF PRIO CLAT */
- if (!calc_lat(&ts->clat_prio_stat[ddir], &min, &max, &mean, &dev)) {
- min = max = 0;
- mean = dev = 0.0;
- }
- tmp_object = json_create_object();
- json_object_add_value_object(dir_object, "clat_low_prio",
- tmp_object);
- json_object_add_value_int(tmp_object, "samples",
- ts->clat_prio_stat[ddir].samples);
- json_object_add_value_int(tmp_object, "min", min);
- json_object_add_value_int(tmp_object, "max", max);
- json_object_add_value_float(tmp_object, "mean", mean);
- json_object_add_value_float(tmp_object, "stddev", dev);
-
- if (ts->clat_percentiles) {
- len = calc_clat_percentiles(ts->io_u_plat_prio[ddir],
- ts->clat_prio_stat[ddir].samples,
- ts->percentile_list, &ovals, &maxv,
- &minv);
- } else
- len = 0;
-
- percentile_object = json_create_object();
- json_object_add_value_object(tmp_object, "percentile", percentile_object);
- for (i = 0; i < FIO_IO_U_LIST_MAX_LEN; i++) {
- if (i >= len) {
- json_object_add_value_int(percentile_object, "0.00", 0);
- continue;
- }
- snprintf(buf, sizeof(buf), "%f", ts->percentile_list[i].u.f);
- json_object_add_value_int(percentile_object, (const char *)buf, ovals[i]);
- }
+ const char *high, *low;

- if (output_format & FIO_OUTPUT_JSON_PLUS) {
- clat_bins_object = json_create_object();
- json_object_add_value_object(tmp_object, "bins", clat_bins_object);
- for(i = 0; i < FIO_IO_U_PLAT_NR; i++) {
- snprintf(buf, sizeof(buf), "%d", i);
- json_object_add_value_int(clat_bins_object, (const char *)buf,
- ts->io_u_plat_prio[ddir][i]);
- }
- json_object_add_value_int(clat_bins_object, "FIO_IO_U_PLAT_BITS",
- FIO_IO_U_PLAT_BITS);
- json_object_add_value_int(clat_bins_object, "FIO_IO_U_PLAT_VAL",
- FIO_IO_U_PLAT_VAL);
- json_object_add_value_int(clat_bins_object, "FIO_IO_U_PLAT_NR",
- FIO_IO_U_PLAT_NR);
+ if (ts->lat_percentiles) {
+ high = "lat_high_prio";
+ low = "lat_low_prio";
+ } else {
+ high = "clat_high_prio";
+ low = "clat_low_prio";
}
- /* END OF PRIO CLAT */
- }

- if (!ddir_rw(ddir))
- return;
+ tmp_object = add_ddir_lat_json(ts, ts->clat_percentiles | ts->lat_percentiles,
+ &ts->clat_high_prio_stat[ddir], ts->io_u_plat_high_prio[ddir]);
+ json_object_add_value_object(dir_object, high, tmp_object);

- if (!calc_lat(&ts->lat_stat[ddir], &min, &max, &mean, &dev)) {
- min = max = 0;
- mean = dev = 0.0;
+ tmp_object = add_ddir_lat_json(ts, ts->clat_percentiles | ts->lat_percentiles,
+ &ts->clat_low_prio_stat[ddir], ts->io_u_plat_low_prio[ddir]);
+ json_object_add_value_object(dir_object, low, tmp_object);
}
- tmp_object = json_create_object();
- json_object_add_value_object(dir_object, "lat_ns", tmp_object);
- json_object_add_value_int(tmp_object, "min", min);
- json_object_add_value_int(tmp_object, "max", max);
- json_object_add_value_float(tmp_object, "mean", mean);
- json_object_add_value_float(tmp_object, "stddev", dev);
- if (ts->lat_percentiles)
- json_object_add_value_object(tmp_object, "percentile", percentile_object);
- if (output_format & FIO_OUTPUT_JSON_PLUS && ts->lat_percentiles)
- json_object_add_value_object(tmp_object, "bins", clat_bins_object);

if (calc_lat(&ts->bw_stat[ddir], &min, &max, &mean, &dev)) {
if (rs->agg[ddir]) {
@@ -1493,6 +1381,7 @@ static void add_ddir_status_json(struct thread_stat *ts,
min = max = 0;
p_of_agg = mean = dev = 0.0;
}
+
json_object_add_value_int(dir_object, "bw_min", min);
json_object_add_value_int(dir_object, "bw_max", max);
json_object_add_value_float(dir_object, "bw_agg", p_of_agg);
@@ -1981,13 +1870,13 @@ void sum_group_stats(struct group_run_stats *dst, struct group_run_stats *src)
void sum_thread_stats(struct thread_stat *dst, struct thread_stat *src,
bool first)
{
- int l, k;
+ int k, l, m;

for (l = 0; l < DDIR_RWDIR_CNT; l++) {
if (!dst->unified_rw_rep) {
sum_stat(&dst->clat_stat[l], &src->clat_stat[l], first, false);
sum_stat(&dst->clat_high_prio_stat[l], &src->clat_high_prio_stat[l], first, false);
- sum_stat(&dst->clat_prio_stat[l], &src->clat_prio_stat[l], first, false);
+ sum_stat(&dst->clat_low_prio_stat[l], &src->clat_low_prio_stat[l], first, false);
sum_stat(&dst->slat_stat[l], &src->slat_stat[l], first, false);
sum_stat(&dst->lat_stat[l], &src->lat_stat[l], first, false);
sum_stat(&dst->bw_stat[l], &src->bw_stat[l], first, true);
@@ -1999,8 +1888,8 @@ void sum_thread_stats(struct thread_stat *dst, struct thread_stat *src,
dst->runtime[l] = src->runtime[l];
} else {
sum_stat(&dst->clat_stat[0], &src->clat_stat[l], first, false);
- sum_stat(&dst->clat_high_prio_stat[l], &src->clat_high_prio_stat[l], first, false);
- sum_stat(&dst->clat_prio_stat[l], &src->clat_prio_stat[l], first, false);
+ sum_stat(&dst->clat_high_prio_stat[0], &src->clat_high_prio_stat[l], first, false);
+ sum_stat(&dst->clat_low_prio_stat[0], &src->clat_low_prio_stat[l], first, false);
sum_stat(&dst->slat_stat[0], &src->slat_stat[l], first, false);
sum_stat(&dst->lat_stat[0], &src->lat_stat[l], first, false);
sum_stat(&dst->bw_stat[0], &src->bw_stat[l], first, true);
@@ -2039,9 +1928,6 @@ void sum_thread_stats(struct thread_stat *dst, struct thread_stat *src,
for (k = 0; k < FIO_IO_U_LAT_M_NR; k++)
dst->io_u_lat_m[k] += src->io_u_lat_m[k];

- for (k = 0; k < FIO_IO_U_PLAT_NR; k++)
- dst->io_u_sync_plat[k] += src->io_u_sync_plat[k];
-
for (k = 0; k < DDIR_RWDIR_CNT; k++) {
if (!dst->unified_rw_rep) {
dst->total_io_u[k] += src->total_io_u[k];
@@ -2056,18 +1942,25 @@ void sum_thread_stats(struct thread_stat *dst, struct thread_stat *src,

dst->total_io_u[DDIR_SYNC] += src->total_io_u[DDIR_SYNC];

- for (k = 0; k < DDIR_RWDIR_CNT; k++) {
- int m;
+ for (k = 0; k < FIO_LAT_CNT; k++)
+ for (l = 0; l < DDIR_RWDIR_CNT; l++)
+ for (m = 0; m < FIO_IO_U_PLAT_NR; m++)
+ if (!dst->unified_rw_rep)
+ dst->io_u_plat[k][l][m] += src->io_u_plat[k][l][m];
+ else
+ dst->io_u_plat[k][0][m] += src->io_u_plat[k][l][m];

+ for (k = 0; k < FIO_IO_U_PLAT_NR; k++)
+ dst->io_u_sync_plat[k] += src->io_u_sync_plat[k];
+
+ for (k = 0; k < DDIR_RWDIR_CNT; k++) {
for (m = 0; m < FIO_IO_U_PLAT_NR; m++) {
if (!dst->unified_rw_rep) {
- dst->io_u_plat[k][m] += src->io_u_plat[k][m];
dst->io_u_plat_high_prio[k][m] += src->io_u_plat_high_prio[k][m];
- dst->io_u_plat_prio[k][m] += src->io_u_plat_prio[k][m];
+ dst->io_u_plat_low_prio[k][m] += src->io_u_plat_low_prio[k][m];
} else {
- dst->io_u_plat[0][m] += src->io_u_plat[k][m];
dst->io_u_plat_high_prio[0][m] += src->io_u_plat_high_prio[k][m];
- dst->io_u_plat_prio[0][m] += src->io_u_plat_prio[k][m];
+ dst->io_u_plat_low_prio[0][m] += src->io_u_plat_low_prio[k][m];
}

}
@@ -2103,7 +1996,7 @@ void init_thread_stat(struct thread_stat *ts)
ts->bw_stat[j].min_val = -1UL;
ts->iops_stat[j].min_val = -1UL;
ts->clat_high_prio_stat[j].min_val = -1UL;
- ts->clat_prio_stat[j].min_val = -1UL;
+ ts->clat_low_prio_stat[j].min_val = -1UL;
}
ts->sync_stat.min_val = -1UL;
ts->groupid = -1;
@@ -2173,6 +2066,7 @@ void __show_run_stats(void)

ts->clat_percentiles = td->o.clat_percentiles;
ts->lat_percentiles = td->o.lat_percentiles;
+ ts->slat_percentiles = td->o.slat_percentiles;
ts->percentile_precision = td->o.percentile_precision;
memcpy(ts->percentile_list, td->o.percentile_list, sizeof(td->o.percentile_list));
opt_lists[j] = &td->opt_list;
@@ -2728,11 +2622,11 @@ static inline void reset_io_stat(struct io_stat *ios)
void reset_io_stats(struct thread_data *td)
{
struct thread_stat *ts = &td->ts;
- int i, j;
+ int i, j, k;

for (i = 0; i < DDIR_RWDIR_CNT; i++) {
reset_io_stat(&ts->clat_high_prio_stat[i]);
- reset_io_stat(&ts->clat_prio_stat[i]);
+ reset_io_stat(&ts->clat_low_prio_stat[i]);
reset_io_stat(&ts->clat_stat[i]);
reset_io_stat(&ts->slat_stat[i]);
reset_io_stat(&ts->lat_stat[i]);
@@ -2746,14 +2640,18 @@ void reset_io_stats(struct thread_data *td)
ts->drop_io_u[i] = 0;

for (j = 0; j < FIO_IO_U_PLAT_NR; j++) {
- ts->io_u_plat[i][j] = 0;
ts->io_u_plat_high_prio[i][j] = 0;
- ts->io_u_plat_prio[i][j] = 0;
+ ts->io_u_plat_low_prio[i][j] = 0;
if (!i)
ts->io_u_sync_plat[j] = 0;
}
}

+ for (i = 0; i < FIO_LAT_CNT; i++)
+ for (j = 0; j < DDIR_RWDIR_CNT; j++)
+ for (k = 0; k < FIO_IO_U_PLAT_NR; k++)
+ ts->io_u_plat[i][j][k] = 0;
+
ts->total_io_u[DDIR_SYNC] = 0;

for (i = 0; i < FIO_IO_U_MAP_NR; i++) {
@@ -2892,19 +2790,27 @@ void add_sync_clat_sample(struct thread_stat *ts, unsigned long long nsec)
add_stat_sample(&ts->sync_stat, nsec);
}

-static void add_clat_percentile_sample(struct thread_stat *ts,
- unsigned long long nsec, enum fio_ddir ddir, uint8_t priority_bit)
+static void add_lat_percentile_sample_noprio(struct thread_stat *ts,
+ unsigned long long nsec, enum fio_ddir ddir, enum fio_lat lat)
{
unsigned int idx = plat_val_to_idx(nsec);
assert(idx < FIO_IO_U_PLAT_NR);

- ts->io_u_plat[ddir][idx]++;
+ ts->io_u_plat[lat][ddir][idx]++;
+}

- if (!priority_bit) {
- ts->io_u_plat_prio[ddir][idx]++;
- } else {
+static void add_lat_percentile_sample(struct thread_stat *ts,
+ unsigned long long nsec, enum fio_ddir ddir, uint8_t priority_bit,
+ enum fio_lat lat)
+{
+ unsigned int idx = plat_val_to_idx(nsec);
+
+ add_lat_percentile_sample_noprio(ts, nsec, ddir, lat);
+
+ if (!priority_bit)
+ ts->io_u_plat_low_prio[ddir][idx]++;
+ else
ts->io_u_plat_high_prio[ddir][idx]++;
- }
}

void add_clat_sample(struct thread_data *td, enum fio_ddir ddir,
@@ -2921,10 +2827,11 @@ void add_clat_sample(struct thread_data *td, enum fio_ddir ddir,

add_stat_sample(&ts->clat_stat[ddir], nsec);

- if (priority_bit) {
- add_stat_sample(&ts->clat_high_prio_stat[ddir], nsec);
- } else {
- add_stat_sample(&ts->clat_prio_stat[ddir], nsec);
+ if (!ts->lat_percentiles) {
+ if (priority_bit)
+ add_stat_sample(&ts->clat_high_prio_stat[ddir], nsec);
+ else
+ add_stat_sample(&ts->clat_low_prio_stat[ddir], nsec);
}

if (td->clat_log)
@@ -2932,7 +2839,10 @@ void add_clat_sample(struct thread_data *td, enum fio_ddir ddir,
offset, priority_bit);

if (ts->clat_percentiles) {
- add_clat_percentile_sample(ts, nsec, ddir, priority_bit);
+ if (ts->lat_percentiles)
+ add_lat_percentile_sample_noprio(ts, nsec, ddir, FIO_CLAT);
+ else
+ add_lat_percentile_sample(ts, nsec, ddir, priority_bit, FIO_CLAT);
}

if (iolog && iolog->hist_msec) {
@@ -2955,7 +2865,7 @@ void add_clat_sample(struct thread_data *td, enum fio_ddir ddir,
* located in iolog.c after printing this sample to the
* log file.
*/
- io_u_plat = (uint64_t *) td->ts.io_u_plat[ddir];
+ io_u_plat = (uint64_t *) td->ts.io_u_plat[FIO_CLAT][ddir];
dst = malloc(sizeof(struct io_u_plat_entry));
memcpy(&(dst->io_u_plat), io_u_plat,
FIO_IO_U_PLAT_NR * sizeof(uint64_t));
@@ -2978,7 +2888,7 @@ void add_clat_sample(struct thread_data *td, enum fio_ddir ddir,
}

void add_slat_sample(struct thread_data *td, enum fio_ddir ddir,
- unsigned long usec, unsigned long long bs, uint64_t offset,
+ unsigned long long nsec, unsigned long long bs, uint64_t offset,
uint8_t priority_bit)
{
const bool needs_lock = td_async_processing(td);
@@ -2990,12 +2900,15 @@ void add_slat_sample(struct thread_data *td, enum fio_ddir ddir,
if (needs_lock)
__td_io_u_lock(td);

- add_stat_sample(&ts->slat_stat[ddir], usec);
+ add_stat_sample(&ts->slat_stat[ddir], nsec);

if (td->slat_log)
- add_log_sample(td, td->slat_log, sample_val(usec), ddir, bs, offset,
+ add_log_sample(td, td->slat_log, sample_val(nsec), ddir, bs, offset,
priority_bit);

+ if (ts->slat_percentiles)
+ add_lat_percentile_sample_noprio(ts, nsec, ddir, FIO_SLAT);
+
if (needs_lock)
__td_io_u_unlock(td);
}
@@ -3019,9 +2932,14 @@ void add_lat_sample(struct thread_data *td, enum fio_ddir ddir,
add_log_sample(td, td->lat_log, sample_val(nsec), ddir, bs,
offset, priority_bit);

- if (ts->lat_percentiles)
- add_clat_percentile_sample(ts, nsec, ddir, priority_bit);
+ if (ts->lat_percentiles) {
+ add_lat_percentile_sample(ts, nsec, ddir, priority_bit, FIO_LAT);
+ if (priority_bit)
+ add_stat_sample(&ts->clat_high_prio_stat[ddir], nsec);
+ else
+ add_stat_sample(&ts->clat_low_prio_stat[ddir], nsec);

+ }
if (needs_lock)
__td_io_u_unlock(td);
}
diff --git a/stat.h b/stat.h
index 9320c6bd..0d141666 100644
--- a/stat.h
+++ b/stat.h
@@ -37,7 +37,7 @@ struct group_run_stats {
list of percentiles */

/*
- * Aggregate clat samples to report percentile(s) of them.
+ * Aggregate latency samples for reporting percentile(s).
*
* EXECUTIVE SUMMARY
*
@@ -58,7 +58,7 @@ struct group_run_stats {
*
* DETAILS
*
- * Suppose the clat varies from 0 to 999 (usec), the straightforward
+ * Suppose the lat varies from 0 to 999 (usec), the straightforward
* method is to keep an array of (999 + 1) buckets, in which a counter
* keeps the count of samples which fall in the bucket, e.g.,
* {[0],[1],...,[999]}. However this consumes a huge amount of space,
@@ -147,6 +147,14 @@ enum block_info_state {
#define FIO_JOBDESC_SIZE 256
#define FIO_VERROR_SIZE 128

+enum fio_lat {
+ FIO_SLAT = 0,
+ FIO_CLAT,
+ FIO_LAT,
+
+ FIO_LAT_CNT = 3,
+};
+
struct thread_stat {
char name[FIO_JOBNAME_SIZE];
char verror[FIO_VERROR_SIZE];
@@ -181,6 +189,8 @@ struct thread_stat {
*/
uint32_t clat_percentiles;
uint32_t lat_percentiles;
+ uint32_t slat_percentiles;
+ uint32_t pad;
uint64_t percentile_precision;
fio_fp64_t percentile_list[FIO_IO_U_LIST_MAX_LEN];

@@ -190,7 +200,7 @@ struct thread_stat {
uint64_t io_u_lat_n[FIO_IO_U_LAT_N_NR];
uint64_t io_u_lat_u[FIO_IO_U_LAT_U_NR];
uint64_t io_u_lat_m[FIO_IO_U_LAT_M_NR];
- uint64_t io_u_plat[DDIR_RWDIR_CNT][FIO_IO_U_PLAT_NR];
+ uint64_t io_u_plat[FIO_LAT_CNT][DDIR_RWDIR_CNT][FIO_IO_U_PLAT_NR];
uint64_t io_u_sync_plat[FIO_IO_U_PLAT_NR];

uint64_t total_io_u[DDIR_RWDIR_SYNC_CNT];
@@ -240,9 +250,9 @@ struct thread_stat {
fio_fp64_t ss_criterion;

uint64_t io_u_plat_high_prio[DDIR_RWDIR_CNT][FIO_IO_U_PLAT_NR] __attribute__((aligned(8)));;
- uint64_t io_u_plat_prio[DDIR_RWDIR_CNT][FIO_IO_U_PLAT_NR];
+ uint64_t io_u_plat_low_prio[DDIR_RWDIR_CNT][FIO_IO_U_PLAT_NR];
struct io_stat clat_high_prio_stat[DDIR_RWDIR_CNT] __attribute__((aligned(8)));
- struct io_stat clat_prio_stat[DDIR_RWDIR_CNT];
+ struct io_stat clat_low_prio_stat[DDIR_RWDIR_CNT];

union {
uint64_t *ss_iops_data;
@@ -331,7 +341,7 @@ extern void add_lat_sample(struct thread_data *, enum fio_ddir, unsigned long lo
unsigned long long, uint64_t, uint8_t);
extern void add_clat_sample(struct thread_data *, enum fio_ddir, unsigned long long,
unsigned long long, uint64_t, uint8_t);
-extern void add_slat_sample(struct thread_data *, enum fio_ddir, unsigned long,
+extern void add_slat_sample(struct thread_data *, enum fio_ddir, unsigned long long,
unsigned long long, uint64_t, uint8_t);
extern void add_agg_sample(union io_sample_data, enum fio_ddir, unsigned long long bs,
uint8_t priority_bit);
diff --git a/t/latency_percentiles.py b/t/latency_percentiles.py
new file mode 100755
index 00000000..0c8d0c19
--- /dev/null
+++ b/t/latency_percentiles.py
@@ -0,0 +1,1324 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Copyright (c) 2020 Western Digital Corporation or its affiliates.
+#
+"""
+# latency_percentiles.py
+#
+# Test the code that produces latency percentiles
+# This is mostly to test the code changes to allow reporting
+# of slat, clat, and lat percentiles
+#
+# USAGE
+# python3 latency-tests.py [-f fio-path] [-a artifact-root] [--debug]
+#
+#
+# Test scenarios:
+#
+# - DONE json
+# unified rw reporting
+# compare with latency log
+# try various combinations of the ?lat_percentile options
+# null, aio
+# r, w, t
+# - DONE json+
+# check presence of latency bins
+# if the json percentiles match those from the raw data
+# then the latency bin values and counts are probably ok
+# - DONE terse
+# produce both terse, JSON output and confirm that they match
+# lat only; both lat and clat
+# - DONE sync_lat
+# confirm that sync_lat data appears
+# - MANUAL TESTING normal output:
+# null ioengine
+# enable all, but only clat and lat appear
+# enable subset of latency types
+# read, write, trim, unified
+# libaio ioengine
+# enable all latency types
+# enable subset of latency types
+# read, write, trim, unified
+# ./fio/fio --name=test --randrepeat=0 --norandommap --time_based --runtime=2s --size=512M \
+# --ioengine=null --slat_percentiles=1 --clat_percentiles=1 --lat_percentiles=1
+# echo confirm that clat and lat percentiles appear
+# ./fio/fio --name=test --randrepeat=0 --norandommap --time_based --runtime=2s --size=512M \
+# --ioengine=null --slat_percentiles=0 --clat_percentiles=0 --lat_percentiles=1
+# echo confirm that only lat percentiles appear
+# ./fio/fio --name=test --randrepeat=0 --norandommap --time_based --runtime=2s --size=512M \
+# --ioengine=null --slat_percentiles=0 --clat_percentiles=1 --lat_percentiles=0
+# echo confirm that only clat percentiles appear
+# ./fio/fio --name=test --randrepeat=0 --norandommap --time_based --runtime=2s --size=512M \
+# --ioengine=libaio --slat_percentiles=1 --clat_percentiles=1 --lat_percentiles=1
+# echo confirm that slat, clat, lat percentiles appear
+# ./fio/fio --name=test --randrepeat=0 --norandommap --time_based --runtime=2s --size=512M \
+# --ioengine=libaio --slat_percentiles=0 --clat_percentiles=1 --lat_percentiles=1
+# echo confirm that clat and lat percentiles appear
+# ./fio/fio --name=test --randrepeat=0 --norandommap --time_based --runtime=2s --size=512M \
+# --ioengine=libaio -rw=randrw
+# echo confirm that clat percentiles appear for reads and writes
+# ./fio/fio --name=test --randrepeat=0 --norandommap --time_based --runtime=2s --size=512M \
+# --ioengine=libaio --slat_percentiles=1 --clat_percentiles=0 --lat_percentiles=0 --rw=randrw
+# echo confirm that slat percentiles appear for both reads and writes
+# ./fio/fio --name=test --randrepeat=0 --norandommap --time_based --runtime=2s --size=512M \
+# --ioengine=libaio --slat_percentiles=1 --clat_percentiles=1 --lat_percentiles=1 \
+# --rw=randrw --unified_rw_reporting=1
+# echo confirm that slat, clat, and lat percentiles appear for 'mixed' IOs
+#./fio/fio --name=test --randrepeat=0 --norandommap --time_based --runtime=2s --size=512M \
+# --ioengine=null --slat_percentiles=1 --clat_percentiles=1 --lat_percentiles=1 \
+# --rw=randrw --fsync=32
+# echo confirm that fsync latencies appear
+"""
+
+import os
+import csv
+import sys
+import json
+import math
+import time
+import argparse
+import platform
+import subprocess
+from pathlib import Path
+
+
+class FioLatTest():
+ """fio latency percentile test."""
+
+ def __init__(self, artifact_root, test_options, debug):
+ """
+ artifact_root root directory for artifacts (subdirectory will be created under here)
+ test test specification
+ """
+ self.artifact_root = artifact_root
+ self.test_options = test_options
+ self.debug = debug
+ self.filename = None
+ self.json_data = None
+ self.terse_data = None
+
+ self.test_dir = os.path.join(self.artifact_root,
+ "{:03d}".format(self.test_options['test_id']))
+ if not os.path.exists(self.test_dir):
+ os.mkdir(self.test_dir)
+
+ self.filename = "latency{:03d}".format(self.test_options['test_id'])
+
+ def run_fio(self, fio_path):
+ """Run a test."""
+
+ fio_args = [
+ "--name=latency",
+ "--randrepeat=0",
+ "--norandommap",
+ "--time_based",
+ "--size=16M",
+ "--rwmixread=50",
+ "--group_reporting=1",
+ "--write_lat_log={0}".format(self.filename),
+ "--output={0}.out".format(self.filename),
+ "--ioengine={ioengine}".format(**self.test_options),
+ "--rw={rw}".format(**self.test_options),
+ "--runtime={runtime}".format(**self.test_options),
+ "--output-format={output-format}".format(**self.test_options),
+ ]
+ for opt in ['slat_percentiles', 'clat_percentiles', 'lat_percentiles',
+ 'unified_rw_reporting', 'fsync', 'fdatasync', 'numjobs', 'cmdprio_percentage']:
+ if opt in self.test_options:
+ option = '--{0}={{{0}}}'.format(opt)
+ fio_args.append(option.format(**self.test_options))
+
+ command = [fio_path] + fio_args
+ with open(os.path.join(self.test_dir, "{0}.command".format(self.filename)), "w+") as \
+ command_file:
+ command_file.write("%s\n" % command)
+
+ passed = True
+ stdout_file = open(os.path.join(self.test_dir, "{0}.stdout".format(self.filename)), "w+")
+ stderr_file = open(os.path.join(self.test_dir, "{0}.stderr".format(self.filename)), "w+")
+ exitcode_file = open(os.path.join(self.test_dir,
+ "{0}.exitcode".format(self.filename)), "w+")
+ try:
+ proc = None
+ # Avoid using subprocess.run() here because when a timeout occurs,
+ # fio will be stopped with SIGKILL. This does not give fio a
+ # chance to clean up and means that child processes may continue
+ # running and submitting IO.
+ proc = subprocess.Popen(command,
+ stdout=stdout_file,
+ stderr=stderr_file,
+ cwd=self.test_dir,
+ universal_newlines=True)
+ proc.communicate(timeout=300)
+ exitcode_file.write('{0}\n'.format(proc.returncode))
+ passed &= (proc.returncode == 0)
+ except subprocess.TimeoutExpired:
+ proc.terminate()
+ proc.communicate()
+ assert proc.poll()
+ print("Timeout expired")
+ passed = False
+ except Exception:
+ if proc:
+ if not proc.poll():
+ proc.terminate()
+ proc.communicate()
+ print("Exception: %s" % sys.exc_info())
+ passed = False
+ finally:
+ stdout_file.close()
+ stderr_file.close()
+ exitcode_file.close()
+
+ if passed:
+ if 'json' in self.test_options['output-format']:
+ if not self.get_json():
+ print('Unable to decode JSON data')
+ passed = False
+ if 'terse' in self.test_options['output-format']:
+ if not self.get_terse():
+ print('Unable to decode terse data')
+ passed = False
+
+ return passed
+
+ def get_json(self):
+ """Convert fio JSON output into a python JSON object"""
+
+ filename = os.path.join(self.test_dir, "{0}.out".format(self.filename))
+ with open(filename, 'r') as file:
+ file_data = file.read()
+
+ #
+ # Sometimes fio informational messages are included at the top of the
+ # JSON output, especially under Windows. Try to decode output as JSON
+ # data, lopping off up to the first four lines
+ #
+ lines = file_data.splitlines()
+ for i in range(5):
+ file_data = '\n'.join(lines[i:])
+ try:
+ self.json_data = json.loads(file_data)
+ except json.JSONDecodeError:
+ continue
+ else:
+ return True
+
+ return False
+
+ def get_terse(self):
+ """Read fio output and return terse format data."""
+
+ filename = os.path.join(self.test_dir, "{0}.out".format(self.filename))
+ with open(filename, 'r') as file:
+ file_data = file.read()
+
+ #
+ # Read the first few lines and see if any of them begin with '3;fio-'
+ # If so, the line is probably terse output. Obviously, this only
+ # works for fio terse version 3 and it does not work for
+ # multi-line terse output
+ #
+ lines = file_data.splitlines()
+ for i in range(8):
+ file_data = lines[i]
+ if file_data.startswith('3;fio-'):
+ self.terse_data = file_data.split(';')
+ return True
+
+ return False
+
+ def check_latencies(self, jsondata, ddir, slat=True, clat=True, tlat=True, plus=False,
+ unified=False):
+ """Check fio latency data.
+
+ ddir data direction to check (0=read, 1=write, 2=trim)
+ slat True if submission latency data available to check
+ clat True if completion latency data available to check
+ tlat True of total latency data available to check
+ plus True if we actually have json+ format data where additional checks can
+ be carried out
+ unified True if fio is reporting unified r/w data
+ """
+
+ types = {
+ 'slat': slat,
+ 'clat': clat,
+ 'lat': tlat
+ }
+
+ retval = True
+
+ for lat in ['slat', 'clat', 'lat']:
+ this_iter = True
+ if not types[lat]:
+ if 'percentile' in jsondata[lat+'_ns']:
+ this_iter = False
+ print('unexpected %s percentiles found' % lat)
+ else:
+ print("%s percentiles skipped" % lat)
+ continue
+ else:
+ if 'percentile' not in jsondata[lat+'_ns']:
+ this_iter = False
+ print('%s percentiles not found in fio output' % lat)
+
+ #
+ # Check only for the presence/absence of json+
+ # latency bins. Future work can check the
+ # accurracy of the bin values and counts.
+ #
+ # Because the latency percentiles are based on
+ # the bins, we can be confident that the bin
+ # values and counts are correct if fio's
+ # latency percentiles match what we compute
+ # from the raw data.
+ #
+ if plus:
+ if 'bins' not in jsondata[lat+'_ns']:
+ print('bins not found with json+ output format')
+ this_iter = False
+ else:
+ if not self.check_jsonplus(jsondata[lat+'_ns']):
+ this_iter = False
+ else:
+ if 'bins' in jsondata[lat+'_ns']:
+ print('json+ bins found with json output format')
+ this_iter = False
+
+ latencies = []
+ for i in range(10):
+ lat_file = os.path.join(self.test_dir, "%s_%s.%s.log" % (self.filename, lat, i+1))
+ if not os.path.exists(lat_file):
+ break
+ with open(lat_file, 'r', newline='') as file:
+ reader = csv.reader(file)
+ for line in reader:
+ if unified or int(line[2]) == ddir:
+ latencies.append(int(line[1]))
+
+ if int(jsondata['total_ios']) != len(latencies):
+ this_iter = False
+ print('%s: total_ios = %s, latencies logged = %d' % \
+ (lat, jsondata['total_ios'], len(latencies)))
+ elif self.debug:
+ print("total_ios %s match latencies logged" % jsondata['total_ios'])
+
+ latencies.sort()
+ ptiles = jsondata[lat+'_ns']['percentile']
+
+ for percentile in ptiles.keys():
+ #
+ # numpy.percentile(latencies, float(percentile),
+ # interpolation='higher')
+ # produces values that mostly match what fio reports
+ # however, in the tails of the distribution, the values produced
+ # by fio's and numpy.percentile's algorithms are occasionally off
+ # by one latency measurement. So instead of relying on the canned
+ # numpy.percentile routine, implement here fio's algorithm
+ #
+ rank = math.ceil(float(percentile)/100 * len(latencies))
+ if rank > 0:
+ index = rank - 1
+ else:
+ index = 0
+ value = latencies[int(index)]
+ fio_val = int(ptiles[percentile])
+ # The theory in stat.h says that the proportional error will be
+ # less than 1/128
+ if not self.similar(fio_val, value):
+ delta = abs(fio_val - value) / value
+ print("Error with %s %sth percentile: "
+ "fio: %d, expected: %d, proportional delta: %f" %
+ (lat, percentile, fio_val, value, delta))
+ print("Rank: %d, index: %d" % (rank, index))
+ this_iter = False
+ elif self.debug:
+ print('%s %sth percentile values match: %d, %d' %
+ (lat, percentile, fio_val, value))
+
+ if this_iter:
+ print("%s percentiles match" % lat)
+ else:
+ retval = False
+
+ return retval
+
+ @staticmethod
+ def check_empty(job):
+ """
+ Make sure JSON data is empty.
+
+ Some data structures should be empty. This function makes sure that they are.
+
+ job JSON object that we need to check for emptiness
+ """
+
+ return job['total_ios'] == 0 and \
+ job['slat_ns']['N'] == 0 and \
+ job['clat_ns']['N'] == 0 and \
+ job['lat_ns']['N'] == 0
+
+ def check_nocmdprio_lat(self, job):
+ """
+ Make sure no high/low priority latencies appear.
+
+ job JSON object to check
+ """
+
+ for ddir in ['read', 'write', 'trim']:
+ if ddir in job:
+ if 'lat_high_prio' in job[ddir] or 'lat_low_prio' in job[ddir] or \
+ 'clat_high_prio' in job[ddir] or 'clat_low_prio' in job[ddir]:
+ print("Unexpected high/low priority latencies found in %s output" % ddir)
+ return False
+
+ if self.debug:
+ print("No high/low priority latencies found")
+
+ return True
+
+ @staticmethod
+ def similar(approximation, actual):
+ """
+ Check whether the approximate values recorded by fio are within the theoretical bound.
+
+ Since it is impractical to store exact latency measurements for each and every IO, fio
+ groups similar latency measurements into variable-sized bins. The theory in stat.h says
+ that the proportional error will be less than 1/128. This function checks whether this
+ is true.
+
+ TODO This test will fail when comparing a value from the largest latency bin against its
+ actual measurement. Find some way to detect this and avoid failing.
+
+ approximation value of the bin used by fio to store a given latency
+ actual actual latency value
+ """
+ delta = abs(approximation - actual) / actual
+ return delta <= 1/128
+
+ def check_jsonplus(self, jsondata):
+ """Check consistency of json+ data
+
+ When we have json+ data we can check the min value, max value, and
+ sample size reported by fio
+
+ jsondata json+ data that we need to check
+ """
+
+ retval = True
+
+ keys = [int(k) for k in jsondata['bins'].keys()]
+ values = [int(jsondata['bins'][k]) for k in jsondata['bins'].keys()]
+ smallest = min(keys)
+ biggest = max(keys)
+ sampsize = sum(values)
+
+ if not self.similar(jsondata['min'], smallest):
+ retval = False
+ print('reported min %d does not match json+ min %d' % (jsondata['min'], smallest))
+ elif self.debug:
+ print('json+ min values match: %d' % jsondata['min'])
+
+ if not self.similar(jsondata['max'], biggest):
+ retval = False
+ print('reported max %d does not match json+ max %d' % (jsondata['max'], biggest))
+ elif self.debug:
+ print('json+ max values match: %d' % jsondata['max'])
+
+ if sampsize != jsondata['N']:
+ retval = False
+ print('reported sample size %d does not match json+ total count %d' % \
+ (jsondata['N'], sampsize))
+ elif self.debug:
+ print('json+ sample sizes match: %d' % sampsize)
+
+ return retval
+
+ def check_sync_lat(self, jsondata, plus=False):
+ """Check fsync latency percentile data.
+
+ All we can check is that some percentiles are reported, unless we have json+ data.
+ If we actually have json+ data then we can do more checking.
+
+ jsondata JSON data for fsync operations
+ plus True if we actually have json+ data
+ """
+ retval = True
+
+ if 'percentile' not in jsondata['lat_ns']:
+ print("Sync percentile data not found")
+ return False
+
+ if int(jsondata['total_ios']) != int(jsondata['lat_ns']['N']):
+ retval = False
+ print('Mismatch between total_ios and lat_ns sample size')
+ elif self.debug:
+ print('sync sample sizes match: %d' % jsondata['total_ios'])
+
+ if not plus:
+ if 'bins' in jsondata['lat_ns']:
+ print('Unexpected json+ bin data found')
+ return False
+
+ if not self.check_jsonplus(jsondata['lat_ns']):
+ retval = False
+
+ return retval
+
+ def check_terse(self, terse, jsondata):
+ """Compare terse latencies with JSON latencies.
+
+ terse terse format data for checking
+ jsondata JSON format data for checking
+ """
+
+ retval = True
+
+ for lat in terse:
+ split = lat.split('%')
+ pct = split[0]
+ terse_val = int(split[1][1:])
+ json_val = math.floor(jsondata[pct]/1000)
+ if terse_val != json_val:
+ retval = False
+ print('Mismatch with %sth percentile: json value=%d,%d terse value=%d' % \
+ (pct, jsondata[pct], json_val, terse_val))
+ elif self.debug:
+ print('Terse %sth percentile matches JSON value: %d' % (pct, terse_val))
+
+ return retval
+
+ def check_prio_latencies(self, jsondata, clat=True, plus=False):
+ """Check consistency of high/low priority latencies.
+
+ clat True if we should check clat data; other check lat data
+ plus True if we have json+ format data where additional checks can
+ be carried out
+ unified True if fio is reporting unified r/w data
+ """
+
+ if clat:
+ high = 'clat_high_prio'
+ low = 'clat_low_prio'
+ combined = 'clat_ns'
+ else:
+ high = 'lat_high_prio'
+ low = 'lat_low_prio'
+ combined = 'lat_ns'
+
+ if not high in jsondata or not low in jsondata or not combined in jsondata:
+ print("Error identifying high/low priority latencies")
+ return False
+
+ if jsondata[high]['N'] + jsondata[low]['N'] != jsondata[combined]['N']:
+ print("High %d + low %d != combined sample size %d" % \
+ (jsondata[high]['N'], jsondata[low]['N'], jsondata[combined]['N']))
+ return False
+ elif self.debug:
+ print("High %d + low %d == combined sample size %d" % \
+ (jsondata[high]['N'], jsondata[low]['N'], jsondata[combined]['N']))
+
+ if min(jsondata[high]['min'], jsondata[low]['min']) != jsondata[combined]['min']:
+ print("Min of high %d, low %d min latencies does not match min %d from combined data" % \
+ (jsondata[high]['min'], jsondata[low]['min'], jsondata[combined]['min']))
+ return False
+ elif self.debug:
+ print("Min of high %d, low %d min latencies matches min %d from combined data" % \
+ (jsondata[high]['min'], jsondata[low]['min'], jsondata[combined]['min']))
+
+ if max(jsondata[high]['max'], jsondata[low]['max']) != jsondata[combined]['max']:
+ print("Max of high %d, low %d max latencies does not match max %d from combined data" % \
+ (jsondata[high]['max'], jsondata[low]['max'], jsondata[combined]['max']))
+ return False
+ elif self.debug:
+ print("Max of high %d, low %d max latencies matches max %d from combined data" % \
+ (jsondata[high]['max'], jsondata[low]['max'], jsondata[combined]['max']))
+
+ weighted_avg = (jsondata[high]['mean'] * jsondata[high]['N'] + \
+ jsondata[low]['mean'] * jsondata[low]['N']) / jsondata[combined]['N']
+ delta = abs(weighted_avg - jsondata[combined]['mean'])
+ if (delta / jsondata[combined]['mean']) > 0.0001:
+ print("Difference between weighted average %f of high, low means "
+ "and actual mean %f exceeds 0.01%%" % (weighted_avg, jsondata[combined]['mean']))
+ return False
+ elif self.debug:
+ print("Weighted average %f of high, low means matches actual mean %f" % \
+ (weighted_avg, jsondata[combined]['mean']))
+
+ if plus:
+ if not self.check_jsonplus(jsondata[high]):
+ return False
+ if not self.check_jsonplus(jsondata[low]):
+ return False
+
+ bins = {**jsondata[high]['bins'], **jsondata[low]['bins']}
+ for duration in bins.keys():
+ if duration in jsondata[high]['bins'] and duration in jsondata[low]['bins']:
+ bins[duration] = jsondata[high]['bins'][duration] + \
+ jsondata[low]['bins'][duration]
+
+ if len(bins) != len(jsondata[combined]['bins']):
+ print("Number of combined high/low bins does not match number of overall bins")
+ return False
+ elif self.debug:
+ print("Number of bins from merged high/low data matches number of overall bins")
+
+ for duration in bins.keys():
+ if bins[duration] != jsondata[combined]['bins'][duration]:
+ print("Merged high/low count does not match overall count for duration %d" \
+ % duration)
+ return False
+
+ print("Merged high/low priority latency data match combined latency data")
+ return True
+
+ def check(self):
+ """Check test output."""
+
+ raise NotImplementedError()
+
+
+class Test001(FioLatTest):
+ """Test object for Test 1."""
+
+ def check(self):
+ """Check Test 1 output."""
+
+ job = self.json_data['jobs'][0]
+
+ retval = True
+ if not self.check_empty(job['write']):
+ print("Unexpected write data found in output")
+ retval = False
+ if not self.check_empty(job['trim']):
+ print("Unexpected trim data found in output")
+ retval = False
+ if not self.check_nocmdprio_lat(job):
+ print("Unexpected high/low priority latencies found")
+ retval = False
+
+ retval &= self.check_latencies(job['read'], 0, slat=False)
+
+ return retval
+
+
+class Test002(FioLatTest):
+ """Test object for Test 2."""
+
+ def check(self):
+ """Check Test 2 output."""
+
+ job = self.json_data['jobs'][0]
+
+ retval = True
+ if not self.check_empty(job['read']):
+ print("Unexpected read data found in output")
+ retval = False
+ if not self.check_empty(job['trim']):
+ print("Unexpected trim data found in output")
+ retval = False
+ if not self.check_nocmdprio_lat(job):
+ print("Unexpected high/low priority latencies found")
+ retval = False
+
+ retval &= self.check_latencies(job['write'], 1, slat=False, clat=False)
+
+ return retval
+
+
+class Test003(FioLatTest):
+ """Test object for Test 3."""
+
+ def check(self):
+ """Check Test 3 output."""
+
+ job = self.json_data['jobs'][0]
+
+ retval = True
+ if not self.check_empty(job['read']):
+ print("Unexpected read data found in output")
+ retval = False
+ if not self.check_empty(job['write']):
+ print("Unexpected write data found in output")
+ retval = False
+ if not self.check_nocmdprio_lat(job):
+ print("Unexpected high/low priority latencies found")
+ retval = False
+
+ retval &= self.check_latencies(job['trim'], 2, slat=False, tlat=False)
+
+ return retval
+
+
+class Test004(FioLatTest):
+ """Test object for Tests 4, 13."""
+
+ def check(self):
+ """Check Test 4, 13 output."""
+
+ job = self.json_data['jobs'][0]
+
+ retval = True
+ if not self.check_empty(job['write']):
+ print("Unexpected write data found in output")
+ retval = False
+ if not self.check_empty(job['trim']):
+ print("Unexpected trim data found in output")
+ retval = False
+ if not self.check_nocmdprio_lat(job):
+ print("Unexpected high/low priority latencies found")
+ retval = False
+
+ retval &= self.check_latencies(job['read'], 0, plus=True)
+
+ return retval
+
+
+class Test005(FioLatTest):
+ """Test object for Test 5."""
+
+ def check(self):
+ """Check Test 5 output."""
+
+ job = self.json_data['jobs'][0]
+
+ retval = True
+ if not self.check_empty(job['read']):
+ print("Unexpected read data found in output")
+ retval = False
+ if not self.check_empty(job['trim']):
+ print("Unexpected trim data found in output")
+ retval = False
+ if not self.check_nocmdprio_lat(job):
+ print("Unexpected high/low priority latencies found")
+ retval = False
+
+ retval &= self.check_latencies(job['write'], 1, slat=False, plus=True)
+
+ return retval
+
+
+class Test006(FioLatTest):
+ """Test object for Test 6."""
+
+ def check(self):
+ """Check Test 6 output."""
+
+ job = self.json_data['jobs'][0]
+
+ retval = True
+ if not self.check_empty(job['write']):
+ print("Unexpected write data found in output")
+ retval = False
+ if not self.check_empty(job['trim']):
+ print("Unexpected trim data found in output")
+ retval = False
+ if not self.check_nocmdprio_lat(job):
+ print("Unexpected high/low priority latencies found")
+ retval = False
+
+ retval &= self.check_latencies(job['read'], 0, slat=False, tlat=False, plus=True)
+
+ return retval
+
+
+class Test007(FioLatTest):
+ """Test object for Test 7."""
+
+ def check(self):
+ """Check Test 7 output."""
+
+ job = self.json_data['jobs'][0]
+
+ retval = True
+ if not self.check_empty(job['trim']):
+ print("Unexpected trim data found in output")
+ retval = False
+ if not self.check_nocmdprio_lat(job):
+ print("Unexpected high/low priority latencies found")
+ retval = False
+
+ retval &= self.check_latencies(job['read'], 0, clat=False, tlat=False, plus=True)
+ retval &= self.check_latencies(job['write'], 1, clat=False, tlat=False, plus=True)
+
+ return retval
+
+
+class Test008(FioLatTest):
+ """Test object for Tests 8, 14."""
+
+ def check(self):
+ """Check Test 8, 14 output."""
+
+ job = self.json_data['jobs'][0]
+
+ retval = True
+ if 'read' in job or 'write'in job or 'trim' in job:
+ print("Unexpected data direction found in fio output")
+ retval = False
+ if not self.check_nocmdprio_lat(job):
+ print("Unexpected high/low priority latencies found")
+ retval = False
+
+ retval &= self.check_latencies(job['mixed'], 0, plus=True, unified=True)
+
+ return retval
+
+
+class Test009(FioLatTest):
+ """Test object for Test 9."""
+
+ def check(self):
+ """Check Test 9 output."""
+
+ job = self.json_data['jobs'][0]
+
+ retval = True
+ if not self.check_empty(job['read']):
+ print("Unexpected read data found in output")
+ retval = False
+ if not self.check_empty(job['trim']):
+ print("Unexpected trim data found in output")
+ retval = False
+ if not self.check_sync_lat(job['sync'], plus=True):
+ print("Error checking fsync latency data")
+ retval = False
+ if not self.check_nocmdprio_lat(job):
+ print("Unexpected high/low priority latencies found")
+ retval = False
+
+ retval &= self.check_latencies(job['write'], 1, slat=False, plus=True)
+
+ return retval
+
+
+class Test010(FioLatTest):
+ """Test object for Test 10."""
+
+ def check(self):
+ """Check Test 10 output."""
+
+ job = self.json_data['jobs'][0]
+
+ retval = True
+ if not self.check_empty(job['trim']):
+ print("Unexpected trim data found in output")
+ retval = False
+ if not self.check_nocmdprio_lat(job):
+ print("Unexpected high/low priority latencies found")
+ retval = False
+
+ retval &= self.check_latencies(job['read'], 0, plus=True)
+ retval &= self.check_latencies(job['write'], 1, plus=True)
+ retval &= self.check_terse(self.terse_data[17:34], job['read']['lat_ns']['percentile'])
+ retval &= self.check_terse(self.terse_data[58:75], job['write']['lat_ns']['percentile'])
+ # Terse data checking only works for default percentiles.
+ # This needs to be changed if something other than the default is ever used.
+
+ return retval
+
+
+class Test011(FioLatTest):
+ """Test object for Test 11."""
+
+ def check(self):
+ """Check Test 11 output."""
+
+ job = self.json_data['jobs'][0]
+
+ retval = True
+ if not self.check_empty(job['trim']):
+ print("Unexpected trim data found in output")
+ retval = False
+ if not self.check_nocmdprio_lat(job):
+ print("Unexpected high/low priority latencies found")
+ retval = False
+
+ retval &= self.check_latencies(job['read'], 0, slat=False, clat=False, plus=True)
+ retval &= self.check_latencies(job['write'], 1, slat=False, clat=False, plus=True)
+ retval &= self.check_terse(self.terse_data[17:34], job['read']['lat_ns']['percentile'])
+ retval &= self.check_terse(self.terse_data[58:75], job['write']['lat_ns']['percentile'])
+ # Terse data checking only works for default percentiles.
+ # This needs to be changed if something other than the default is ever used.
+
+ return retval
+
+
+class Test015(FioLatTest):
+ """Test object for Test 15."""
+
+ def check(self):
+ """Check Test 15 output."""
+
+ job = self.json_data['jobs'][0]
+
+ retval = True
+ if not self.check_empty(job['write']):
+ print("Unexpected write data found in output")
+ retval = False
+ if not self.check_empty(job['trim']):
+ print("Unexpected trim data found in output")
+ retval = False
+
+ retval &= self.check_latencies(job['read'], 0, plus=True)
+ retval &= self.check_prio_latencies(job['read'], clat=False, plus=True)
+
+ return retval
+
+
+class Test016(FioLatTest):
+ """Test object for Test 16."""
+
+ def check(self):
+ """Check Test 16 output."""
+
+ job = self.json_data['jobs'][0]
+
+ retval = True
+ if not self.check_empty(job['read']):
+ print("Unexpected read data found in output")
+ retval = False
+ if not self.check_empty(job['trim']):
+ print("Unexpected trim data found in output")
+ retval = False
+
+ retval &= self.check_latencies(job['write'], 1, slat=False, plus=True)
+ retval &= self.check_prio_latencies(job['write'], clat=False, plus=True)
+
+ return retval
+
+
+class Test017(FioLatTest):
+ """Test object for Test 17."""
+
+ def check(self):
+ """Check Test 17 output."""
+
+ job = self.json_data['jobs'][0]
+
+ retval = True
+ if not self.check_empty(job['write']):
+ print("Unexpected write data found in output")
+ retval = False
+ if not self.check_empty(job['trim']):
+ print("Unexpected trim data found in output")
+ retval = False
+
+ retval &= self.check_latencies(job['read'], 0, slat=False, tlat=False, plus=True)
+ retval &= self.check_prio_latencies(job['read'], plus=True)
+
+ return retval
+
+
+class Test018(FioLatTest):
+ """Test object for Test 18."""
+
+ def check(self):
+ """Check Test 18 output."""
+
+ job = self.json_data['jobs'][0]
+
+ retval = True
+ if not self.check_empty(job['trim']):
+ print("Unexpected trim data found in output")
+ retval = False
+
+ retval &= self.check_latencies(job['read'], 0, clat=False, tlat=False, plus=True)
+ retval &= self.check_latencies(job['write'], 1, clat=False, tlat=False, plus=True)
+
+ # We actually have json+ data but setting plus=False below avoids checking the
+ # json+ bins which did not exist for clat and lat because this job is run with
+ # clat_percentiles=0, lat_percentiles=0, However, we can still check the summary
+ # statistics
+ retval &= self.check_prio_latencies(job['write'], plus=False)
+ retval &= self.check_prio_latencies(job['read'], plus=False)
+
+ return retval
+
+
+class Test019(FioLatTest):
+ """Test object for Tests 19, 20."""
+
+ def check(self):
+ """Check Test 19, 20 output."""
+
+ job = self.json_data['jobs'][0]
+
+ retval = True
+ if 'read' in job or 'write'in job or 'trim' in job:
+ print("Unexpected data direction found in fio output")
+ retval = False
+
+ retval &= self.check_latencies(job['mixed'], 0, plus=True, unified=True)
+ retval &= self.check_prio_latencies(job['mixed'], clat=False, plus=True)
+
+ return retval
+
+
+def parse_args():
+ """Parse command-line arguments."""
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument('-f', '--fio', help='path to file executable (e.g., ./fio)')
+ parser.add_argument('-a', '--artifact-root', help='artifact root directory')
+ parser.add_argument('-d', '--debug', help='enable debug output', action='store_true')
+ parser.add_argument('-s', '--skip', nargs='+', type=int,
+ help='list of test(s) to skip')
+ parser.add_argument('-o', '--run-only', nargs='+', type=int,
+ help='list of test(s) to run, skipping all others')
+ args = parser.parse_args()
+
+ return args
+
+
+def main():
+ """Run tests of fio latency percentile reporting"""
+
+ args = parse_args()
+
+ artifact_root = args.artifact_root if args.artifact_root else \
+ "latency-test-{0}".format(time.strftime("%Y%m%d-%H%M%S"))
+ os.mkdir(artifact_root)
+ print("Artifact directory is %s" % artifact_root)
+
+ if args.fio:
+ fio = str(Path(args.fio).absolute())
+ else:
+ fio = 'fio'
+ print("fio path is %s" % fio)
+
+ if platform.system() == 'Linux':
+ aio = 'libaio'
+ elif platform.system() == 'Windows':
+ aio = 'windowsaio'
+ else:
+ aio = 'posixaio'
+
+ test_list = [
+ {
+ # randread, null
+ # enable slat, clat, lat
+ # only clat and lat will appear because
+ # because the null ioengine is syncrhonous
+ "test_id": 1,
+ "runtime": 2,
+ "output-format": "json",
+ "slat_percentiles": 1,
+ "clat_percentiles": 1,
+ "lat_percentiles": 1,
+ "ioengine": 'null',
+ 'rw': 'randread',
+ "test_obj": Test001,
+ },
+ {
+ # randwrite, null
+ # enable lat only
+ "test_id": 2,
+ "runtime": 2,
+ "output-format": "json",
+ "slat_percentiles": 0,
+ "clat_percentiles": 0,
+ "lat_percentiles": 1,
+ "ioengine": 'null',
+ 'rw': 'randwrite',
+ "test_obj": Test002,
+ },
+ {
+ # randtrim, null
+ # enable clat only
+ "test_id": 3,
+ "runtime": 2,
+ "output-format": "json",
+ "slat_percentiles": 0,
+ "clat_percentiles": 1,
+ "lat_percentiles": 0,
+ "ioengine": 'null',
+ 'rw': 'randtrim',
+ "test_obj": Test003,
+ },
+ {
+ # randread, aio
+ # enable slat, clat, lat
+ # all will appear because liaio is asynchronous
+ "test_id": 4,
+ "runtime": 5,
+ "output-format": "json+",
+ "slat_percentiles": 1,
+ "clat_percentiles": 1,
+ "lat_percentiles": 1,
+ "ioengine": aio,
+ 'rw': 'randread',
+ "test_obj": Test004,
+ },
+ {
+ # randwrite, aio
+ # enable only clat, lat
+ "test_id": 5,
+ "runtime": 5,
+ "output-format": "json+",
+ "slat_percentiles": 0,
+ "clat_percentiles": 1,
+ "lat_percentiles": 1,
+ "ioengine": aio,
+ 'rw': 'randwrite',
+ "test_obj": Test005,
+ },
+ {
+ # randread, aio
+ # by default only clat should appear
+ "test_id": 6,
+ "runtime": 5,
+ "output-format": "json+",
+ "ioengine": aio,
+ 'rw': 'randread',
+ "test_obj": Test006,
+ },
+ {
+ # 50/50 r/w, aio
+ # enable only slat
+ "test_id": 7,
+ "runtime": 5,
+ "output-format": "json+",
+ "slat_percentiles": 1,
+ "clat_percentiles": 0,
+ "lat_percentiles": 0,
+ "ioengine": aio,
+ 'rw': 'randrw',
+ "test_obj": Test007,
+ },
+ {
+ # 50/50 r/w, aio, unified_rw_reporting
+ # enable slat, clat, lat
+ "test_id": 8,
+ "runtime": 5,
+ "output-format": "json+",
+ "slat_percentiles": 1,
+ "clat_percentiles": 1,
+ "lat_percentiles": 1,
+ "ioengine": aio,
+ 'rw': 'randrw',
+ 'unified_rw_reporting': 1,
+ "test_obj": Test008,
+ },
+ {
+ # randwrite, null
+ # enable slat, clat, lat
+ # fsync
+ "test_id": 9,
+ "runtime": 2,
+ "output-format": "json+",
+ "slat_percentiles": 1,
+ "clat_percentiles": 1,
+ "lat_percentiles": 1,
+ "ioengine": 'null',
+ 'rw': 'randwrite',
+ 'fsync': 32,
+ "test_obj": Test009,
+ },
+ {
+ # 50/50 r/w, aio
+ # enable slat, clat, lat
+ "test_id": 10,
+ "runtime": 5,
+ "output-format": "terse,json+",
+ "slat_percentiles": 1,
+ "clat_percentiles": 1,
+ "lat_percentiles": 1,
+ "ioengine": aio,
+ 'rw': 'randrw',
+ "test_obj": Test010,
+ },
+ {
+ # 50/50 r/w, aio
+ # enable only lat
+ "test_id": 11,
+ "runtime": 5,
+ "output-format": "terse,json+",
+ "slat_percentiles": 0,
+ "clat_percentiles": 0,
+ "lat_percentiles": 1,
+ "ioengine": aio,
+ 'rw': 'randrw',
+ "test_obj": Test011,
+ },
+ {
+ # randread, null
+ # enable slat, clat, lat
+ # only clat and lat will appear because
+ # because the null ioengine is syncrhonous
+ # same as Test 1 except
+ # numjobs = 4 to test sum_thread_stats() changes
+ "test_id": 12,
+ "runtime": 2,
+ "output-format": "json",
+ "slat_percentiles": 1,
+ "clat_percentiles": 1,
+ "lat_percentiles": 1,
+ "ioengine": 'null',
+ 'rw': 'randread',
+ 'numjobs': 4,
+ "test_obj": Test001,
+ },
+ {
+ # randread, aio
+ # enable slat, clat, lat
+ # all will appear because liaio is asynchronous
+ # same as Test 4 except
+ # numjobs = 4 to test sum_thread_stats() changes
+ "test_id": 13,
+ "runtime": 5,
+ "output-format": "json+",
+ "slat_percentiles": 1,
+ "clat_percentiles": 1,
+ "lat_percentiles": 1,
+ "ioengine": aio,
+ 'rw': 'randread',
+ 'numjobs': 4,
+ "test_obj": Test004,
+ },
+ {
+ # 50/50 r/w, aio, unified_rw_reporting
+ # enable slat, clat, lata
+ # same as Test 8 except
+ # numjobs = 4 to test sum_thread_stats() changes
+ "test_id": 14,
+ "runtime": 5,
+ "output-format": "json+",
+ "slat_percentiles": 1,
+ "clat_percentiles": 1,
+ "lat_percentiles": 1,
+ "ioengine": aio,
+ 'rw': 'randrw',
+ 'unified_rw_reporting': 1,
+ 'numjobs': 4,
+ "test_obj": Test008,
+ },
+ {
+ # randread, aio
+ # enable slat, clat, lat
+ # all will appear because liaio is asynchronous
+ # same as Test 4 except add cmdprio_percentage
+ "test_id": 15,
+ "runtime": 5,
+ "output-format": "json+",
+ "slat_percentiles": 1,
+ "clat_percentiles": 1,
+ "lat_percentiles": 1,
+ "ioengine": aio,
+ 'rw': 'randread',
+ 'cmdprio_percentage': 50,
+ "test_obj": Test015,
+ },
+ {
+ # randwrite, aio
+ # enable only clat, lat
+ # same as Test 5 except add cmdprio_percentage
+ "test_id": 16,
+ "runtime": 5,
+ "output-format": "json+",
+ "slat_percentiles": 0,
+ "clat_percentiles": 1,
+ "lat_percentiles": 1,
+ "ioengine": aio,
+ 'rw': 'randwrite',
+ 'cmdprio_percentage': 50,
+ "test_obj": Test016,
+ },
+ {
+ # randread, aio
+ # by default only clat should appear
+ # same as Test 6 except add cmdprio_percentage
+ "test_id": 17,
+ "runtime": 5,
+ "output-format": "json+",
+ "ioengine": aio,
+ 'rw': 'randread',
+ 'cmdprio_percentage': 50,
+ "test_obj": Test017,
+ },
+ {
+ # 50/50 r/w, aio
+ # enable only slat
+ # same as Test 7 except add cmdprio_percentage
+ "test_id": 18,
+ "runtime": 5,
+ "output-format": "json+",
+ "slat_percentiles": 1,
+ "clat_percentiles": 0,
+ "lat_percentiles": 0,
+ "ioengine": aio,
+ 'rw': 'randrw',
+ 'cmdprio_percentage': 50,
+ "test_obj": Test018,
+ },
+ {
+ # 50/50 r/w, aio, unified_rw_reporting
+ # enable slat, clat, lat
+ # same as Test 8 except add cmdprio_percentage
+ "test_id": 19,
+ "runtime": 5,
+ "output-format": "json+",
+ "slat_percentiles": 1,
+ "clat_percentiles": 1,
+ "lat_percentiles": 1,
+ "ioengine": aio,
+ 'rw': 'randrw',
+ 'unified_rw_reporting': 1,
+ 'cmdprio_percentage': 50,
+ "test_obj": Test019,
+ },
+ {
+ # 50/50 r/w, aio, unified_rw_reporting
+ # enable slat, clat, lat
+ # same as Test 19 except
+ # add numjobs = 4 to test sum_thread_stats() changes
+ "test_id": 20,
+ "runtime": 5,
+ "output-format": "json+",
+ "slat_percentiles": 1,
+ "clat_percentiles": 1,
+ "lat_percentiles": 1,
+ "ioengine": aio,
+ 'rw': 'randrw',
+ 'unified_rw_reporting': 1,
+ 'cmdprio_percentage': 50,
+ 'numjobs': 4,
+ "test_obj": Test019,
+ },
+ ]
+
+ passed = 0
+ failed = 0
+ skipped = 0
+
+ for test in test_list:
+ if (args.skip and test['test_id'] in args.skip) or \
+ (args.run_only and test['test_id'] not in args.run_only):
+ skipped = skipped + 1
+ outcome = 'SKIPPED (User request)'
+ elif platform.system() != 'Linux' and 'cmdprio_percentage' in test:
+ skipped = skipped + 1
+ outcome = 'SKIPPED (Linux required for cmdprio_percentage tests)'
+ else:
+ test_obj = test['test_obj'](artifact_root, test, args.debug)
+ status = test_obj.run_fio(fio)
+ if status:
+ status = test_obj.check()
+ if status:
+ passed = passed + 1
+ outcome = 'PASSED'
+ else:
+ failed = failed + 1
+ outcome = 'FAILED'
+
+ print("**********Test {0} {1}**********".format(test['test_id'], outcome))
+
+ print("{0} tests passed, {1} failed, {2} skipped".format(passed, failed, skipped))
+
+ sys.exit(failed)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py
index 3d236e37..003ff664 100755
--- a/t/run-fio-tests.py
+++ b/t/run-fio-tests.py
@@ -510,17 +510,17 @@ class Requirements(object):
SUCCESS_DEFAULT = {
'zero_return': True,
'stderr_empty': True,
- 'timeout': 300,
+ 'timeout': 600,
}
SUCCESS_NONZERO = {
'zero_return': False,
'stderr_empty': False,
- 'timeout': 300,
+ 'timeout': 600,
}
SUCCESS_STDERR = {
'zero_return': True,
'stderr_empty': False,
- 'timeout': 300,
+ 'timeout': 600,
}
TEST_LIST = [
{
@@ -712,6 +712,14 @@ TEST_LIST = [
'success': SUCCESS_DEFAULT,
'requirements': [Requirements.unittests],
},
+ {
+ 'test_id': 1010,
+ 'test_class': FioExeTest,
+ 'exe': 't/latency_percentiles.py',
+ 'parameters': ['-f', '{fio_path}'],
+ 'success': SUCCESS_DEFAULT,
+ 'requirements': [],
+ },
]

diff --git a/thread_options.h b/thread_options.h
index 4b131bda..c78ed43d 100644
--- a/thread_options.h
+++ b/thread_options.h
@@ -250,6 +250,7 @@ struct thread_options {
unsigned int trim_zero;
unsigned long long trim_backlog;
unsigned int clat_percentiles;
+ unsigned int slat_percentiles;
unsigned int lat_percentiles;
unsigned int percentile_precision; /* digits after decimal for percentiles */
fio_fp64_t percentile_list[FIO_IO_U_LIST_MAX_LEN];
@@ -370,7 +371,7 @@ struct thread_options_pack {
uint32_t iodepth_batch_complete_min;
uint32_t iodepth_batch_complete_max;
uint32_t serialize_overlap;
- uint32_t lat_percentiles;
+ uint32_t pad;

uint64_t size;
uint64_t io_size;
@@ -430,7 +431,7 @@ struct thread_options_pack {
uint32_t override_sync;
uint32_t rand_repeatable;
uint32_t allrand_repeatable;
- uint32_t pad;
+ uint32_t pad2;
uint64_t rand_seed;
uint32_t log_avg_msec;
uint32_t log_hist_msec;
@@ -464,7 +465,6 @@ struct thread_options_pack {

uint32_t hugepage_size;
uint64_t rw_min_bs;
- uint32_t pad2;
uint32_t thinktime;
uint32_t thinktime_spin;
uint32_t thinktime_blocks;
@@ -539,7 +539,10 @@ struct thread_options_pack {
uint32_t trim_zero;
uint64_t trim_backlog;
uint32_t clat_percentiles;
+ uint32_t lat_percentiles;
+ uint32_t slat_percentiles;
uint32_t percentile_precision;
+ uint32_t pad3;
fio_fp64_t percentile_list[FIO_IO_U_LIST_MAX_LEN];

uint8_t read_iolog_file[FIO_TOP_STR_MAX];
@@ -573,7 +576,6 @@ struct thread_options_pack {
uint32_t rate_iops_min[DDIR_RWDIR_CNT];
uint32_t rate_process;
uint32_t rate_ign_think;
- uint32_t pad3;

uint8_t ioscheduler[FIO_TOP_STR_MAX];

@@ -603,7 +605,6 @@ struct thread_options_pack {
uint32_t flow_sleep;

uint32_t offset_increment_percent;
- uint32_t pad4;
uint64_t offset_increment;
uint64_t number_ios;