Merge branch 'fio_pr_alternate_epoch' of https://github.com/PCPartPicker/fio
authorJens Axboe <axboe@kernel.dk>
Thu, 3 Feb 2022 22:34:40 +0000 (15:34 -0700)
committerJens Axboe <axboe@kernel.dk>
Thu, 3 Feb 2022 22:34:40 +0000 (15:34 -0700)
* 'fio_pr_alternate_epoch' of https://github.com/PCPartPicker/fio:
  Support for alternate epochs in fio log files

1  2 
HOWTO
backend.c
fio.1
fio.h
options.c
os/windows/posix.c
rate-submit.c
stat.c
thread_options.h

diff --combined HOWTO
index 216ea185b16d4120fe55a0059bfdc2a71649087b,99fb575126483aa050c1d86abbc4f41c56ede13e..74ba7216e132c358309ffd0961c2cb73ea7494be
--- 1/HOWTO
--- 2/HOWTO
+++ b/HOWTO
@@@ -1063,12 -1063,6 +1063,12 @@@ Target file/devic
        Limit on the number of simultaneously opened zones per single
        thread/process.
  
 +.. option:: ignore_zone_limits=bool
 +      If this option is used, fio will ignore the maximum number of open
 +      zones limit of the zoned block device in use, thus allowing the
 +      option :option:`max_open_zones` value to be larger than the device
 +      reported limit. Default: false.
 +
  .. option:: zone_reset_threshold=float
  
        A number between zero and one that indicates the ratio of logical
@@@ -1344,7 -1338,7 +1344,7 @@@ I/O typ
  .. option:: fdatasync=int
  
        Like :option:`fsync` but uses :manpage:`fdatasync(2)` to only sync data and
 -      not metadata blocks. In Windows, FreeBSD, DragonFlyBSD or OSX there is no
 +      not metadata blocks. In Windows, DragonFlyBSD or OSX there is no
        :manpage:`fdatasync(2)` so this falls back to using :manpage:`fsync(2)`.
        Defaults to 0, which means fio does not periodically issue and wait for a
        data-only sync to complete.
@@@ -1886,12 -1880,11 +1886,12 @@@ I/O siz
  
  .. option:: filesize=irange(int)
  
 -      Individual file sizes. May be a range, in which case fio will select sizes
 -      for files at random within the given range and limited to :option:`size` in
 -      total (if that is given). If not given, each created file is the same size.
 -      This option overrides :option:`size` in terms of file size, which means
 -      this value is used as a fixed size or possible range of each file.
 +      Individual file sizes. May be a range, in which case fio will select sizes for
 +      files at random within the given range. If not given, each created file is the
 +      same size. This option overrides :option:`size` in terms of file size, i.e. if
 +      :option:`filesize` is specified then :option:`size` becomes merely the default
 +      for :option:`io_size` and has no effect at all if :option:`io_size` is set
 +      explicitly.
  
  .. option:: file_append=bool
  
@@@ -2212,28 -2205,10 +2212,28 @@@ with the caveat that when used on the c
        depending on the block size of the IO. This option is useful only
        when used together with the :option:`bssplit` option, that is,
        multiple different block sizes are used for reads and writes.
 -      The format for this option is the same as the format of the
 -      :option:`bssplit` option, with the exception that values for
 -      trim IOs are ignored. This option is mutually exclusive with the
 -      :option:`cmdprio_percentage` option.
 +
 +      The first accepted format for this option is the same as the format of
 +      the :option:`bssplit` option:
 +
 +              cmdprio_bssplit=blocksize/percentage:blocksize/percentage
 +
 +      In this case, each entry will use the priority class and priority
 +      level defined by the options :option:`cmdprio_class` and
 +      :option:`cmdprio` respectively.
 +
 +      The second accepted format for this option is:
 +
 +              cmdprio_bssplit=blocksize/percentage/class/level:blocksize/percentage/class/level
 +
 +      In this case, the priority class and priority level is defined inside
 +      each entry. In comparison with the first accepted format, the second
 +      accepted format does not restrict all entries to have the same priority
 +      class and priority level.
 +
 +      For both formats, only the read and write data directions are supported,
 +      values for trim IOs are ignored. This option is mutually exclusive with
 +      the :option:`cmdprio_percentage` option.
  
  .. option:: fixedbufs : [io_uring]
  
  
        **write**
                This is the default where write opcodes are issued as usual.
 -      **verify**
 +      **write_and_verify**
                Issue WRITE AND VERIFY commands. The BYTCHK bit is set to 0. This
                directs the device to carry out a medium verification with no data
                comparison. The writefua option is ignored with this selection.
 -      **same**
 +      **verify**
 +              This option is deprecated. Use write_and_verify instead.
 +      **write_same**
                Issue WRITE SAME commands. This transfers a single block to the device
                and writes this same block of data to a contiguous sequence of LBAs
                beginning at the specified offset. fio's block size parameter specifies
                for each command but only the first 512 bytes will be used and
                transferred to the device. The writefua option is ignored with this
                selection.
 +      **same**
 +              This option is deprecated. Use write_same instead.
 +      **write_same_ndob**
 +              Issue WRITE SAME(16) commands as above but with the No Data Output
 +              Buffer (NDOB) bit set. No data will be transferred to the device with
 +              this bit set. Data written will be a pre-determined pattern such as
 +              all zeroes.
 +      **write_stream**
 +              Issue WRITE STREAM(16) commands. Use the **stream_id** option to specify
 +              the stream identifier.
 +      **verify_bytchk_00**
 +              Issue VERIFY commands with BYTCHK set to 00. This directs the
 +              device to carry out a medium verification with no data comparison.
 +      **verify_bytchk_01**
 +              Issue VERIFY commands with BYTCHK set to 01. This directs the device to
 +              compare the data on the device with the data transferred to the device.
 +      **verify_bytchk_11**
 +              Issue VERIFY commands with BYTCHK set to 11. This transfers a
 +              single block to the device and compares the contents of this block with the
 +              data on the device beginning at the specified offset. fio's block size
 +              parameter specifies the total amount of data compared with this command.
 +              However, only one block (sector) worth of data is transferred to the device.
 +              This is similar to the WRITE SAME command except that data is compared instead
 +              of written.
 +
 +.. option:: stream_id=int : [sg]
 +
 +      Set the stream identifier for WRITE STREAM commands. If this is set to 0 (which is not
 +      a valid stream identifier) fio will open a stream and then close it when done. Default
 +      is 0.
  
  .. option:: hipri : [sg]
  
@@@ -3681,6 -3624,19 +3681,19 @@@ Measurements and reportin
        write_type_log for each log type, instead of the default zero-based
        timestamps.
  
+ .. option:: log_alternate_epoch=bool
+       If set, fio will log timestamps based on the epoch used by the clock specified
+       in the log_alternate_epoch_clock_id option, to the log files produced by
+       enabling write_type_log for each log type, instead of the default zero-based
+       timestamps.
+ .. option:: log_alternate_epoch_clock_id=int
+       Specifies the clock_id to be used by clock_gettime to obtain the alternate epoch
+       if either log_unix_epoch or log_alternate_epoch are true. Otherwise has no
+       effect. Default value is 0, or CLOCK_REALTIME.
  .. option:: block_error_percentiles=bool
  
        If set, record errors in trim block-sized units from writes and trims and
diff --combined backend.c
index 933d84144f5caec32d7c0d5f8c7168dbc140f714,151a561555fc45895cf29fa780d0b8b5cf399b44..061e3b329d2cd113d1f566a6e2da31c8019d37c7
+++ b/backend.c
@@@ -1777,18 -1777,6 +1777,18 @@@ static void *thread_main(void *data
        if (!init_iolog(td))
                goto err;
  
 +      /* ioprio_set() has to be done before td_io_init() */
 +      if (fio_option_is_set(o, ioprio) ||
 +          fio_option_is_set(o, ioprio_class)) {
 +              ret = ioprio_set(IOPRIO_WHO_PROCESS, 0, o->ioprio_class, o->ioprio);
 +              if (ret == -1) {
 +                      td_verror(td, errno, "ioprio_set");
 +                      goto err;
 +              }
 +              td->ioprio = ioprio_value(o->ioprio_class, o->ioprio);
 +              td->ts.ioprio = td->ioprio;
 +      }
 +
        if (td_io_init(td))
                goto err;
  
        if (o->verify_async && verify_async_init(td))
                goto err;
  
 -      if (fio_option_is_set(o, ioprio) ||
 -          fio_option_is_set(o, ioprio_class)) {
 -              ret = ioprio_set(IOPRIO_WHO_PROCESS, 0, o->ioprio_class, o->ioprio);
 -              if (ret == -1) {
 -                      td_verror(td, errno, "ioprio_set");
 -                      goto err;
 -              }
 -              td->ioprio = ioprio_value(o->ioprio_class, o->ioprio);
 -      }
 -
        if (o->cgroup && cgroup_setup(td, cgroup_list, &cgroup_mnt))
                goto err;
  
        if (rate_submit_init(td, sk_out))
                goto err;
  
-       set_epoch_time(td, o->log_unix_epoch);
+       set_epoch_time(td, o->log_unix_epoch | o->log_alternate_epoch, o->log_alternate_epoch_clock_id);
        fio_getrusage(&td->ru_start);
        memcpy(&td->bw_sample_time, &td->epoch, sizeof(td->epoch));
        memcpy(&td->iops_sample_time, &td->epoch, sizeof(td->epoch));
@@@ -2613,9 -2611,6 +2613,9 @@@ int fio_backend(struct sk_out *sk_out
        }
  
        for_each_td(td, i) {
 +              struct thread_stat *ts = &td->ts;
 +
 +              free_clat_prio_stats(ts);
                steadystate_free(td);
                fio_options_free(td);
                fio_dump_options_free(td);
diff --combined fio.1
index 0b25fad244121d1014fb3f2c2d9dccd469e589c1,74f1a6ea07e9039f76503fc2b88d4c383c3825b6..f32d791594e24ed46d818c407bcd79e1b6e57c18
--- 1/fio.1
--- 2/fio.1
+++ b/fio.1
@@@ -838,9 -838,9 +838,9 @@@ threads/processes
  Limit on the number of simultaneously opened zones per single thread/process.
  .TP
  .BI ignore_zone_limits \fR=\fPbool
 -If this isn't set, fio will query the max open zones limit from the zoned block
 -device, and exit if the specified \fBmax_open_zones\fR value is larger than the
 -limit reported by the device. Default: false.
 +If this option is used, fio will ignore the maximum number of open zones limit
 +of the zoned block device in use, thus allowing the option \fBmax_open_zones\fR
 +value to be larger than the device reported limit. Default: false.
  .TP
  .BI zone_reset_threshold \fR=\fPfloat
  A number between zero and one that indicates the ratio of logical blocks with
@@@ -1122,7 -1122,7 +1122,7 @@@ see \fBend_fsync\fR and \fBfsync_on_clo
  .TP
  .BI fdatasync \fR=\fPint
  Like \fBfsync\fR but uses \fBfdatasync\fR\|(2) to only sync data and
 -not metadata blocks. In Windows, FreeBSD, DragonFlyBSD or OSX there is no
 +not metadata blocks. In Windows, DragonFlyBSD or OSX there is no
  \fBfdatasync\fR\|(2) so this falls back to using \fBfsync\fR\|(2).
  Defaults to 0, which means fio does not periodically issue and wait for a
  data-only sync to complete.
@@@ -1686,10 -1686,10 +1686,10 @@@ also be set as number of zones using 'z
  .TP
  .BI filesize \fR=\fPirange(int)
  Individual file sizes. May be a range, in which case fio will select sizes
 -for files at random within the given range and limited to \fBsize\fR in
 -total (if that is given). If not given, each created file is the same size.
 -This option overrides \fBsize\fR in terms of file size, which means
 -this value is used as a fixed size or possible range of each file.
 +for files at random within the given range. If not given, each created file
 +is the same size. This option overrides \fBsize\fR in terms of file size, 
 +i.e. \fBsize\fR becomes merely the default for \fBio_size\fR (and
 +has no effect it all if \fBio_size\fR is set explicitly).
  .TP
  .BI file_append \fR=\fPbool
  Perform I/O after the end of the file. Normally fio will operate within the
@@@ -1995,34 -1995,10 +1995,34 @@@ To get a finer control over I/O priorit
  the percentage of IOs that must have a priority set depending on the block
  size of the IO. This option is useful only when used together with the option
  \fBbssplit\fR, that is, multiple different block sizes are used for reads and
 -writes. The format for this option is the same as the format of the
 -\fBbssplit\fR option, with the exception that values for trim IOs are
 -ignored. This option is mutually exclusive with the \fBcmdprio_percentage\fR
 -option.
 +writes.
 +.RS
 +.P
 +The first accepted format for this option is the same as the format of the
 +\fBbssplit\fR option:
 +.RS
 +.P
 +cmdprio_bssplit=blocksize/percentage:blocksize/percentage
 +.RE
 +.P
 +In this case, each entry will use the priority class and priority level defined
 +by the options \fBcmdprio_class\fR and \fBcmdprio\fR respectively.
 +.P
 +The second accepted format for this option is:
 +.RS
 +.P
 +cmdprio_bssplit=blocksize/percentage/class/level:blocksize/percentage/class/level
 +.RE
 +.P
 +In this case, the priority class and priority level is defined inside each
 +entry. In comparison with the first accepted format, the second accepted format
 +does not restrict all entries to have the same priority class and priority
 +level.
 +.P
 +For both formats, only the read and write data directions are supported, values
 +for trim IOs are ignored. This option is mutually exclusive with the
 +\fBcmdprio_percentage\fR option.
 +.RE
  .TP
  .BI (io_uring)fixedbufs
  If fio is asked to do direct IO, then Linux will map pages for each IO call, and
@@@ -2308,7 -2284,7 +2308,7 @@@ With writefua option set to 1, write op
  unit access (fua) flag. Default: 0.
  .TP
  .BI (sg)sg_write_mode \fR=\fPstr
 -Specify the type of write commands to issue. This option can take three
 +Specify the type of write commands to issue. This option can take multiple
  values:
  .RS
  .RS
  .B write (default)
  Write opcodes are issued as usual
  .TP
 +.B write_and_verify
 +Issue WRITE AND VERIFY commands. The BYTCHK bit is set to 00b. This directs the
 +device to carry out a medium verification with no data comparison for the data
 +that was written. The writefua option is ignored with this selection.
 +.TP
  .B verify
 -Issue WRITE AND VERIFY commands. The BYTCHK bit is set to 0. This
 -directs the device to carry out a medium verification with no data
 -comparison. The writefua option is ignored with this selection.
 +This option is deprecated. Use write_and_verify instead.
  .TP
 -.B same
 +.B write_same
  Issue WRITE SAME commands. This transfers a single block to the device
  and writes this same block of data to a contiguous sequence of LBAs
  beginning at the specified offset. fio's block size parameter
@@@ -2335,43 -2308,9 +2335,43 @@@ blocksize=8k will write 16 sectors wit
  generate 8k of data for each command butonly the first 512 bytes will
  be used and transferred to the device. The writefua option is ignored
  with this selection.
 +.TP
 +.B same
 +This option is deprecated. Use write_same instead.
 +.TP
 +.B write_same_ndob
 +Issue WRITE SAME(16) commands as above but with the No Data Output
 +Buffer (NDOB) bit set. No data will be transferred to the device with
 +this bit set. Data written will be a pre-determined pattern such as
 +all zeroes.
 +.TP
 +.B write_stream
 +Issue WRITE STREAM(16) commands. Use the stream_id option to specify
 +the stream identifier.
 +.TP
 +.B verify_bytchk_00
 +Issue VERIFY commands with BYTCHK set to 00. This directs the device to carry
 +out a medium verification with no data comparison.
 +.TP
 +.B verify_bytchk_01
 +Issue VERIFY commands with BYTCHK set to 01. This directs the device to
 +compare the data on the device with the data transferred to the device.
 +.TP
 +.B verify_bytchk_11
 +Issue VERIFY commands with BYTCHK set to 11. This transfers a single block to
 +the device and compares the contents of this block with the data on the device
 +beginning at the specified offset. fio's block size parameter specifies the
 +total amount of data compared with this command. However, only one block
 +(sector) worth of data is transferred to the device. This is similar to the
 +WRITE SAME command except that data is compared instead of written.
  .RE
  .RE
  .TP
 +.BI (sg)stream_id \fR=\fPint
 +Set the stream identifier for WRITE STREAM commands. If this is set to 0 (which is not
 +a valid stream identifier) fio will open a stream and then close it when done. Default
 +is 0.
 +.TP
  .BI (nbd)uri \fR=\fPstr
  Specify the NBD URI of the server to test.
  The string is a standard NBD URI (see
@@@ -3384,6 -3323,17 +3384,17 @@@ If set, fio will log Unix timestamps t
  write_type_log for each log type, instead of the default zero-based
  timestamps.
  .TP
+ .BI log_alternate_epoch \fR=\fPbool
+ If set, fio will log timestamps based on the epoch used by the clock specified
+ in the \fBlog_alternate_epoch_clock_id\fR option, to the log files produced by
+ enabling write_type_log for each log type, instead of the default zero-based
+ timestamps.
+ .TP
+ .BI log_alternate_epoch_clock_id \fR=\fPint
+ Specifies the clock_id to be used by clock_gettime to obtain the alternate epoch
+ if either \fBBlog_unix_epoch\fR or \fBlog_alternate_epoch\fR are true. Otherwise has no
+ effect. Default value is 0, or CLOCK_REALTIME.
+ .TP
  .BI block_error_percentiles \fR=\fPbool
  If set, record errors in trim block-sized units from writes and trims and
  output a histogram of how many trims it took to get to errors, and what kind
diff --combined fio.h
index 1ea3d064c913510ff1ada2be0c4206ba99ee5474,e9cdba94a6091f0fb7e9f72a34e2dd6b317da64b..7b0ca8435978f7eb1216d287584c780941fe4049
--- 1/fio.h
--- 2/fio.h
+++ b/fio.h
@@@ -380,7 -380,7 +380,7 @@@ struct thread_data 
  
        struct timespec start;  /* start of this loop */
        struct timespec epoch;  /* time job was started */
-       unsigned long long unix_epoch; /* Time job was started, unix epoch based. */
+       unsigned long long alternate_epoch; /* Time job was started, clock_gettime's clock_id epoch based. */
        struct timespec last_issue;
        long time_offset;
        struct timespec ts_cache;
        struct flist_head io_log_list;
        FILE *io_log_rfile;
        unsigned int io_log_blktrace;
 +      unsigned int io_log_blktrace_swap;
 +      unsigned long long io_log_blktrace_last_ttime;
        unsigned int io_log_current;
        unsigned int io_log_checkmark;
        unsigned int io_log_highmark;
diff --combined options.c
index dcf5e09fc2f5cdd733c3a5093482c38400ec6b15,0d7dc0797312aef62f5f79f4f227bfed5dfe6b3c..6cdbd2686cf5e715b8dd76baae0d23d1fca5088a
+++ b/options.c
@@@ -278,128 -278,6 +278,128 @@@ static int str_bssplit_cb(void *data, c
        return ret;
  }
  
 +static int parse_cmdprio_bssplit_entry(struct thread_options *o,
 +                                     struct split_prio *entry, char *str)
 +{
 +      int matches = 0;
 +      char *bs_str = NULL;
 +      long long bs_val;
 +      unsigned int perc = 0, class, level;
 +
 +      /*
 +       * valid entry formats:
 +       * bs/ - %s/ - set perc to 0, prio to -1.
 +       * bs/perc - %s/%u - set prio to -1.
 +       * bs/perc/class/level - %s/%u/%u/%u
 +       */
 +      matches = sscanf(str, "%m[^/]/%u/%u/%u", &bs_str, &perc, &class, &level);
 +      if (matches < 1) {
 +              log_err("fio: invalid cmdprio_bssplit format\n");
 +              return 1;
 +      }
 +
 +      if (str_to_decimal(bs_str, &bs_val, 1, o, 0, 0)) {
 +              log_err("fio: split conversion failed\n");
 +              free(bs_str);
 +              return 1;
 +      }
 +      free(bs_str);
 +
 +      entry->bs = bs_val;
 +      entry->perc = min(perc, 100u);
 +      entry->prio = -1;
 +      switch (matches) {
 +      case 1: /* bs/ case */
 +      case 2: /* bs/perc case */
 +              break;
 +      case 4: /* bs/perc/class/level case */
 +              class = min(class, (unsigned int) IOPRIO_MAX_PRIO_CLASS);
 +              level = min(level, (unsigned int) IOPRIO_MAX_PRIO);
 +              entry->prio = ioprio_value(class, level);
 +              break;
 +      default:
 +              log_err("fio: invalid cmdprio_bssplit format\n");
 +              return 1;
 +      }
 +
 +      return 0;
 +}
 +
 +/*
 + * Returns a negative integer if the first argument should be before the second
 + * argument in the sorted list. A positive integer if the first argument should
 + * be after the second argument in the sorted list. A zero if they are equal.
 + */
 +static int fio_split_prio_cmp(const void *p1, const void *p2)
 +{
 +      const struct split_prio *tmp1 = p1;
 +      const struct split_prio *tmp2 = p2;
 +
 +      if (tmp1->bs > tmp2->bs)
 +              return 1;
 +      if (tmp1->bs < tmp2->bs)
 +              return -1;
 +      return 0;
 +}
 +
 +int split_parse_prio_ddir(struct thread_options *o, struct split_prio **entries,
 +                        int *nr_entries, char *str)
 +{
 +      struct split_prio *tmp_entries;
 +      unsigned int nr_bssplits;
 +      char *str_cpy, *p, *fname;
 +
 +      /* strsep modifies the string, dup it so that we can use strsep twice */
 +      p = str_cpy = strdup(str);
 +      if (!p)
 +              return 1;
 +
 +      nr_bssplits = 0;
 +      while ((fname = strsep(&str_cpy, ":")) != NULL) {
 +              if (!strlen(fname))
 +                      break;
 +              nr_bssplits++;
 +      }
 +      free(p);
 +
 +      if (nr_bssplits > BSSPLIT_MAX) {
 +              log_err("fio: too many cmdprio_bssplit entries\n");
 +              return 1;
 +      }
 +
 +      tmp_entries = calloc(nr_bssplits, sizeof(*tmp_entries));
 +      if (!tmp_entries)
 +              return 1;
 +
 +      nr_bssplits = 0;
 +      while ((fname = strsep(&str, ":")) != NULL) {
 +              struct split_prio *entry;
 +
 +              if (!strlen(fname))
 +                      break;
 +
 +              entry = &tmp_entries[nr_bssplits];
 +
 +              if (parse_cmdprio_bssplit_entry(o, entry, fname)) {
 +                      log_err("fio: failed to parse cmdprio_bssplit entry\n");
 +                      free(tmp_entries);
 +                      return 1;
 +              }
 +
 +              /* skip zero perc entries, they provide no useful information */
 +              if (entry->perc)
 +                      nr_bssplits++;
 +      }
 +
 +      qsort(tmp_entries, nr_bssplits, sizeof(*tmp_entries),
 +            fio_split_prio_cmp);
 +
 +      *entries = tmp_entries;
 +      *nr_entries = nr_bssplits;
 +
 +      return 0;
 +}
 +
  static int str2error(char *str)
  {
        const char *err[] = { "EPERM", "ENOENT", "ESRCH", "EINTR", "EIO",
@@@ -4514,6 -4392,24 +4514,24 @@@ struct fio_option fio_options[FIO_MAX_O
                .category = FIO_OPT_C_LOG,
                .group = FIO_OPT_G_INVALID,
        },
+       {
+               .name = "log_alternate_epoch",
+               .lname = "Log epoch alternate",
+               .type = FIO_OPT_BOOL,
+               .off1 = offsetof(struct thread_options, log_alternate_epoch),
+               .help = "Use alternate epoch time in log files. Uses the same epoch as that is used by clock_gettime with specified log_alternate_epoch_clock_id.",
+               .category = FIO_OPT_C_LOG,
+               .group = FIO_OPT_G_INVALID,
+       },
+       {
+               .name = "log_alternate_epoch_clock_id",
+               .lname = "Log alternate epoch clock_id",
+               .type = FIO_OPT_INT,
+               .off1 = offsetof(struct thread_options, log_alternate_epoch_clock_id),
+               .help = "If log_alternate_epoch or log_unix_epoch is true, this option specifies the clock_id from clock_gettime whose epoch should be used. If neither of those is true, this option has no effect. Default value is 0, or CLOCK_REALTIME",
+               .category = FIO_OPT_C_LOG,
+               .group = FIO_OPT_G_INVALID,
+       },
        {
                .name   = "block_error_percentiles",
                .lname  = "Block error percentiles",
diff --combined os/windows/posix.c
index 0978bcf6f4638cecfe019c36b24ae15f0f1579f0,f1df2d76e0abe4ec0891be04fb2632366832512d..0d415e1e0dd0272f7ea797b4e8735812f83522f2
@@@ -537,16 -537,21 +537,21 @@@ int fcntl(int fildes, int cmd, ...
  return 0;
  }
  
+ #ifndef CLOCK_MONOTONIC_RAW
+ #define CLOCK_MONOTONIC_RAW 4
+ #endif
  /*
   * Get the value of a local clock source.
-  * This implementation supports 2 clocks: CLOCK_MONOTONIC provides high-accuracy
-  * relative time, while CLOCK_REALTIME provides a low-accuracy wall time.
+  * This implementation supports 3 clocks: CLOCK_MONOTONIC/CLOCK_MONOTONIC_RAW
+  * provide high-accuracy relative time, while CLOCK_REALTIME provides a
+  * low-accuracy wall time.
   */
  int clock_gettime(clockid_t clock_id, struct timespec *tp)
  {
        int rc = 0;
  
-       if (clock_id == CLOCK_MONOTONIC) {
+       if (clock_id == CLOCK_MONOTONIC || clock_id == CLOCK_MONOTONIC_RAW) {
                static LARGE_INTEGER freq = {{0,0}};
                LARGE_INTEGER counts;
                uint64_t t;
@@@ -1026,174 -1031,3 +1031,174 @@@ in_addr_t inet_network(const char *cp
        hbo = ((nbo & 0xFF) << 24) + ((nbo & 0xFF00) << 8) + ((nbo & 0xFF0000) >> 8) + ((nbo & 0xFF000000) >> 24);
        return hbo;
  }
 +
 +static HANDLE create_named_pipe(char *pipe_name, int wait_connect_time)
 +{
 +      HANDLE hpipe;
 +
 +      hpipe = CreateNamedPipe (
 +                      pipe_name,
 +                      PIPE_ACCESS_DUPLEX,
 +                      PIPE_WAIT | PIPE_TYPE_BYTE,
 +                      1, 0, 0, wait_connect_time, NULL);
 +
 +      if (hpipe == INVALID_HANDLE_VALUE) {
 +              log_err("ConnectNamedPipe failed (%lu).\n", GetLastError());
 +              return INVALID_HANDLE_VALUE;
 +      }
 +
 +      if (!ConnectNamedPipe(hpipe, NULL)) {
 +              log_err("ConnectNamedPipe failed (%lu).\n", GetLastError());
 +              CloseHandle(hpipe);
 +              return INVALID_HANDLE_VALUE;
 +      }
 +
 +      return hpipe;
 +}
 +
 +static BOOL windows_create_process(PROCESS_INFORMATION *pi, const char *args, HANDLE *hjob)
 +{
 +      LPSTR this_cmd_line = GetCommandLine();
 +      LPSTR new_process_cmd_line = malloc((strlen(this_cmd_line)+strlen(args)) * sizeof(char *));
 +      STARTUPINFO si = {0};
 +      DWORD flags = 0;
 +
 +      strcpy(new_process_cmd_line, this_cmd_line);
 +      strcat(new_process_cmd_line, args);
 +
 +      si.cb = sizeof(si);
 +      memset(pi, 0, sizeof(*pi));
 +
 +      if ((hjob != NULL) && (*hjob != INVALID_HANDLE_VALUE))
 +              flags = CREATE_SUSPENDED | CREATE_BREAKAWAY_FROM_JOB;
 +
 +      flags |= CREATE_NEW_CONSOLE;
 +
 +      if( !CreateProcess( NULL,
 +              new_process_cmd_line,
 +              NULL,    /* Process handle not inherited */
 +              NULL,    /* Thread handle not inherited */
 +              TRUE,    /* no handle inheritance */
 +              flags,
 +              NULL,    /* Use parent's environment block */
 +              NULL,    /* Use parent's starting directory */
 +              &si,
 +              pi )
 +      )
 +      {
 +              log_err("CreateProcess failed (%lu).\n", GetLastError() );
 +              free(new_process_cmd_line);
 +              return 1;
 +      }
 +      if ((hjob != NULL) && (*hjob != INVALID_HANDLE_VALUE)) {
 +              BOOL ret = AssignProcessToJobObject(*hjob, pi->hProcess);
 +              if (!ret) {
 +                      log_err("AssignProcessToJobObject failed (%lu).\n", GetLastError() );
 +                      return 1;
 +              }
 +
 +              ResumeThread(pi->hThread);
 +      }
 +
 +      free(new_process_cmd_line);
 +      return 0;
 +}
 +
 +HANDLE windows_create_job(void)
 +{
 +      JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = { 0 };
 +      BOOL success;
 +      HANDLE hjob = CreateJobObject(NULL, NULL);
 +
 +      jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
 +      success = SetInformationJobObject(hjob, JobObjectExtendedLimitInformation, &jeli, sizeof(jeli));
 +      if ( success == 0 ) {
 +        log_err( "SetInformationJobObject failed: error %lu\n", GetLastError() );
 +        return INVALID_HANDLE_VALUE;
 +    }
 +      return hjob;
 +}
 +
 +/* wait for a child process to either exit or connect to a child */
 +static bool monitor_process_till_connect(PROCESS_INFORMATION *pi, HANDLE *hpipe)
 +{
 +      bool connected = FALSE;
 +      bool process_alive = TRUE;
 +      char buffer[32] = {0};
 +      DWORD bytes_read;
 +
 +      do {
 +              DWORD exit_code;
 +              GetExitCodeProcess(pi->hProcess, &exit_code);
 +              if (exit_code != STILL_ACTIVE) {
 +                      dprint(FD_PROCESS, "process %u exited %d\n", GetProcessId(pi->hProcess), exit_code);
 +                      break;
 +              }
 +
 +              memset(buffer, 0, sizeof(buffer));
 +              ReadFile(*hpipe, &buffer, sizeof(buffer) - 1, &bytes_read, NULL);
 +              if (bytes_read && strstr(buffer, "connected")) {
 +                      dprint(FD_PROCESS, "process %u connected to client\n", GetProcessId(pi->hProcess));
 +                      connected = TRUE;
 +              }
 +              usleep(10*1000);
 +      } while (process_alive && !connected);
 +      return connected;
 +}
 +
 +/*create a process with --server-internal to emulate fork() */
 +HANDLE windows_handle_connection(HANDLE hjob, int sk)
 +{
 +      char pipe_name[64] =  "\\\\.\\pipe\\fiointernal-";
 +      char args[128] = " --server-internal=";
 +      PROCESS_INFORMATION pi;
 +      HANDLE hpipe = INVALID_HANDLE_VALUE;
 +      WSAPROTOCOL_INFO protocol_info;
 +      HANDLE ret;
 +
 +      sprintf(pipe_name+strlen(pipe_name), "%d", GetCurrentProcessId());
 +      sprintf(args+strlen(args), "%s", pipe_name);
 +
 +      if (windows_create_process(&pi, args, &hjob) != 0)
 +              return INVALID_HANDLE_VALUE;
 +      else
 +              ret = pi.hProcess;
 +
 +      /* duplicate socket and write the protocol_info to pipe so child can
 +       * duplicate the communciation socket */
 +      if (WSADuplicateSocket(sk, GetProcessId(pi.hProcess), &protocol_info)) {
 +              log_err("WSADuplicateSocket failed (%lu).\n", GetLastError());
 +              ret = INVALID_HANDLE_VALUE;
 +              goto cleanup;
 +      }
 +
 +      /* make a pipe with a unique name based upon processid */
 +      hpipe = create_named_pipe(pipe_name, 1000);
 +      if (hpipe == INVALID_HANDLE_VALUE) {
 +              ret = INVALID_HANDLE_VALUE;
 +              goto cleanup;
 +      }
 +
 +      if (!WriteFile(hpipe, &protocol_info, sizeof(protocol_info), NULL, NULL)) {
 +              log_err("WriteFile failed (%lu).\n", GetLastError());
 +              ret = INVALID_HANDLE_VALUE;
 +              goto cleanup;
 +      }
 +
 +      dprint(FD_PROCESS, "process %d created child process %u\n", GetCurrentProcessId(), GetProcessId(pi.hProcess));
 +
 +      /* monitor the process until it either exits or connects. This level
 +       * doesnt care which of those occurs because the result is that it
 +       * needs to loop around and create another child process to monitor */
 +      if (!monitor_process_till_connect(&pi, &hpipe))
 +              ret = INVALID_HANDLE_VALUE;
 +
 +cleanup:
 +      /* close the handles and pipes because this thread is done monitoring them */
 +      if (ret == INVALID_HANDLE_VALUE)
 +              CloseHandle(pi.hProcess);
 +      CloseHandle(pi.hThread);
 +      DisconnectNamedPipe(hpipe);
 +      CloseHandle(hpipe);
 +      return ret;
 +}
diff --combined rate-submit.c
index e3a71168013f6526190b75323b6cd06160488a57,13a0d706e078be41a53a31efc8d0a70f511d648f..268356d17a1f1b35a6eaa0ad82219db86177a4de
@@@ -173,7 -173,7 +173,7 @@@ static int io_workqueue_init_worker_fn(
        if (td->io_ops->post_init && td->io_ops->post_init(td))
                goto err_io_init;
  
-       set_epoch_time(td, td->o.log_unix_epoch);
+       set_epoch_time(td, td->o.log_unix_epoch | td->o.log_alternate_epoch, td->o.log_alternate_epoch_clock_id);
        fio_getrusage(&td->ru_start);
        clear_io_state(td, 1);
  
@@@ -195,16 -195,7 +195,16 @@@ static void io_workqueue_exit_worker_fn
        struct thread_data *td = sw->priv;
  
        (*sum_cnt)++;
 -      sum_thread_stats(&sw->wq->td->ts, &td->ts, *sum_cnt == 1);
 +
 +      /*
 +       * io_workqueue_update_acct_fn() doesn't support per prio stats, and
 +       * even if it did, offload can't be used with all async IO engines.
 +       * If group reporting is set in the parent td, the group result
 +       * generated by __show_run_stats() can still contain multiple prios
 +       * from different offloaded jobs.
 +       */
 +      sw->wq->td->ts.disable_prio_stat = 1;
 +      sum_thread_stats(&sw->wq->td->ts, &td->ts);
  
        fio_options_free(td);
        close_and_free_files(td);
diff --combined stat.c
index 24fc679fa1c39d7f79db798c9175b58f559139a8,98f30107d1ada1a3f49ebcb580089a87a884ffeb..0876222a1b6be28d0f5fd7ce6b278e6f90c099ff
--- 1/stat.c
--- 2/stat.c
+++ b/stat.c
@@@ -265,18 -265,6 +265,18 @@@ static void show_clat_percentiles(uint6
        free(ovals);
  }
  
 +static int get_nr_prios_with_samples(struct thread_stat *ts, enum fio_ddir ddir)
 +{
 +      int i, nr_prios_with_samples = 0;
 +
 +      for (i = 0; i < ts->nr_clat_prio[ddir]; i++) {
 +              if (ts->clat_prio[ddir][i].clat_stat.samples)
 +                      nr_prios_with_samples++;
 +      }
 +
 +      return nr_prios_with_samples;
 +}
 +
  bool calc_lat(struct io_stat *is, unsigned long long *min,
              unsigned long long *max, double *mean, double *dev)
  {
@@@ -301,10 -289,9 +301,10 @@@ void show_mixed_group_stats(struct grou
  {
        char *io, *agg, *min, *max;
        char *ioalt, *aggalt, *minalt, *maxalt;
 -      uint64_t io_mix = 0, agg_mix = 0, min_mix = -1, max_mix = 0, min_run = -1, max_run = 0;
 -      int i;
 +      uint64_t io_mix = 0, agg_mix = 0, min_mix = -1, max_mix = 0;
 +      uint64_t min_run = -1, max_run = 0;
        const int i2p = is_power_of_2(rs->kb_base);
 +      int i;
  
        for (i = 0; i < DDIR_RWDIR_CNT; i++) {
                if (!rs->max_run[i])
@@@ -376,9 -363,9 +376,9 @@@ void show_group_stats(struct group_run_
                free(minalt);
                free(maxalt);
        }
 -      
 +
        /* Need to aggregate statisitics to show mixed values */
 -      if (rs->unified_rw_rep == UNIFIED_BOTH) 
 +      if (rs->unified_rw_rep == UNIFIED_BOTH)
                show_mixed_group_stats(rs, out);
  }
  
@@@ -474,57 -461,179 +474,57 @@@ static void display_lat(const char *nam
        free(maxp);
  }
  
 -static double convert_agg_kbytes_percent(struct group_run_stats *rs, int ddir, int mean)
 +static struct thread_stat *gen_mixed_ddir_stats_from_ts(struct thread_stat *ts)
  {
 -      double p_of_agg = 100.0;
 -      if (rs && rs->agg[ddir] > 1024) {
 -              p_of_agg = mean * 100.0 / (double) (rs->agg[ddir] / 1024.0);
 -
 -              if (p_of_agg > 100.0)
 -                      p_of_agg = 100.0;
 -      }
 -      return p_of_agg;
 -}
 -
 -static void show_mixed_ddir_status(struct group_run_stats *rs, struct thread_stat *ts,
 -                           struct buf_output *out)
 -{
 -      unsigned long runt;
 -      unsigned long long min, max, bw, iops;
 -      double mean, dev;
 -      char *io_p, *bw_p, *bw_p_alt, *iops_p, *post_st = NULL;
        struct thread_stat *ts_lcl;
  
 -      int i2p;
 -      int ddir = 0;
 -
 -      /* Handle aggregation of Reads (ddir = 0), Writes (ddir = 1), and Trims (ddir = 2) */
 +      /*
 +       * Handle aggregation of Reads (ddir = 0), Writes (ddir = 1), and
 +       * Trims (ddir = 2)
 +       */
        ts_lcl = malloc(sizeof(struct thread_stat));
 -      memset((void *)ts_lcl, 0, sizeof(struct thread_stat));
 -      ts_lcl->unified_rw_rep = UNIFIED_MIXED;               /* calculate mixed stats  */
 -      init_thread_stat_min_vals(ts_lcl);
 -
 -      sum_thread_stats(ts_lcl, ts, 1);
 -
 -      assert(ddir_rw(ddir));
 -
 -      if (!ts_lcl->runtime[ddir])
 -              return;
 -
 -      i2p = is_power_of_2(rs->kb_base);
 -      runt = ts_lcl->runtime[ddir];
 -
 -      bw = (1000 * ts_lcl->io_bytes[ddir]) / runt;
 -      io_p = num2str(ts_lcl->io_bytes[ddir], ts->sig_figs, 1, i2p, N2S_BYTE);
 -      bw_p = num2str(bw, ts->sig_figs, 1, i2p, ts->unit_base);
 -      bw_p_alt = num2str(bw, ts->sig_figs, 1, !i2p, ts->unit_base);
 -
 -      iops = (1000 * ts_lcl->total_io_u[ddir]) / runt;
 -      iops_p = num2str(iops, ts->sig_figs, 1, 0, N2S_NONE);
 -
 -      log_buf(out, "  mixed: IOPS=%s, BW=%s (%s)(%s/%llumsec)%s\n",
 -                      iops_p, bw_p, bw_p_alt, io_p,
 -                      (unsigned long long) ts_lcl->runtime[ddir],
 -                      post_st ? : "");
 -
 -      free(post_st);
 -      free(io_p);
 -      free(bw_p);
 -      free(bw_p_alt);
 -      free(iops_p);
 -
 -      if (calc_lat(&ts_lcl->slat_stat[ddir], &min, &max, &mean, &dev))
 -              display_lat("slat", min, max, mean, dev, out);
 -      if (calc_lat(&ts_lcl->clat_stat[ddir], &min, &max, &mean, &dev))
 -              display_lat("clat", min, max, mean, dev, out);
 -      if (calc_lat(&ts_lcl->lat_stat[ddir], &min, &max, &mean, &dev))
 -              display_lat(" lat", min, max, mean, dev, out);
 -      if (calc_lat(&ts_lcl->clat_high_prio_stat[ddir], &min, &max, &mean, &dev)) {
 -              display_lat(ts_lcl->lat_percentiles ? "high prio_lat" : "high prio_clat",
 -                              min, max, mean, dev, out);
 -              if (calc_lat(&ts_lcl->clat_low_prio_stat[ddir], &min, &max, &mean, &dev))
 -                      display_lat(ts_lcl->lat_percentiles ? "low prio_lat" : "low prio_clat",
 -                                      min, max, mean, dev, out);
 -      }
 -
 -      if (ts->slat_percentiles && ts_lcl->slat_stat[ddir].samples > 0)
 -              show_clat_percentiles(ts_lcl->io_u_plat[FIO_SLAT][ddir],
 -                              ts_lcl->slat_stat[ddir].samples,
 -                              ts->percentile_list,
 -                              ts->percentile_precision, "slat", out);
 -      if (ts->clat_percentiles && ts_lcl->clat_stat[ddir].samples > 0)
 -              show_clat_percentiles(ts_lcl->io_u_plat[FIO_CLAT][ddir],
 -                              ts_lcl->clat_stat[ddir].samples,
 -                              ts->percentile_list,
 -                              ts->percentile_precision, "clat", out);
 -      if (ts->lat_percentiles && ts_lcl->lat_stat[ddir].samples > 0)
 -              show_clat_percentiles(ts_lcl->io_u_plat[FIO_LAT][ddir],
 -                              ts_lcl->lat_stat[ddir].samples,
 -                              ts->percentile_list,
 -                              ts->percentile_precision, "lat", out);
 -
 -      if (ts->clat_percentiles || ts->lat_percentiles) {
 -              const char *name = ts->lat_percentiles ? "lat" : "clat";
 -              char prio_name[32];
 -              uint64_t samples;
 -
 -              if (ts->lat_percentiles)
 -                      samples = ts_lcl->lat_stat[ddir].samples;
 -              else
 -                      samples = ts_lcl->clat_stat[ddir].samples;
 -
 -              /* Only print this if some high and low priority stats were collected */
 -              if (ts_lcl->clat_high_prio_stat[ddir].samples > 0 &&
 -                              ts_lcl->clat_low_prio_stat[ddir].samples > 0)
 -              {
 -                      sprintf(prio_name, "high prio (%.2f%%) %s",
 -                                      100. * (double) ts_lcl->clat_high_prio_stat[ddir].samples / (double) samples,
 -                                      name);
 -                      show_clat_percentiles(ts_lcl->io_u_plat_high_prio[ddir],
 -                                      ts_lcl->clat_high_prio_stat[ddir].samples,
 -                                      ts->percentile_list,
 -                                      ts->percentile_precision, prio_name, out);
 -
 -                      sprintf(prio_name, "low prio (%.2f%%) %s",
 -                                      100. * (double) ts_lcl->clat_low_prio_stat[ddir].samples / (double) samples,
 -                                      name);
 -                      show_clat_percentiles(ts_lcl->io_u_plat_low_prio[ddir],
 -                                      ts_lcl->clat_low_prio_stat[ddir].samples,
 -                                      ts->percentile_list,
 -                                      ts->percentile_precision, prio_name, out);
 -              }
 +      if (!ts_lcl) {
 +              log_err("fio: failed to allocate local thread stat\n");
 +              return NULL;
        }
  
 -      if (calc_lat(&ts_lcl->bw_stat[ddir], &min, &max, &mean, &dev)) {
 -              double p_of_agg = 100.0, fkb_base = (double)rs->kb_base;
 -              const char *bw_str;
 +      init_thread_stat(ts_lcl);
  
 -              if ((rs->unit_base == 1) && i2p)
 -                      bw_str = "Kibit";
 -              else if (rs->unit_base == 1)
 -                      bw_str = "kbit";
 -              else if (i2p)
 -                      bw_str = "KiB";
 -              else
 -                      bw_str = "kB";
 +      /* calculate mixed stats  */
 +      ts_lcl->unified_rw_rep = UNIFIED_MIXED;
 +      ts_lcl->lat_percentiles = ts->lat_percentiles;
 +      ts_lcl->clat_percentiles = ts->clat_percentiles;
 +      ts_lcl->slat_percentiles = ts->slat_percentiles;
 +      ts_lcl->percentile_precision = ts->percentile_precision;
 +      memcpy(ts_lcl->percentile_list, ts->percentile_list, sizeof(ts->percentile_list));
  
 -              p_of_agg = convert_agg_kbytes_percent(rs, ddir, mean);
 +      sum_thread_stats(ts_lcl, ts);
  
 -              if (rs->unit_base == 1) {
 -                      min *= 8.0;
 -                      max *= 8.0;
 -                      mean *= 8.0;
 -                      dev *= 8.0;
 -              }
 +      return ts_lcl;
 +}
  
 -              if (mean > fkb_base * fkb_base) {
 -                      min /= fkb_base;
 -                      max /= fkb_base;
 -                      mean /= fkb_base;
 -                      dev /= fkb_base;
 -                      bw_str = (rs->unit_base == 1 ? "Mibit" : "MiB");
 -              }
 +static double convert_agg_kbytes_percent(struct group_run_stats *rs,
 +                                       enum fio_ddir ddir, int mean)
 +{
 +      double p_of_agg = 100.0;
 +      if (rs && rs->agg[ddir] > 1024) {
 +              p_of_agg = mean * 100.0 / (double) (rs->agg[ddir] / 1024.0);
  
 -              log_buf(out, "   bw (%5s/s): min=%5llu, max=%5llu, per=%3.2f%%, "
 -                      "avg=%5.02f, stdev=%5.02f, samples=%" PRIu64 "\n",
 -                      bw_str, min, max, p_of_agg, mean, dev,
 -                      (&ts_lcl->bw_stat[ddir])->samples);
 -      }
 -      if (calc_lat(&ts_lcl->iops_stat[ddir], &min, &max, &mean, &dev)) {
 -              log_buf(out, "   iops        : min=%5llu, max=%5llu, "
 -                      "avg=%5.02f, stdev=%5.02f, samples=%" PRIu64 "\n",
 -                      min, max, mean, dev, (&ts_lcl->iops_stat[ddir])->samples);
 +              if (p_of_agg > 100.0)
 +                      p_of_agg = 100.0;
        }
 -
 -      free(ts_lcl);
 +      return p_of_agg;
  }
  
  static void show_ddir_status(struct group_run_stats *rs, struct thread_stat *ts,
 -                           int ddir, struct buf_output *out)
 +                           enum fio_ddir ddir, struct buf_output *out)
  {
        unsigned long runt;
        unsigned long long min, max, bw, iops;
        double mean, dev;
        char *io_p, *bw_p, *bw_p_alt, *iops_p, *post_st = NULL;
 -      int i2p;
 +      int i2p, i;
 +      const char *clat_type = ts->lat_percentiles ? "lat" : "clat";
  
        if (ddir_sync(ddir)) {
                if (calc_lat(&ts->sync_stat, &min, &max, &mean, &dev)) {
                display_lat("clat", min, max, mean, dev, out);
        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(ts->lat_percentiles ? "high prio_lat" : "high prio_clat",
 -                              min, max, mean, dev, out);
 -              if (calc_lat(&ts->clat_low_prio_stat[ddir], &min, &max, &mean, &dev))
 -                      display_lat(ts->lat_percentiles ? "low prio_lat" : "low prio_clat",
 -                                      min, max, mean, dev, out);
 +
 +      /* Only print per prio stats if there are >= 2 prios with samples */
 +      if (get_nr_prios_with_samples(ts, ddir) >= 2) {
 +              for (i = 0; i < ts->nr_clat_prio[ddir]; i++) {
 +                      if (calc_lat(&ts->clat_prio[ddir][i].clat_stat, &min,
 +                                   &max, &mean, &dev)) {
 +                              char buf[64];
 +
 +                              snprintf(buf, sizeof(buf),
 +                                       "%s prio %u/%u",
 +                                       clat_type,
 +                                       ts->clat_prio[ddir][i].ioprio >> 13,
 +                                       ts->clat_prio[ddir][i].ioprio & 7);
 +                              display_lat(buf, min, max, mean, dev, out);
 +                      }
 +              }
        }
  
        if (ts->slat_percentiles && ts->slat_stat[ddir].samples > 0)
                                        ts->percentile_precision, "lat", out);
  
        if (ts->clat_percentiles || ts->lat_percentiles) {
 -              const char *name = ts->lat_percentiles ? "lat" : "clat";
 -              char prio_name[32];
 +              char prio_name[64];
                uint64_t samples;
  
                if (ts->lat_percentiles)
                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_low_prio_stat[ddir].samples > 0)
 -              {
 -                      sprintf(prio_name, "high prio (%.2f%%) %s",
 -                                      100. * (double) ts->clat_high_prio_stat[ddir].samples / (double) samples,
 -                                      name);
 -                      show_clat_percentiles(ts->io_u_plat_high_prio[ddir],
 -                                              ts->clat_high_prio_stat[ddir].samples,
 -                                              ts->percentile_list,
 -                                              ts->percentile_precision, prio_name, out);
 -
 -                      sprintf(prio_name, "low prio (%.2f%%) %s",
 -                                      100. * (double) ts->clat_low_prio_stat[ddir].samples / (double) samples,
 -                                      name);
 -                      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);
 +              /* Only print per prio stats if there are >= 2 prios with samples */
 +              if (get_nr_prios_with_samples(ts, ddir) >= 2) {
 +                      for (i = 0; i < ts->nr_clat_prio[ddir]; i++) {
 +                              uint64_t prio_samples = ts->clat_prio[ddir][i].clat_stat.samples;
 +
 +                              if (prio_samples > 0) {
 +                                      snprintf(prio_name, sizeof(prio_name),
 +                                               "%s prio %u/%u (%.2f%% of IOs)",
 +                                               clat_type,
 +                                               ts->clat_prio[ddir][i].ioprio >> 13,
 +                                               ts->clat_prio[ddir][i].ioprio & 7,
 +                                               100. * (double) prio_samples / (double) samples);
 +                                      show_clat_percentiles(ts->clat_prio[ddir][i].io_u_plat,
 +                                                            prio_samples, ts->percentile_list,
 +                                                            ts->percentile_precision,
 +                                                            prio_name, out);
 +                              }
 +                      }
                }
        }
  
        }
  }
  
 +static void show_mixed_ddir_status(struct group_run_stats *rs,
 +                                 struct thread_stat *ts,
 +                                 struct buf_output *out)
 +{
 +      struct thread_stat *ts_lcl = gen_mixed_ddir_stats_from_ts(ts);
 +
 +      if (ts_lcl)
 +              show_ddir_status(rs, ts_lcl, DDIR_READ, out);
 +
 +      free_clat_prio_stats(ts_lcl);
 +      free(ts_lcl);
 +}
 +
  static bool show_lat(double *io_u_lat, int nr, const char **ranges,
                     const char *msg, struct buf_output *out)
  {
@@@ -1134,8 -1222,9 +1134,8 @@@ void show_disk_util(int terse, struct j
        if (!is_running_backend())
                return;
  
 -      if (flist_empty(&disk_list)) {
 +      if (flist_empty(&disk_list))
                return;
 -      }
  
        if ((output_format & FIO_OUTPUT_JSON) && parent)
                do_json = true;
        if (!terse && !do_json)
                log_buf(out, "\nDisk stats (read/write):\n");
  
 -      if (do_json)
 +      if (do_json) {
                json_object_add_disk_utils(parent, &disk_list);
 -      else if (output_format & ~(FIO_OUTPUT_JSON | FIO_OUTPUT_JSON_PLUS)) {
 +      else if (output_format & ~(FIO_OUTPUT_JSON | FIO_OUTPUT_JSON_PLUS)) {
                flist_for_each(entry, &disk_list) {
                        du = flist_entry(entry, struct disk_util, list);
  
@@@ -1274,9 -1363,8 +1274,9 @@@ static void show_thread_status_normal(s
  }
  
  static void show_ddir_status_terse(struct thread_stat *ts,
 -                                 struct group_run_stats *rs, int ddir,
 -                                 int ver, struct buf_output *out)
 +                                 struct group_run_stats *rs,
 +                                 enum fio_ddir ddir, int ver,
 +                                 struct buf_output *out)
  {
        unsigned long long min, max, minv, maxv, bw, iops;
        unsigned long long *ovals = NULL;
        else
                log_buf(out, ";%llu;%llu;%f;%f", 0ULL, 0ULL, 0.0, 0.0);
  
 -      if (ts->lat_percentiles)
 +      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)
 +      } 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++) {
                if (i >= len) {
                        log_buf(out, ";0%%=0");
                }
  
                log_buf(out, ";%llu;%llu;%f%%;%f;%f", min, max, p_of_agg, mean, dev);
 -      } else
 +      } else {
                log_buf(out, ";%llu;%llu;%f%%;%f;%f", 0ULL, 0ULL, 0.0, 0.0, 0.0);
 +      }
  
        if (ver == 5) {
                if (bw_stat)
@@@ -1370,19 -1456,28 +1370,19 @@@ static void show_mixed_ddir_status_ters
                                   struct group_run_stats *rs,
                                   int ver, struct buf_output *out)
  {
 -      struct thread_stat *ts_lcl;
 +      struct thread_stat *ts_lcl = gen_mixed_ddir_stats_from_ts(ts);
  
 -      /* Handle aggregation of Reads (ddir = 0), Writes (ddir = 1), and Trims (ddir = 2) */
 -      ts_lcl = malloc(sizeof(struct thread_stat));
 -      memset((void *)ts_lcl, 0, sizeof(struct thread_stat));
 -      ts_lcl->unified_rw_rep = UNIFIED_MIXED;               /* calculate mixed stats  */
 -      init_thread_stat_min_vals(ts_lcl);
 -      ts_lcl->lat_percentiles = ts->lat_percentiles;
 -      ts_lcl->clat_percentiles = ts->clat_percentiles;
 -      ts_lcl->slat_percentiles = ts->slat_percentiles;
 -      ts_lcl->percentile_precision = ts->percentile_precision;                
 -      memcpy(ts_lcl->percentile_list, ts->percentile_list, sizeof(ts->percentile_list));
 -      
 -      sum_thread_stats(ts_lcl, ts, 1);
 +      if (ts_lcl)
 +              show_ddir_status_terse(ts_lcl, rs, DDIR_READ, ver, out);
  
 -      /* add the aggregated stats to json parent */
 -      show_ddir_status_terse(ts_lcl, rs, DDIR_READ, ver, out);
 +      free_clat_prio_stats(ts_lcl);
        free(ts_lcl);
  }
  
 -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)
 +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;
  }
  
  static void add_ddir_status_json(struct thread_stat *ts,
 -              struct group_run_stats *rs, int ddir, struct json_object *parent)
 +                               struct group_run_stats *rs, enum fio_ddir ddir,
 +                               struct json_object *parent)
  {
        unsigned long long min, max;
        unsigned long long bw_bytes, bw;
        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) {
 -              const char *high, *low;
 +      /* Only include per prio stats if there are >= 2 prios with samples */
 +      if (get_nr_prios_with_samples(ts, ddir) >= 2) {
 +              struct json_array *array = json_create_array();
 +              const char *obj_name;
 +              int i;
  
 -              if (ts->lat_percentiles) {
 -                      high = "lat_high_prio";
 -                      low = "lat_low_prio";
 -              } else {
 -                      high = "clat_high_prio";
 -                      low = "clat_low_prio";
 +              if (ts->lat_percentiles)
 +                      obj_name = "lat_ns";
 +              else
 +                      obj_name = "clat_ns";
 +
 +              json_object_add_value_array(dir_object, "prios", array);
 +
 +              for (i = 0; i < ts->nr_clat_prio[ddir]; i++) {
 +                      if (ts->clat_prio[ddir][i].clat_stat.samples > 0) {
 +                              struct json_object *obj = json_create_object();
 +                              unsigned long long class, level;
 +
 +                              class = ts->clat_prio[ddir][i].ioprio >> 13;
 +                              json_object_add_value_int(obj, "prioclass", class);
 +                              level = ts->clat_prio[ddir][i].ioprio & 7;
 +                              json_object_add_value_int(obj, "prio", level);
 +
 +                              tmp_object = add_ddir_lat_json(ts,
 +                                                             ts->clat_percentiles | ts->lat_percentiles,
 +                                                             &ts->clat_prio[ddir][i].clat_stat,
 +                                                             ts->clat_prio[ddir][i].io_u_plat);
 +                              json_object_add_value_object(obj, obj_name, tmp_object);
 +                              json_array_add_value_object(array, obj);
 +                      }
                }
 -
 -              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);
 -
 -              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);
        }
  
        if (calc_lat(&ts->bw_stat[ddir], &min, &max, &mean, &dev)) {
  static void add_mixed_ddir_status_json(struct thread_stat *ts,
                struct group_run_stats *rs, struct json_object *parent)
  {
 -      struct thread_stat *ts_lcl;
 -
 -      /* Handle aggregation of Reads (ddir = 0), Writes (ddir = 1), and Trims (ddir = 2) */
 -      ts_lcl = malloc(sizeof(struct thread_stat));
 -      memset((void *)ts_lcl, 0, sizeof(struct thread_stat));
 -      ts_lcl->unified_rw_rep = UNIFIED_MIXED;               /* calculate mixed stats  */
 -      init_thread_stat_min_vals(ts_lcl);
 -      ts_lcl->lat_percentiles = ts->lat_percentiles;
 -      ts_lcl->clat_percentiles = ts->clat_percentiles;
 -      ts_lcl->slat_percentiles = ts->slat_percentiles;
 -      ts_lcl->percentile_precision = ts->percentile_precision;                
 -      memcpy(ts_lcl->percentile_list, ts->percentile_list, sizeof(ts->percentile_list));
 -
 -      sum_thread_stats(ts_lcl, ts, 1);
 +      struct thread_stat *ts_lcl = gen_mixed_ddir_stats_from_ts(ts);
  
        /* add the aggregated stats to json parent */
 -      add_ddir_status_json(ts_lcl, rs, DDIR_READ, parent);
 +      if (ts_lcl)
 +              add_ddir_status_json(ts_lcl, rs, DDIR_READ, parent);
 +
 +      free_clat_prio_stats(ts_lcl);
        free(ts_lcl);
  }
  
@@@ -1981,10 -2073,9 +1981,10 @@@ static void __sum_stat(struct io_stat *
   * numbers. For group_reporting, we should just add those up, not make
   * them the mean of everything.
   */
 -static void sum_stat(struct io_stat *dst, struct io_stat *src, bool first,
 -                   bool pure_sum)
 +static void sum_stat(struct io_stat *dst, struct io_stat *src, bool pure_sum)
  {
 +      bool first = dst->samples == 0;
 +
        if (src->samples == 0)
                return;
  
@@@ -2034,248 -2125,48 +2034,248 @@@ void sum_group_stats(struct group_run_s
                dst->sig_figs = src->sig_figs;
  }
  
 -void sum_thread_stats(struct thread_stat *dst, struct thread_stat *src,
 -                    bool first)
 +/*
 + * Free the clat_prio_stat arrays allocated by alloc_clat_prio_stat_ddir().
 + */
 +void free_clat_prio_stats(struct thread_stat *ts)
 +{
 +      enum fio_ddir ddir;
 +
 +      for (ddir = 0; ddir < DDIR_RWDIR_CNT; ddir++) {
 +              sfree(ts->clat_prio[ddir]);
 +              ts->clat_prio[ddir] = NULL;
 +              ts->nr_clat_prio[ddir] = 0;
 +      }
 +}
 +
 +/*
 + * Allocate a clat_prio_stat array. The array has to be allocated/freed using
 + * smalloc/sfree, so that it is accessible by the process/thread summing the
 + * thread_stats.
 + */
 +int alloc_clat_prio_stat_ddir(struct thread_stat *ts, enum fio_ddir ddir,
 +                            int nr_prios)
 +{
 +      struct clat_prio_stat *clat_prio;
 +      int i;
 +
 +      clat_prio = scalloc(nr_prios, sizeof(*ts->clat_prio[ddir]));
 +      if (!clat_prio) {
 +              log_err("fio: failed to allocate ts clat data\n");
 +              return 1;
 +      }
 +
 +      for (i = 0; i < nr_prios; i++)
 +              clat_prio[i].clat_stat.min_val = ULONG_MAX;
 +
 +      ts->clat_prio[ddir] = clat_prio;
 +      ts->nr_clat_prio[ddir] = nr_prios;
 +
 +      return 0;
 +}
 +
 +static int grow_clat_prio_stat(struct thread_stat *dst, enum fio_ddir ddir)
 +{
 +      int curr_len = dst->nr_clat_prio[ddir];
 +      void *new_arr;
 +
 +      new_arr = scalloc(curr_len + 1, sizeof(*dst->clat_prio[ddir]));
 +      if (!new_arr) {
 +              log_err("fio: failed to grow clat prio array\n");
 +              return 1;
 +      }
 +
 +      memcpy(new_arr, dst->clat_prio[ddir],
 +             curr_len * sizeof(*dst->clat_prio[ddir]));
 +      sfree(dst->clat_prio[ddir]);
 +
 +      dst->clat_prio[ddir] = new_arr;
 +      dst->clat_prio[ddir][curr_len].clat_stat.min_val = ULONG_MAX;
 +      dst->nr_clat_prio[ddir]++;
 +
 +      return 0;
 +}
 +
 +static int find_clat_prio_index(struct thread_stat *dst, enum fio_ddir ddir,
 +                              uint32_t ioprio)
 +{
 +      int i, nr_prios = dst->nr_clat_prio[ddir];
 +
 +      for (i = 0; i < nr_prios; i++) {
 +              if (dst->clat_prio[ddir][i].ioprio == ioprio)
 +                      return i;
 +      }
 +
 +      return -1;
 +}
 +
 +static int alloc_or_get_clat_prio_index(struct thread_stat *dst,
 +                                      enum fio_ddir ddir, uint32_t ioprio,
 +                                      int *idx)
 +{
 +      int index = find_clat_prio_index(dst, ddir, ioprio);
 +
 +      if (index == -1) {
 +              index = dst->nr_clat_prio[ddir];
 +
 +              if (grow_clat_prio_stat(dst, ddir))
 +                      return 1;
 +
 +              dst->clat_prio[ddir][index].ioprio = ioprio;
 +      }
 +
 +      *idx = index;
 +
 +      return 0;
 +}
 +
 +static int clat_prio_stats_copy(struct thread_stat *dst, struct thread_stat *src,
 +                              enum fio_ddir dst_ddir, enum fio_ddir src_ddir)
 +{
 +      size_t sz = sizeof(*src->clat_prio[src_ddir]) *
 +              src->nr_clat_prio[src_ddir];
 +
 +      dst->clat_prio[dst_ddir] = smalloc(sz);
 +      if (!dst->clat_prio[dst_ddir]) {
 +              log_err("fio: failed to alloc clat prio array\n");
 +              return 1;
 +      }
 +
 +      memcpy(dst->clat_prio[dst_ddir], src->clat_prio[src_ddir], sz);
 +      dst->nr_clat_prio[dst_ddir] = src->nr_clat_prio[src_ddir];
 +
 +      return 0;
 +}
 +
 +static int clat_prio_stat_add_samples(struct thread_stat *dst,
 +                                    enum fio_ddir dst_ddir, uint32_t ioprio,
 +                                    struct io_stat *io_stat,
 +                                    uint64_t *io_u_plat)
 +{
 +      int i, dst_index;
 +
 +      if (!io_stat->samples)
 +              return 0;
 +
 +      if (alloc_or_get_clat_prio_index(dst, dst_ddir, ioprio, &dst_index))
 +              return 1;
 +
 +      sum_stat(&dst->clat_prio[dst_ddir][dst_index].clat_stat, io_stat,
 +               false);
 +
 +      for (i = 0; i < FIO_IO_U_PLAT_NR; i++)
 +              dst->clat_prio[dst_ddir][dst_index].io_u_plat[i] += io_u_plat[i];
 +
 +      return 0;
 +}
 +
 +static int sum_clat_prio_stats_src_single_prio(struct thread_stat *dst,
 +                                             struct thread_stat *src,
 +                                             enum fio_ddir dst_ddir,
 +                                             enum fio_ddir src_ddir)
 +{
 +      struct io_stat *io_stat;
 +      uint64_t *io_u_plat;
 +
 +      /*
 +       * If src ts has no clat_prio_stat array, then all I/Os were submitted
 +       * using src->ioprio. Thus, the global samples in src->clat_stat (or
 +       * src->lat_stat) can be used as the 'per prio' samples for src->ioprio.
 +       */
 +      assert(!src->clat_prio[src_ddir]);
 +      assert(src->nr_clat_prio[src_ddir] == 0);
 +
 +      if (src->lat_percentiles) {
 +              io_u_plat = src->io_u_plat[FIO_LAT][src_ddir];
 +              io_stat = &src->lat_stat[src_ddir];
 +      } else {
 +              io_u_plat = src->io_u_plat[FIO_CLAT][src_ddir];
 +              io_stat = &src->clat_stat[src_ddir];
 +      }
 +
 +      return clat_prio_stat_add_samples(dst, dst_ddir, src->ioprio, io_stat,
 +                                        io_u_plat);
 +}
 +
 +static int sum_clat_prio_stats_src_multi_prio(struct thread_stat *dst,
 +                                            struct thread_stat *src,
 +                                            enum fio_ddir dst_ddir,
 +                                            enum fio_ddir src_ddir)
 +{
 +      int i;
 +
 +      /*
 +       * If src ts has a clat_prio_stat array, then there are multiple prios
 +       * in use (i.e. src ts had cmdprio_percentage or cmdprio_bssplit set).
 +       * The samples for the default prio will exist in the src->clat_prio
 +       * array, just like the samples for any other prio.
 +       */
 +      assert(src->clat_prio[src_ddir]);
 +      assert(src->nr_clat_prio[src_ddir]);
 +
 +      /* If the dst ts doesn't yet have a clat_prio array, simply memcpy. */
 +      if (!dst->clat_prio[dst_ddir])
 +              return clat_prio_stats_copy(dst, src, dst_ddir, src_ddir);
 +
 +      /* The dst ts already has a clat_prio_array, add src stats into it. */
 +      for (i = 0; i < src->nr_clat_prio[src_ddir]; i++) {
 +              struct io_stat *io_stat = &src->clat_prio[src_ddir][i].clat_stat;
 +              uint64_t *io_u_plat = src->clat_prio[src_ddir][i].io_u_plat;
 +              uint32_t ioprio = src->clat_prio[src_ddir][i].ioprio;
 +
 +              if (clat_prio_stat_add_samples(dst, dst_ddir, ioprio, io_stat, io_u_plat))
 +                      return 1;
 +      }
 +
 +      return 0;
 +}
 +
 +static int sum_clat_prio_stats(struct thread_stat *dst, struct thread_stat *src,
 +                             enum fio_ddir dst_ddir, enum fio_ddir src_ddir)
 +{
 +      if (dst->disable_prio_stat)
 +              return 0;
 +
 +      if (!src->clat_prio[src_ddir])
 +              return sum_clat_prio_stats_src_single_prio(dst, src, dst_ddir,
 +                                                         src_ddir);
 +
 +      return sum_clat_prio_stats_src_multi_prio(dst, src, dst_ddir, src_ddir);
 +}
 +
 +void sum_thread_stats(struct thread_stat *dst, struct thread_stat *src)
  {
        int k, l, m;
  
        for (l = 0; l < DDIR_RWDIR_CNT; l++) {
 -              if (!(dst->unified_rw_rep == UNIFIED_MIXED)) {
 -                      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_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);
 -                      sum_stat(&dst->iops_stat[l], &src->iops_stat[l], first, true);
 +              if (dst->unified_rw_rep != UNIFIED_MIXED) {
 +                      sum_stat(&dst->clat_stat[l], &src->clat_stat[l], false);
 +                      sum_stat(&dst->slat_stat[l], &src->slat_stat[l], false);
 +                      sum_stat(&dst->lat_stat[l], &src->lat_stat[l], false);
 +                      sum_stat(&dst->bw_stat[l], &src->bw_stat[l], true);
 +                      sum_stat(&dst->iops_stat[l], &src->iops_stat[l], true);
 +                      sum_clat_prio_stats(dst, src, l, l);
  
                        dst->io_bytes[l] += src->io_bytes[l];
  
                        if (dst->runtime[l] < src->runtime[l])
                                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[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);
 -                      sum_stat(&dst->iops_stat[0], &src->iops_stat[l], first, true);
 +                      sum_stat(&dst->clat_stat[0], &src->clat_stat[l], false);
 +                      sum_stat(&dst->slat_stat[0], &src->slat_stat[l], false);
 +                      sum_stat(&dst->lat_stat[0], &src->lat_stat[l], false);
 +                      sum_stat(&dst->bw_stat[0], &src->bw_stat[l], true);
 +                      sum_stat(&dst->iops_stat[0], &src->iops_stat[l], true);
 +                      sum_clat_prio_stats(dst, src, 0, l);
  
                        dst->io_bytes[0] += src->io_bytes[l];
  
                        if (dst->runtime[0] < src->runtime[l])
                                dst->runtime[0] = src->runtime[l];
 -
 -                      /*
 -                       * We're summing to the same destination, so override
 -                       * 'first' after the first iteration of the loop
 -                       */
 -                      first = false;
                }
        }
  
 -      sum_stat(&dst->sync_stat, &src->sync_stat, first, false);
 +      sum_stat(&dst->sync_stat, &src->sync_stat, false);
        dst->usr_time += src->usr_time;
        dst->sys_time += src->sys_time;
        dst->ctx += src->ctx;
                dst->io_u_lat_m[k] += src->io_u_lat_m[k];
  
        for (k = 0; k < DDIR_RWDIR_CNT; k++) {
 -              if (!(dst->unified_rw_rep == UNIFIED_MIXED)) {
 +              if (dst->unified_rw_rep != UNIFIED_MIXED) {
                        dst->total_io_u[k] += src->total_io_u[k];
                        dst->short_io_u[k] += src->short_io_u[k];
                        dst->drop_io_u[k] += src->drop_io_u[k];
        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 == UNIFIED_MIXED))
 +                              if (dst->unified_rw_rep != UNIFIED_MIXED)
                                        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 == UNIFIED_MIXED)) {
 -                              dst->io_u_plat_high_prio[k][m] += src->io_u_plat_high_prio[k][m];
 -                              dst->io_u_plat_low_prio[k][m] += src->io_u_plat_low_prio[k][m];
 -                      } else {
 -                              dst->io_u_plat_high_prio[0][m] += src->io_u_plat_high_prio[k][m];
 -                              dst->io_u_plat_low_prio[0][m] += src->io_u_plat_low_prio[k][m];
 -                      }
 -
 -              }
 -      }
 -
        dst->total_run_time += src->total_run_time;
        dst->total_submit += src->total_submit;
        dst->total_complete += src->total_complete;
@@@ -2347,6 -2251,8 +2347,6 @@@ void init_thread_stat_min_vals(struct t
                ts->lat_stat[i].min_val = ULONG_MAX;
                ts->bw_stat[i].min_val = ULONG_MAX;
                ts->iops_stat[i].min_val = ULONG_MAX;
 -              ts->clat_high_prio_stat[i].min_val = ULONG_MAX;
 -              ts->clat_low_prio_stat[i].min_val = ULONG_MAX;
        }
        ts->sync_stat.min_val = ULONG_MAX;
  }
@@@ -2359,58 -2265,6 +2359,58 @@@ void init_thread_stat(struct thread_sta
        ts->groupid = -1;
  }
  
 +static void init_per_prio_stats(struct thread_stat *threadstats, int nr_ts)
 +{
 +      struct thread_data *td;
 +      struct thread_stat *ts;
 +      int i, j, last_ts, idx;
 +      enum fio_ddir ddir;
 +
 +      j = 0;
 +      last_ts = -1;
 +      idx = 0;
 +
 +      /*
 +       * Loop through all tds, if a td requires per prio stats, temporarily
 +       * store a 1 in ts->disable_prio_stat, and then do an additional
 +       * loop at the end where we invert the ts->disable_prio_stat values.
 +       */
 +      for_each_td(td, i) {
 +              if (!td->o.stats)
 +                      continue;
 +              if (idx &&
 +                  (!td->o.group_reporting ||
 +                   (td->o.group_reporting && last_ts != td->groupid))) {
 +                      idx = 0;
 +                      j++;
 +              }
 +
 +              last_ts = td->groupid;
 +              ts = &threadstats[j];
 +
 +              /* idx == 0 means first td in group, or td is not in a group. */
 +              if (idx == 0)
 +                      ts->ioprio = td->ioprio;
 +              else if (td->ioprio != ts->ioprio)
 +                      ts->disable_prio_stat = 1;
 +
 +              for (ddir = 0; ddir < DDIR_RWDIR_CNT; ddir++) {
 +                      if (td->ts.clat_prio[ddir]) {
 +                              ts->disable_prio_stat = 1;
 +                              break;
 +                      }
 +              }
 +
 +              idx++;
 +      }
 +
 +      /* Loop through all dst threadstats and fixup the values. */
 +      for (i = 0; i < nr_ts; i++) {
 +              ts = &threadstats[i];
 +              ts->disable_prio_stat = !ts->disable_prio_stat;
 +      }
 +}
 +
  void __show_run_stats(void)
  {
        struct group_run_stats *runstats, *rs;
                opt_lists[i] = NULL;
        }
  
 +      init_per_prio_stats(threadstats, nr_ts);
 +
        j = 0;
        last_ts = -1;
        idx = 0;
                opt_lists[j] = &td->opt_list;
  
                idx++;
 -              ts->members++;
  
                if (ts->groupid == -1) {
                        /*
                for (k = 0; k < ts->nr_block_infos; k++)
                        ts->block_infos[k] = td->ts.block_infos[k];
  
 -              sum_thread_stats(ts, &td->ts, idx == 1);
 +              sum_thread_stats(ts, &td->ts);
 +
 +              ts->members++;
  
                if (td->o.ss_dur) {
                        ts->ss_state = td->ss.state;
        }
  
        for (i = 0; i < groupid + 1; i++) {
 -              int ddir;
 +              enum fio_ddir ddir;
  
                rs = &runstats[i];
  
  
        log_info_flush();
        free(runstats);
 +
 +      /* free arrays allocated by sum_thread_stats(), if any */
 +      for (i = 0; i < nr_ts; i++) {
 +              ts = &threadstats[i];
 +              free_clat_prio_stats(ts);
 +      }
        free(threadstats);
        free(opt_lists);
  }
@@@ -2837,14 -2682,6 +2837,14 @@@ static inline void add_stat_sample(stru
        is->samples++;
  }
  
 +static inline void add_stat_prio_sample(struct clat_prio_stat *clat_prio,
 +                                      unsigned short clat_prio_index,
 +                                      unsigned long long nsec)
 +{
 +      if (clat_prio)
 +              add_stat_sample(&clat_prio[clat_prio_index].clat_stat, nsec);
 +}
 +
  /*
   * Return a struct io_logs, which is added to the tail of the log
   * list for 'iolog'.
@@@ -3017,7 -2854,7 +3017,7 @@@ static void __add_log_sample(struct io_
                s = get_sample(iolog, cur_log, cur_log->nr_samples);
  
                s->data = data;
-               s->time = t + (iolog->td ? iolog->td->unix_epoch : 0);
+               s->time = t + (iolog->td ? iolog->td->alternate_epoch : 0);
                io_sample_set_ddir(iolog, s, ddir);
                s->bs = bs;
                s->priority = priority;
@@@ -3042,36 -2879,14 +3042,36 @@@ static inline void reset_io_stat(struc
        ios->mean.u.f = ios->S.u.f = 0;
  }
  
 +static inline void reset_io_u_plat(uint64_t *io_u_plat)
 +{
 +      int i;
 +
 +      for (i = 0; i < FIO_IO_U_PLAT_NR; i++)
 +              io_u_plat[i] = 0;
 +}
 +
 +static inline void reset_clat_prio_stats(struct thread_stat *ts)
 +{
 +      enum fio_ddir ddir;
 +      int i;
 +
 +      for (ddir = 0; ddir < DDIR_RWDIR_CNT; ddir++) {
 +              if (!ts->clat_prio[ddir])
 +                      continue;
 +
 +              for (i = 0; i < ts->nr_clat_prio[ddir]; i++) {
 +                      reset_io_stat(&ts->clat_prio[ddir][i].clat_stat);
 +                      reset_io_u_plat(ts->clat_prio[ddir][i].io_u_plat);
 +              }
 +      }
 +}
 +
  void reset_io_stats(struct thread_data *td)
  {
        struct thread_stat *ts = &td->ts;
 -      int i, j, k;
 +      int i, j;
  
        for (i = 0; i < DDIR_RWDIR_CNT; i++) {
 -              reset_io_stat(&ts->clat_high_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]);
                ts->total_io_u[i] = 0;
                ts->short_io_u[i] = 0;
                ts->drop_io_u[i] = 0;
 -
 -              for (j = 0; j < FIO_IO_U_PLAT_NR; j++) {
 -                      ts->io_u_plat_high_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;
 +                      reset_io_u_plat(ts->io_u_plat[i][j]);
 +
 +      reset_clat_prio_stats(ts);
  
        ts->total_io_u[DDIR_SYNC] = 0;
 +      reset_io_u_plat(ts->io_u_sync_plat);
  
        for (i = 0; i < FIO_IO_U_MAP_NR; i++) {
                ts->io_u_map[i] = 0;
@@@ -3138,7 -2958,7 +3138,7 @@@ static void __add_stat_to_log(struct io
  static void _add_stat_to_log(struct io_log *iolog, unsigned long elapsed,
                             bool log_max)
  {
 -      int ddir;
 +      enum fio_ddir ddir;
  
        for (ddir = 0; ddir < DDIR_RWDIR_CNT; ddir++)
                __add_stat_to_log(iolog, ddir, elapsed, log_max);
@@@ -3243,21 -3063,22 +3243,21 @@@ static inline void add_lat_percentile_s
        ts->io_u_plat[lat][ddir][idx]++;
  }
  
 -static inline void add_lat_percentile_prio_sample(struct thread_stat *ts,
 -                                                unsigned long long nsec,
 -                                                enum fio_ddir ddir,
 -                                                bool high_prio)
 +static inline void
 +add_lat_percentile_prio_sample(struct thread_stat *ts, unsigned long long nsec,
 +                             enum fio_ddir ddir,
 +                             unsigned short clat_prio_index)
  {
        unsigned int idx = plat_val_to_idx(nsec);
  
 -      if (!high_prio)
 -              ts->io_u_plat_low_prio[ddir][idx]++;
 -      else
 -              ts->io_u_plat_high_prio[ddir][idx]++;
 +      if (ts->clat_prio[ddir])
 +              ts->clat_prio[ddir][clat_prio_index].io_u_plat[idx]++;
  }
  
  void add_clat_sample(struct thread_data *td, enum fio_ddir ddir,
                     unsigned long long nsec, unsigned long long bs,
 -                   uint64_t offset, unsigned int ioprio, bool high_prio)
 +                   uint64_t offset, unsigned int ioprio,
 +                   unsigned short clat_prio_index)
  {
        const bool needs_lock = td_async_processing(td);
        unsigned long elapsed, this_window;
        add_stat_sample(&ts->clat_stat[ddir], nsec);
  
        /*
 -       * When lat_percentiles=1 (default 0), the reported high/low priority
 +       * When lat_percentiles=1 (default 0), the reported per priority
         * percentiles and stats are used for describing total latency values,
         * even though the variable names themselves start with clat_.
         *
         * lat_percentiles=0. add_lat_sample() will add the prio stat sample
         * when lat_percentiles=1.
         */
 -      if (!ts->lat_percentiles) {
 -              if (high_prio)
 -                      add_stat_sample(&ts->clat_high_prio_stat[ddir], nsec);
 -              else
 -                      add_stat_sample(&ts->clat_low_prio_stat[ddir], nsec);
 -      }
 +      if (!ts->lat_percentiles)
 +              add_stat_prio_sample(ts->clat_prio[ddir], clat_prio_index,
 +                                   nsec);
  
        if (td->clat_log)
                add_log_sample(td, td->clat_log, sample_val(nsec), ddir, bs,
                add_lat_percentile_sample(ts, nsec, ddir, FIO_CLAT);
                if (!ts->lat_percentiles)
                        add_lat_percentile_prio_sample(ts, nsec, ddir,
 -                                                     high_prio);
 +                                                     clat_prio_index);
        }
  
        if (iolog && iolog->hist_msec) {
@@@ -3368,8 -3192,7 +3368,8 @@@ void add_slat_sample(struct thread_dat
  
  void add_lat_sample(struct thread_data *td, enum fio_ddir ddir,
                    unsigned long long nsec, unsigned long long bs,
 -                  uint64_t offset, unsigned int ioprio, bool high_prio)
 +                  uint64_t offset, unsigned int ioprio,
 +                  unsigned short clat_prio_index)
  {
        const bool needs_lock = td_async_processing(td);
        struct thread_stat *ts = &td->ts;
                               offset, ioprio);
  
        /*
 -       * When lat_percentiles=1 (default 0), the reported high/low priority
 +       * When lat_percentiles=1 (default 0), the reported per priority
         * percentiles and stats are used for describing total latency values,
         * even though the variable names themselves start with clat_.
         *
         */
        if (ts->lat_percentiles) {
                add_lat_percentile_sample(ts, nsec, ddir, FIO_LAT);
 -              add_lat_percentile_prio_sample(ts, nsec, ddir, high_prio);
 -              if (high_prio)
 -                      add_stat_sample(&ts->clat_high_prio_stat[ddir], nsec);
 -              else
 -                      add_stat_sample(&ts->clat_low_prio_stat[ddir], nsec);
 -
 +              add_lat_percentile_prio_sample(ts, nsec, ddir, clat_prio_index);
 +              add_stat_prio_sample(ts->clat_prio[ddir], clat_prio_index,
 +                                   nsec);
        }
        if (needs_lock)
                __td_io_u_unlock(td);
diff --combined thread_options.h
index ace658b7144d2b69ca7d059c2b246987f5544162,450e7ddeee25d2ac471e79da637bbf8e168d4502..4162c42faf731db6ec1d434656032d31e8350777
@@@ -50,12 -50,6 +50,12 @@@ struct split 
        unsigned long long val2[ZONESPLIT_MAX];
  };
  
 +struct split_prio {
 +      uint64_t bs;
 +      int32_t prio;
 +      uint32_t perc;
 +};
 +
  struct bssplit {
        uint64_t bs;
        uint32_t perc;
@@@ -172,6 -166,8 +172,8 @@@ struct thread_options 
        unsigned int log_gz;
        unsigned int log_gz_store;
        unsigned int log_unix_epoch;
+       unsigned int log_alternate_epoch;
+       unsigned int log_alternate_epoch_clock_id;
        unsigned int norandommap;
        unsigned int softrandommap;
        unsigned int bs_unaligned;
@@@ -488,6 -484,8 +490,8 @@@ struct thread_options_pack 
        uint32_t log_gz;
        uint32_t log_gz_store;
        uint32_t log_unix_epoch;
+       uint32_t log_alternate_epoch;
+       uint32_t log_alternate_epoch_clock_id;
        uint32_t norandommap;
        uint32_t softrandommap;
        uint32_t bs_unaligned;
@@@ -708,8 -706,4 +712,8 @@@ extern int str_split_parse(struct threa
  extern int split_parse_ddir(struct thread_options *o, struct split *split,
                            char *str, bool absolute, unsigned int max_splits);
  
 +extern int split_parse_prio_ddir(struct thread_options *o,
 +                               struct split_prio **entries, int *nr_entries,
 +                               char *str);
 +
  #endif