diff --git a/HOWTO b/HOWTO index 86fb296445..208c2f989f 100644 --- a/HOWTO +++ b/HOWTO @@ -299,6 +299,10 @@ Command line options Use the directory specified by `path` for generated state files instead of the current working directory. +.. option:: --latency-stats=nr + + Change default latency stats number. The default buckets number is 10. + Any parameters following the options will be assumed to be job files, unless they match a job file parameter. Multiple job files can be listed and each job file will be regarded as a separate group. Fio will :option:`stonewall` @@ -4045,13 +4049,20 @@ field was introduced or whether it's specific to some terse version): <=1, 2, 4, 8, 16, 32, >=64 + I/O latencies nanoseconds:: + + <=2, 4, 10, 20, 50, 100, 250, 500, 750, 1000 + The number of buckets may vary. + I/O latencies microseconds:: <=2, 4, 10, 20, 50, 100, 250, 500, 750, 1000 + The number of buckets may vary. I/O latencies milliseconds:: <=2, 4, 10, 20, 50, 100, 250, 500, 750, 1000, 2000, >=2000 + The number of buckets may vary. Disk utilization [v3]:: @@ -4084,7 +4095,7 @@ minimal output v3, separated by semicolons:: In client/server mode terse output differs from what appears when jobs are run locally. Disk utilization data is omitted from the standard terse output and for v3 and later appears on its own separate line at the end of each terse -reporting cycle. +reporting cycle. The number of buckets may vary. JSON output diff --git a/client.c b/client.c index 29d8750a5b..d9856d8012 100644 --- a/client.c +++ b/client.c @@ -2150,6 +2150,8 @@ int fio_handle_clients(struct client_ops *ops) fio_client_json_fini(); + stat_free_lat(&client_ts); + free(pfds); return retval || error_clients; } diff --git a/debug.h b/debug.h index 51b18de235..851acfb993 100644 --- a/debug.h +++ b/debug.h @@ -23,6 +23,7 @@ enum { FD_STEADYSTATE, FD_HELPERTHREAD, FD_ZBD, + FD_STAT, FD_DEBUG_MAX, }; diff --git a/fio.1 b/fio.1 index 5aa54a4d04..ddeedae39f 100644 --- a/fio.1 +++ b/fio.1 @@ -176,6 +176,9 @@ Set this \fIcommand\fR as remote trigger. .BI \-\-aux\-path \fR=\fPpath Use the directory specified by \fIpath\fP for generated state files instead of the current working directory. +.TP +.BI \-\-latency\-stats \fR=\fPnr +Change default latency stats number. The default buckets number is 10. .SH "JOB FILE FORMAT" Any parameters following the options will be assumed to be job files, unless they match a job file parameter. Multiple job files can be listed and each job @@ -3748,11 +3751,22 @@ I/O depths: .RS .P .B +I/O latencies nanoseconds: +.RE +.P +.nf + <=2, 4, 10, 20, 50, 100, 250, 500, 750, 1000 + The number of buckets may vary. +.fi +.RS +.P +.B I/O latencies microseconds: .RE .P .nf <=2, 4, 10, 20, 50, 100, 250, 500, 750, 1000 + The number of buckets may vary. .fi .RS .P @@ -3762,6 +3776,7 @@ I/O latencies milliseconds: .P .nf <=2, 4, 10, 20, 50, 100, 250, 500, 750, 1000, 2000, >=2000 + The number of buckets may vary. .fi .RS .P @@ -3813,7 +3828,7 @@ minimal output v3, separated by semicolons: In client/server mode terse output differs from what appears when jobs are run locally. Disk utilization data is omitted from the standard terse output and for v3 and later appears on its own separate line at the end of each terse -reporting cycle. +reporting cycle. The number of buckets may vary. .SH JSON OUTPUT The \fBjson\fR output format is intended to be both human readable and convenient for automated parsing. For the most part its sections mirror those of the diff --git a/init.c b/init.c index 60c7cff405..428c224e08 100644 --- a/init.c +++ b/init.c @@ -293,6 +293,11 @@ static struct option l_opts[FIO_NR_OPTIONS] = { .has_arg = no_argument, .val = 'A' | FIO_CLIENT_FLAG, }, + { + .name = (char *) "latency-stats", + .has_arg = required_argument, + .val = 'n' | FIO_CLIENT_FLAG, + }, { .name = NULL, }, @@ -537,6 +542,8 @@ static void put_job(struct thread_data *td) if (td->o.name) free(td->o.name); + stat_free_lat(&td->ts); + memset(td, 0, sizeof(*td)); segments[cur_segment].nr_threads--; thread_number--; @@ -1532,6 +1539,7 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num, td->ts.clat_low_prio_stat[i].min_val = ULONG_MAX; } td->ts.sync_stat.min_val = ULONG_MAX; + stat_alloc_lat(&td->ts); td->ddir_seq_nr = o->ddir_seq_nr; if ((o->stonewall || o->new_group) && prev_group_jobs) { @@ -2241,6 +2249,7 @@ static void usage(const char *name) printf(" --trigger=cmd\t\tSet this command as local trigger\n"); printf(" --trigger-remote=cmd\tSet this command as remote trigger\n"); printf(" --aux-path=path\tUse this path for fio state generated files\n"); + printf(" --latency-stats=nr\tChange default latency stats number\n"); printf("\nFio was written by Jens Axboe \n"); } @@ -2322,6 +2331,10 @@ const struct debug_level debug_levels[] = { .help = "Zoned Block Device logging", .shift = FD_ZBD, }, + { .name = "stat", + .help = "Statisitics logging", + .shift = FD_STAT, + }, { .name = NULL, }, }; @@ -2906,11 +2919,17 @@ int parse_cmd_line(int argc, char *argv[], int client_type) } trigger_timeout /= 1000000; break; - case 'A': did_arg = true; merge_blktrace_only = true; break; + case 'n': + if (!stat_set_lat(atoi(optarg))) { + log_err("fio: bad latency stats number\n"); + exit_val = 1; + do_exit++; + } + break; case '?': log_err("%s: unrecognized option '%s'\n", argv[0], argv[optind - 1]); diff --git a/io_u.c b/io_u.c index b421a579bd..277cc0c358 100644 --- a/io_u.c +++ b/io_u.c @@ -1045,41 +1045,11 @@ void io_u_mark_depth(struct thread_data *td, unsigned int nr) static void io_u_mark_lat_nsec(struct thread_data *td, unsigned long long nsec) { - int idx = 0; + int idx; assert(nsec < 1000); - switch (nsec) { - case 750 ... 999: - idx = 9; - break; - case 500 ... 749: - idx = 8; - break; - case 250 ... 499: - idx = 7; - break; - case 100 ... 249: - idx = 6; - break; - case 50 ... 99: - idx = 5; - break; - case 20 ... 49: - idx = 4; - break; - case 10 ... 19: - idx = 3; - break; - case 4 ... 9: - idx = 2; - break; - case 2 ... 3: - idx = 1; - fallthrough; - case 0 ... 1: - break; - } + idx = stat_get_lat_idx(nsec); assert(idx < FIO_IO_U_LAT_N_NR); td->ts.io_u_lat_n[idx]++; @@ -1087,41 +1057,11 @@ static void io_u_mark_lat_nsec(struct thread_data *td, unsigned long long nsec) static void io_u_mark_lat_usec(struct thread_data *td, unsigned long long usec) { - int idx = 0; + int idx; assert(usec < 1000 && usec >= 1); - switch (usec) { - case 750 ... 999: - idx = 9; - break; - case 500 ... 749: - idx = 8; - break; - case 250 ... 499: - idx = 7; - break; - case 100 ... 249: - idx = 6; - break; - case 50 ... 99: - idx = 5; - break; - case 20 ... 49: - idx = 4; - break; - case 10 ... 19: - idx = 3; - break; - case 4 ... 9: - idx = 2; - break; - case 2 ... 3: - idx = 1; - fallthrough; - case 0 ... 1: - break; - } + idx = stat_get_lat_idx(usec); assert(idx < FIO_IO_U_LAT_U_NR); td->ts.io_u_lat_u[idx]++; @@ -1129,45 +1069,20 @@ static void io_u_mark_lat_usec(struct thread_data *td, unsigned long long usec) static void io_u_mark_lat_msec(struct thread_data *td, unsigned long long msec) { - int idx = 0; + int idx; + int nr = stat_get_lat_m_nr(); assert(msec >= 1); switch (msec) { default: - idx = 11; + idx = nr - 1; break; case 1000 ... 1999: - idx = 10; - break; - case 750 ... 999: - idx = 9; - break; - case 500 ... 749: - idx = 8; + idx = nr - 2; break; - case 250 ... 499: - idx = 7; - break; - case 100 ... 249: - idx = 6; - break; - case 50 ... 99: - idx = 5; - break; - case 20 ... 49: - idx = 4; - break; - case 10 ... 19: - idx = 3; - break; - case 4 ... 9: - idx = 2; - break; - case 2 ... 3: - idx = 1; - fallthrough; - case 0 ... 1: + case 1 ... 999: + idx = stat_get_lat_idx(msec); break; } diff --git a/server.h b/server.h index c128df28ad..daed057acb 100644 --- a/server.h +++ b/server.h @@ -48,7 +48,7 @@ struct fio_net_cmd_reply { }; enum { - FIO_SERVER_VER = 91, + FIO_SERVER_VER = 92, FIO_SERVER_MAX_FRAGMENT_PDU = 1024, FIO_SERVER_MAX_CMD_MB = 2048, diff --git a/stat.c b/stat.c index a8a96c85a4..e9c85e0168 100644 --- a/stat.c +++ b/stat.c @@ -19,8 +19,15 @@ #define LOG_MSEC_SLACK 1 +#define STAT_LAT_NR_DEF 10 +#define STAT_LAT_RANGE_MAX 1000 +#define STAT_NAME_LEN 20 + struct fio_sem *stat_sem; +static int stat_lat_nr = STAT_LAT_NR_DEF; +static int *stat_lat_ranges; + void clear_rusage_stat(struct thread_data *td) { struct thread_stat *ts = &td->ts; @@ -798,11 +805,25 @@ static void show_ddir_status(struct group_run_stats *rs, struct thread_stat *ts, } } -static bool show_lat(double *io_u_lat, int nr, const char **ranges, - const char *msg, struct buf_output *out) +static int stat_get_range(int i) +{ + int range = STAT_LAT_RANGE_MAX; + + if (i + 1 >= stat_lat_nr) + range *= i + 2 - stat_lat_nr; + else + range = stat_lat_ranges[i + 1]; + + return range; +} + +static bool show_lat(double *io_u_lat, int nr, const char *msg, + struct buf_output *out) { bool new_line = true, shown = false; int i, line = 0; + int range; + int range_max = STAT_LAT_RANGE_MAX * 2; for (i = 0; i < nr; i++) { if (io_u_lat[i] <= 0.0) @@ -817,7 +838,9 @@ static bool show_lat(double *io_u_lat, int nr, const char **ranges, } if (line) log_buf(out, ", "); - log_buf(out, "%s%3.2f%%", ranges[i], io_u_lat[i]); + range = stat_get_range(i); + log_buf(out, "%s%d=%3.2f%%", range > range_max ? ">=" : "", + range > range_max ? range_max : range, io_u_lat[i]); line++; if (line == 5) new_line = true; @@ -831,27 +854,17 @@ static bool show_lat(double *io_u_lat, int nr, const char **ranges, static void show_lat_n(double *io_u_lat_n, struct buf_output *out) { - const char *ranges[] = { "2=", "4=", "10=", "20=", "50=", "100=", - "250=", "500=", "750=", "1000=", }; - - show_lat(io_u_lat_n, FIO_IO_U_LAT_N_NR, ranges, "nsec", out); + show_lat(io_u_lat_n, FIO_IO_U_LAT_N_NR, "nsec", out); } static void show_lat_u(double *io_u_lat_u, struct buf_output *out) { - const char *ranges[] = { "2=", "4=", "10=", "20=", "50=", "100=", - "250=", "500=", "750=", "1000=", }; - - show_lat(io_u_lat_u, FIO_IO_U_LAT_U_NR, ranges, "usec", out); + show_lat(io_u_lat_u, FIO_IO_U_LAT_U_NR, "usec", out); } static void show_lat_m(double *io_u_lat_m, struct buf_output *out) { - const char *ranges[] = { "2=", "4=", "10=", "20=", "50=", "100=", - "250=", "500=", "750=", "1000=", "2000=", - ">=2000=", }; - - show_lat(io_u_lat_m, FIO_IO_U_LAT_M_NR, ranges, "msec", out); + show_lat(io_u_lat_m, FIO_IO_U_LAT_M_NR, "msec", out); } static void show_latencies(struct thread_stat *ts, struct buf_output *out) @@ -1805,6 +1818,9 @@ static struct json_object *show_thread_status_json(struct thread_stat *ts, double usr_cpu, sys_cpu; int i; size_t size; + char name[STAT_NAME_LEN]; + int range; + int range_max = STAT_LAT_RANGE_MAX * 2; root = json_create_object(); json_object_add_value_string(root, "jobname", ts->name); @@ -1852,11 +1868,10 @@ static struct json_object *show_thread_status_json(struct thread_stat *ts, json_object_add_value_object(root, "iodepth_level", tmp); /* Only show fixed 7 I/O depth levels*/ for (i = 0; i < 7; i++) { - char name[20]; if (i < 6) - snprintf(name, 20, "%d", 1 << i); + snprintf(name, STAT_NAME_LEN, "%d", 1 << i); else - snprintf(name, 20, ">=%d", 1 << i); + snprintf(name, STAT_NAME_LEN, ">=%d", 1 << i); json_object_add_value_float(tmp, (const char *)name, io_u_dist[i]); } @@ -1866,13 +1881,12 @@ static struct json_object *show_thread_status_json(struct thread_stat *ts, json_object_add_value_object(root, "iodepth_submit", tmp); /* Only show fixed 7 I/O depth levels*/ for (i = 0; i < 7; i++) { - char name[20]; if (i == 0) - snprintf(name, 20, "0"); + snprintf(name, STAT_NAME_LEN, "0"); else if (i < 6) - snprintf(name, 20, "%d", 1 << (i+1)); + snprintf(name, STAT_NAME_LEN, "%d", 1 << (i+1)); else - snprintf(name, 20, ">=%d", 1 << i); + snprintf(name, STAT_NAME_LEN, ">=%d", 1 << i); json_object_add_value_float(tmp, (const char *)name, io_u_dist[i]); } @@ -1882,13 +1896,12 @@ static struct json_object *show_thread_status_json(struct thread_stat *ts, json_object_add_value_object(root, "iodepth_complete", tmp); /* Only show fixed 7 I/O depth levels*/ for (i = 0; i < 7; i++) { - char name[20]; if (i == 0) - snprintf(name, 20, "0"); + snprintf(name, STAT_NAME_LEN, "0"); else if (i < 6) - snprintf(name, 20, "%d", 1 << (i+1)); + snprintf(name, STAT_NAME_LEN, "%d", 1 << (i+1)); else - snprintf(name, 20, ">=%d", 1 << i); + snprintf(name, STAT_NAME_LEN, ">=%d", 1 << i); json_object_add_value_float(tmp, (const char *)name, io_u_dist[i]); } @@ -1902,26 +1915,25 @@ static struct json_object *show_thread_status_json(struct thread_stat *ts, tmp = json_create_object(); json_object_add_value_object(root, "latency_ns", tmp); for (i = 0; i < FIO_IO_U_LAT_N_NR; i++) { - const char *ranges[] = { "2", "4", "10", "20", "50", "100", - "250", "500", "750", "1000", }; - json_object_add_value_float(tmp, ranges[i], io_u_lat_n[i]); + snprintf(name, STAT_NAME_LEN, "%d", stat_get_range(i)); + json_object_add_value_float(tmp, name, io_u_lat_n[i]); } /* Microsecond latency */ tmp = json_create_object(); json_object_add_value_object(root, "latency_us", tmp); for (i = 0; i < FIO_IO_U_LAT_U_NR; i++) { - const char *ranges[] = { "2", "4", "10", "20", "50", "100", - "250", "500", "750", "1000", }; - json_object_add_value_float(tmp, ranges[i], io_u_lat_u[i]); + snprintf(name, STAT_NAME_LEN, "%d", stat_get_range(i)); + json_object_add_value_float(tmp, name, io_u_lat_u[i]); } /* Millisecond latency */ tmp = json_create_object(); json_object_add_value_object(root, "latency_ms", tmp); for (i = 0; i < FIO_IO_U_LAT_M_NR; i++) { - const char *ranges[] = { "2", "4", "10", "20", "50", "100", - "250", "500", "750", "1000", "2000", - ">=2000", }; - json_object_add_value_float(tmp, ranges[i], io_u_lat_m[i]); + range = stat_get_range(i); + snprintf(name, STAT_NAME_LEN, "%s%d", + range > range_max ? ">=" : "", + range > range_max ? range_max : range); + json_object_add_value_float(tmp, name, io_u_lat_m[i]); } /* Additional output if continue_on_error set - default off*/ @@ -2284,6 +2296,7 @@ void init_thread_stat(struct thread_stat *ts) } ts->sync_stat.min_val = -1UL; ts->groupid = -1; + stat_alloc_lat(ts); } void __show_run_stats(void) @@ -2573,6 +2586,9 @@ void __show_run_stats(void) buf_output_free(out); } + for (i = 0; i < nr_ts; i++) + stat_free_lat(&threadstats[i]); + fio_idle_prof_cleanup(); log_info_flush(); @@ -3446,3 +3462,95 @@ uint32_t *io_u_block_info(struct thread_data *td, struct io_u *io_u) return info; } + +int stat_get_lat_n_nr(void) +{ + return stat_lat_nr; +} + +int stat_get_lat_u_nr(void) +{ + return stat_lat_nr; +} + +int stat_get_lat_m_nr(void) +{ + return stat_lat_nr + 2; +} + +void stat_alloc_lat_ranges(void) +{ + int i; + int range = STAT_LAT_RANGE_MAX; + int widths[] = { 1, 2, 6, 10, 30, 50, 150, 250 }; + int width_nr = sizeof(widths) / sizeof(widths[0]); + int idx = width_nr - 1; + + for (i = 0; i < width_nr; i++) { + widths[i] = widths[i] * STAT_LAT_NR_DEF / stat_lat_nr; + if (!widths[i]) + widths[i]++; + } + + stat_lat_ranges = scalloc(stat_lat_nr, sizeof(int)); + + for (i = stat_lat_nr; i; i--) { + if (range > widths[idx]) + range -= widths[idx]; + else if (idx) + range -= widths[--idx]; + stat_lat_ranges[i - 1] = range; + } + + if (stat_lat_ranges[0] <= 0) + stat_lat_ranges[0] = 1; + + for (i = 1; i < stat_lat_nr; i++) { + if (stat_lat_ranges[i] <= stat_lat_ranges[i - 1]) + stat_lat_ranges[i] = stat_lat_ranges[i - 1] + 1; + } + + for (i = 0; i < stat_lat_nr; i++) + dprint(FD_STAT, "lat_range[%d]: %d\n", i, stat_lat_ranges[i]); +} + +void stat_alloc_lat(struct thread_stat *ts) +{ + ts->io_u_lat_n = scalloc(FIO_IO_U_LAT_N_NR, sizeof(uint64_t)); + ts->io_u_lat_u = scalloc(FIO_IO_U_LAT_U_NR, sizeof(uint64_t)); + ts->io_u_lat_m = scalloc(FIO_IO_U_LAT_M_NR, sizeof(uint64_t)); + + if (!stat_lat_ranges) + stat_alloc_lat_ranges(); +} + +void stat_free_lat(struct thread_stat *ts) +{ + sfree(ts->io_u_lat_n); + sfree(ts->io_u_lat_u); + sfree(ts->io_u_lat_m); + sfree(stat_lat_ranges); + stat_lat_ranges = NULL; +} + +bool stat_set_lat(int nr) +{ + if (nr >= STAT_LAT_RANGE_MAX) + return false; + + stat_lat_nr = nr; + + return true; +} + +int stat_get_lat_idx(int lat) +{ + int i; + + for (i = stat_lat_nr - 1; i >= 0; i--) { + if (lat >= stat_lat_ranges[i]) + break; + } + + return i; +} diff --git a/stat.h b/stat.h index d08d4dc097..be461e9fde 100644 --- a/stat.h +++ b/stat.h @@ -22,9 +22,9 @@ struct group_run_stats { * How many depth levels to log */ #define FIO_IO_U_MAP_NR 7 -#define FIO_IO_U_LAT_N_NR 10 -#define FIO_IO_U_LAT_U_NR 10 -#define FIO_IO_U_LAT_M_NR 12 +#define FIO_IO_U_LAT_N_NR stat_get_lat_n_nr() +#define FIO_IO_U_LAT_U_NR stat_get_lat_u_nr() +#define FIO_IO_U_LAT_M_NR stat_get_lat_m_nr() /* * Constants for clat percentiles @@ -200,9 +200,18 @@ struct thread_stat { uint64_t io_u_map[FIO_IO_U_MAP_NR]; uint64_t io_u_submit[FIO_IO_U_MAP_NR]; uint64_t io_u_complete[FIO_IO_U_MAP_NR]; - 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]; + union { + uint64_t *io_u_lat_n; + uint64_t pad2; + }; + union { + uint64_t *io_u_lat_u; + uint64_t pad3; + }; + union { + uint64_t *io_u_lat_m; + uint64_t pad4; + }; 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]; @@ -221,7 +230,7 @@ struct thread_stat { */ union { uint16_t continue_on_error; - uint32_t pad2; + uint32_t pad5; }; uint32_t first_error; uint64_t total_err_count; @@ -236,7 +245,7 @@ struct thread_stat { uint32_t unit_base; uint32_t latency_depth; - uint32_t pad3; + uint32_t pad6; uint64_t latency_target; fio_fp64_t latency_percentile; uint64_t latency_window; @@ -259,12 +268,12 @@ struct thread_stat { union { uint64_t *ss_iops_data; - uint64_t pad4; + uint64_t pad7; }; union { uint64_t *ss_bw_data; - uint64_t pad5; + uint64_t pad8; }; uint64_t cachehit; @@ -401,4 +410,11 @@ static inline bool nsec_to_msec(unsigned long long *min, uint32_t *io_u_block_info(struct thread_data *td, struct io_u *io_u); +int stat_get_lat_n_nr(void); +int stat_get_lat_u_nr(void); +int stat_get_lat_m_nr(void); +void stat_alloc_lat(struct thread_stat *ts); +void stat_free_lat(struct thread_stat *ts); +bool stat_set_lat(int nr); +int stat_get_lat_idx(int lat); #endif