Record job start time to fix time pain points
authoraggieNick02 <nick@pcpartpicker.com>
Fri, 1 Sep 2023 15:50:34 +0000 (10:50 -0500)
committeraggieNick02 <nick@pcpartpicker.com>
Fri, 8 Sep 2023 20:21:04 +0000 (15:21 -0500)
Add a new key in the json per-job output, job_start, that records the
job start time obtained via a call to clock_gettime using the clock_id
specified by the new job_start_clock_id option. This allows times of fio
jobs and log entries to be compared/ordered against each other and
against other system events recorded against the same clock_id.

Add a note to the documentation for group_reporting about how there are
several per-job values for which only the first job's value is recorded
in the json output format when group_reporting is enabled.

Fixes #1544

Signed-off-by: Nick Neumann nick@pcpartpicker.com
15 files changed:
HOWTO.rst
backend.c
cconv.c
client.c
fio.1
fio.h
fio_time.h
libfio.c
options.c
rate-submit.c
server.c
stat.c
stat.h
thread_options.h
time.c

index 89032941562d6311fab406664dda5db76e929e9f..9c825cc27dc6141242878f6e05f9e539b734ce03 100644 (file)
--- a/HOWTO.rst
+++ b/HOWTO.rst
@@ -755,6 +755,10 @@ Time related parameters
        calls will be excluded from other uses. Fio will manually clear it from the
        CPU mask of other jobs.
 
        calls will be excluded from other uses. Fio will manually clear it from the
        CPU mask of other jobs.
 
+.. option:: job_start_clock_id=int
+   The clock_id passed to the call to `clock_gettime` used to record job_start
+   in the `json` output format. Default is 0, or CLOCK_REALTIME.
+
 
 Target file/device
 ~~~~~~~~~~~~~~~~~~
 
 Target file/device
 ~~~~~~~~~~~~~~~~~~
@@ -3966,6 +3970,13 @@ Measurements and reporting
        same reporting group, unless if separated by a :option:`stonewall`, or by
        using :option:`new_group`.
 
        same reporting group, unless if separated by a :option:`stonewall`, or by
        using :option:`new_group`.
 
+    NOTE: When :option: `group_reporting` is used along with `json` output,
+    there are certain per-job properties which can be different between jobs
+    but do not have a natural group-level equivalent. Examples include
+    `kb_base`, `unit_base`, `sig_figs`, `thread_number`, `pid`, and
+    `job_start`. For these properties, the values for the first job are
+    recorded for the group.
+
 .. option:: new_group
 
        Start a new reporting group. See: :option:`group_reporting`.  If not given,
 .. option:: new_group
 
        Start a new reporting group. See: :option:`group_reporting`.  If not given,
index 5f0740395bad36cd9d13c30bf6bb9d871805c785..a5895fec4d5d44096d1c67145b65a82180c100b0 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -1858,7 +1858,7 @@ static void *thread_main(void *data)
        if (rate_submit_init(td, sk_out))
                goto err;
 
        if (rate_submit_init(td, sk_out))
                goto err;
 
-       set_epoch_time(td, o->log_unix_epoch | o->log_alternate_epoch, o->log_alternate_epoch_clock_id);
+       set_epoch_time(td, o->log_alternate_epoch_clock_id, o->job_start_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));
        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));
diff --git a/cconv.c b/cconv.c
index ce6acbe6abd0d829966c49f016571ee3057ccd1d..b0127dd870f8c48252a9bfe7b8d44ccbdad0837d 100644 (file)
--- a/cconv.c
+++ b/cconv.c
@@ -219,6 +219,7 @@ int convert_thread_options_to_cpu(struct thread_options *o,
        o->log_unix_epoch = le32_to_cpu(top->log_unix_epoch);
        o->log_alternate_epoch = le32_to_cpu(top->log_alternate_epoch);
        o->log_alternate_epoch_clock_id = le32_to_cpu(top->log_alternate_epoch_clock_id);
        o->log_unix_epoch = le32_to_cpu(top->log_unix_epoch);
        o->log_alternate_epoch = le32_to_cpu(top->log_alternate_epoch);
        o->log_alternate_epoch_clock_id = le32_to_cpu(top->log_alternate_epoch_clock_id);
+       o->job_start_clock_id = le32_to_cpu(top->job_start_clock_id);
        o->norandommap = le32_to_cpu(top->norandommap);
        o->softrandommap = le32_to_cpu(top->softrandommap);
        o->bs_unaligned = le32_to_cpu(top->bs_unaligned);
        o->norandommap = le32_to_cpu(top->norandommap);
        o->softrandommap = le32_to_cpu(top->softrandommap);
        o->bs_unaligned = le32_to_cpu(top->bs_unaligned);
@@ -458,6 +459,7 @@ void convert_thread_options_to_net(struct thread_options_pack *top,
        top->log_unix_epoch = cpu_to_le32(o->log_unix_epoch);
        top->log_alternate_epoch = cpu_to_le32(o->log_alternate_epoch);
        top->log_alternate_epoch_clock_id = cpu_to_le32(o->log_alternate_epoch_clock_id);
        top->log_unix_epoch = cpu_to_le32(o->log_unix_epoch);
        top->log_alternate_epoch = cpu_to_le32(o->log_alternate_epoch);
        top->log_alternate_epoch_clock_id = cpu_to_le32(o->log_alternate_epoch_clock_id);
+       top->job_start_clock_id = cpu_to_le32(o->job_start_clock_id);
        top->norandommap = cpu_to_le32(o->norandommap);
        top->softrandommap = cpu_to_le32(o->softrandommap);
        top->bs_unaligned = cpu_to_le32(o->bs_unaligned);
        top->norandommap = cpu_to_le32(o->norandommap);
        top->softrandommap = cpu_to_le32(o->softrandommap);
        top->bs_unaligned = cpu_to_le32(o->bs_unaligned);
index c257036bf5fa6524659ff9beb524945f8e06920c..345fa9101bdaf1dd210d690ee33b4749086d24f1 100644 (file)
--- a/client.c
+++ b/client.c
@@ -956,6 +956,7 @@ static void convert_ts(struct thread_stat *dst, struct thread_stat *src)
        dst->error              = le32_to_cpu(src->error);
        dst->thread_number      = le32_to_cpu(src->thread_number);
        dst->groupid            = le32_to_cpu(src->groupid);
        dst->error              = le32_to_cpu(src->error);
        dst->thread_number      = le32_to_cpu(src->thread_number);
        dst->groupid            = le32_to_cpu(src->groupid);
+       dst->job_start          = le64_to_cpu(src->job_start);
        dst->pid                = le32_to_cpu(src->pid);
        dst->members            = le32_to_cpu(src->members);
        dst->unified_rw_rep     = le32_to_cpu(src->unified_rw_rep);
        dst->pid                = le32_to_cpu(src->pid);
        dst->members            = le32_to_cpu(src->members);
        dst->unified_rw_rep     = le32_to_cpu(src->unified_rw_rep);
diff --git a/fio.1 b/fio.1
index f0dc49ab004cdeafc3b17d23709065aa4ef6e25a..3e5b840c94bc2c71b4bc97a36e789b4f5f96021d 100644 (file)
--- a/fio.1
+++ b/fio.1
@@ -537,6 +537,10 @@ copy that segment, instead of entering the kernel with a
 \fBgettimeofday\fR\|(2) call. The CPU set aside for doing these time
 calls will be excluded from other uses. Fio will manually clear it from the
 CPU mask of other jobs.
 \fBgettimeofday\fR\|(2) call. The CPU set aside for doing these time
 calls will be excluded from other uses. Fio will manually clear it from the
 CPU mask of other jobs.
+.TP
+.BI job_start_clock_id \fR=\fPint
+The clock_id passed to the call to \fBclock_gettime\fR used to record job_start
+in the \fBjson\fR output format. Default is 0, or CLOCK_REALTIME.
 .SS "Target file/device"
 .TP
 .BI directory \fR=\fPstr
 .SS "Target file/device"
 .TP
 .BI directory \fR=\fPstr
@@ -3664,6 +3668,15 @@ quickly becomes unwieldy. To see the final report per-group instead of
 per-job, use \fBgroup_reporting\fR. Jobs in a file will be part of the
 same reporting group, unless if separated by a \fBstonewall\fR, or by
 using \fBnew_group\fR.
 per-job, use \fBgroup_reporting\fR. Jobs in a file will be part of the
 same reporting group, unless if separated by a \fBstonewall\fR, or by
 using \fBnew_group\fR.
+.RS
+.P
+NOTE: When \fBgroup_reporting\fR is used along with \fBjson\fR output, there
+are certain per-job properties which can be different between jobs but do not
+have a natural group-level equivalent. Examples include \fBkb_base\fR,
+\fBunit_base\fR, \fBsig_figs\fR, \fBthread_number\fR, \fBpid\fR, and
+\fBjob_start\fR. For these properties, the values for the first job are
+recorded for the group.
+.RE
 .TP
 .BI new_group
 Start a new reporting group. See: \fBgroup_reporting\fR. If not given,
 .TP
 .BI new_group
 Start a new reporting group. See: \fBgroup_reporting\fR. If not given,
diff --git a/fio.h b/fio.h
index a54f57c93e3e0f40e076d19b230f4933e6782dab..1322656fd4497501577804124438f38f55765e93 100644 (file)
--- a/fio.h
+++ b/fio.h
@@ -388,7 +388,8 @@ struct thread_data {
 
        struct timespec start;  /* start of this loop */
        struct timespec epoch;  /* time job was started */
 
        struct timespec start;  /* start of this loop */
        struct timespec epoch;  /* time job was started */
-       unsigned long long alternate_epoch; /* Time job was started, clock_gettime's clock_id epoch based. */
+       unsigned long long alternate_epoch; /* Time job was started, as clock_gettime(log_alternate_epoch_clock_id) */
+       unsigned long long job_start; /* Time job was started, as clock_gettime(job_start_clock_id) */
        struct timespec last_issue;
        long time_offset;
        struct timespec ts_cache;
        struct timespec last_issue;
        long time_offset;
        struct timespec ts_cache;
index 62d92120a5ac40b1e22cd8db33d0167eb6a78a66..b20e734c4a8a3053cd4262458575e670710a8e7c 100644 (file)
@@ -30,6 +30,6 @@ extern bool ramp_time_over(struct thread_data *);
 extern bool in_ramp_time(struct thread_data *);
 extern void fio_time_init(void);
 extern void timespec_add_msec(struct timespec *, unsigned int);
 extern bool in_ramp_time(struct thread_data *);
 extern void fio_time_init(void);
 extern void timespec_add_msec(struct timespec *, unsigned int);
-extern void set_epoch_time(struct thread_data *, int, clockid_t);
+extern void set_epoch_time(struct thread_data *, clockid_t, clockid_t);
 
 #endif
 
 #endif
index 237ce34cb3bb9dc42599309683ca807850a1af5d..5c4332776983377240770c1259a0b4e31e4976c7 100644 (file)
--- a/libfio.c
+++ b/libfio.c
@@ -149,7 +149,7 @@ void reset_all_stats(struct thread_data *td)
                td->ts.runtime[i] = 0;
        }
 
                td->ts.runtime[i] = 0;
        }
 
-       set_epoch_time(td, td->o.log_unix_epoch | td->o.log_alternate_epoch, td->o.log_alternate_epoch_clock_id);
+       set_epoch_time(td, td->o.log_alternate_epoch_clock_id, td->o.job_start_clock_id);
        memcpy(&td->start, &td->epoch, sizeof(td->epoch));
        memcpy(&td->iops_sample_time, &td->epoch, sizeof(td->epoch));
        memcpy(&td->bw_sample_time, &td->epoch, sizeof(td->epoch));
        memcpy(&td->start, &td->epoch, sizeof(td->epoch));
        memcpy(&td->iops_sample_time, &td->epoch, sizeof(td->epoch));
        memcpy(&td->bw_sample_time, &td->epoch, sizeof(td->epoch));
index 48aa0d7b1ccaf2d7a8041a2b383bafdf31ca7a5f..281ea609e41d4334f44b83679a2483707cdbf9a9 100644 (file)
--- a/options.c
+++ b/options.c
@@ -4952,6 +4952,16 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
                .category = FIO_OPT_C_GENERAL,
                .group  = FIO_OPT_G_CLOCK,
        },
                .category = FIO_OPT_C_GENERAL,
                .group  = FIO_OPT_G_CLOCK,
        },
+       {
+               .name   = "job_start_clock_id",
+               .lname  = "Job start clock_id",
+               .type   = FIO_OPT_INT,
+               .off1   = offsetof(struct thread_options, job_start_clock_id),
+               .help   = "The clock_id passed to the call to clock_gettime used to record job_start in the json output format. Default is 0, or CLOCK_REALTIME",
+               .verify = gtod_cpu_verify,
+               .category = FIO_OPT_C_GENERAL,
+               .group  = FIO_OPT_G_CLOCK,
+       },
        {
                .name   = "unified_rw_reporting",
                .lname  = "Unified RW Reporting",
        {
                .name   = "unified_rw_reporting",
                .lname  = "Unified RW Reporting",
index 6f6d15bd660fbaa1ed504530edd3f3f6b38822aa..92be3df75ebc181a653c5b745d15c84c9d7b97d2 100644 (file)
@@ -185,7 +185,7 @@ static int io_workqueue_init_worker_fn(struct submit_worker *sw)
        if (td->io_ops->post_init && td->io_ops->post_init(td))
                goto err_io_init;
 
        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 | td->o.log_alternate_epoch, td->o.log_alternate_epoch_clock_id);
+       set_epoch_time(td, td->o.log_alternate_epoch_clock_id, td->o.job_start_clock_id);
        fio_getrusage(&td->ru_start);
        clear_io_state(td, 1);
 
        fio_getrusage(&td->ru_start);
        clear_io_state(td, 1);
 
index bb423702a714224306445bbffeb12d174215c1fe..27332e326e9b8db29c37084fe13d04d6535ad73d 100644 (file)
--- a/server.c
+++ b/server.c
@@ -1706,6 +1706,7 @@ void fio_server_send_ts(struct thread_stat *ts, struct group_run_stats *rs)
        p.ts.error              = cpu_to_le32(ts->error);
        p.ts.thread_number      = cpu_to_le32(ts->thread_number);
        p.ts.groupid            = cpu_to_le32(ts->groupid);
        p.ts.error              = cpu_to_le32(ts->error);
        p.ts.thread_number      = cpu_to_le32(ts->thread_number);
        p.ts.groupid            = cpu_to_le32(ts->groupid);
+       p.ts.job_start          = cpu_to_le64(ts->job_start);
        p.ts.pid                = cpu_to_le32(ts->pid);
        p.ts.members            = cpu_to_le32(ts->members);
        p.ts.unified_rw_rep     = cpu_to_le32(ts->unified_rw_rep);
        p.ts.pid                = cpu_to_le32(ts->pid);
        p.ts.members            = cpu_to_le32(ts->members);
        p.ts.unified_rw_rep     = cpu_to_le32(ts->unified_rw_rep);
diff --git a/stat.c b/stat.c
index 7b791628f4f7ca776c8f22ca5e609d6e0a5904cf..8c8d69f0473ab8f33eaa0828d5e1a66065241145 100644 (file)
--- a/stat.c
+++ b/stat.c
@@ -1712,6 +1712,7 @@ static struct json_object *show_thread_status_json(struct thread_stat *ts,
        root = json_create_object();
        json_object_add_value_string(root, "jobname", ts->name);
        json_object_add_value_int(root, "groupid", ts->groupid);
        root = json_create_object();
        json_object_add_value_string(root, "jobname", ts->name);
        json_object_add_value_int(root, "groupid", ts->groupid);
+       json_object_add_value_int(root, "job_start", ts->job_start);
        json_object_add_value_int(root, "error", ts->error);
 
        /* ETA Info */
        json_object_add_value_int(root, "error", ts->error);
 
        /* ETA Info */
@@ -2526,6 +2527,7 @@ void __show_run_stats(void)
                         */
                        ts->thread_number = td->thread_number;
                        ts->groupid = td->groupid;
                         */
                        ts->thread_number = td->thread_number;
                        ts->groupid = td->groupid;
+                       ts->job_start = td->job_start;
 
                        /*
                         * first pid in group, not very useful...
 
                        /*
                         * first pid in group, not very useful...
@@ -3048,7 +3050,9 @@ static void __add_log_sample(struct io_log *iolog, union io_sample_data data,
                s = get_sample(iolog, cur_log, cur_log->nr_samples);
 
                s->data = data;
                s = get_sample(iolog, cur_log, cur_log->nr_samples);
 
                s->data = data;
-               s->time = t + (iolog->td ? iolog->td->alternate_epoch : 0);
+               s->time = t;
+               if (iolog->td && (iolog->td->o.log_unix_epoch || iolog->td->o.log_alternate_epoch))
+                       s->time += iolog->td->alternate_epoch;
                io_sample_set_ddir(iolog, s, ddir);
                s->bs = bs;
                s->priority = priority;
                io_sample_set_ddir(iolog, s, ddir);
                s->bs = bs;
                s->priority = priority;
diff --git a/stat.h b/stat.h
index 8ceabc48c7e0fd393ee8a307b6f6fdb0acdccab6..bd986d4e71c9b7f1b9b42174df33076143396f65 100644 (file)
--- a/stat.h
+++ b/stat.h
@@ -169,6 +169,7 @@ struct thread_stat {
        uint32_t error;
        uint32_t thread_number;
        uint32_t groupid;
        uint32_t error;
        uint32_t thread_number;
        uint32_t groupid;
+       uint64_t job_start; /* Time job was started, as clock_gettime(job_start_clock_id) */
        uint32_t pid;
        char description[FIO_JOBDESC_SIZE];
        uint32_t members;
        uint32_t pid;
        char description[FIO_JOBDESC_SIZE];
        uint32_t members;
index 38a9993d23a3eef4395d450ed73253a1d1ee5e2a..91c194cf7197df780c07e76cd23b168fdb33f535 100644 (file)
@@ -273,6 +273,7 @@ struct thread_options {
        unsigned int unified_rw_rep;
        unsigned int gtod_reduce;
        unsigned int gtod_cpu;
        unsigned int unified_rw_rep;
        unsigned int gtod_reduce;
        unsigned int gtod_cpu;
+       unsigned int job_start_clock_id;
        enum fio_cs clocksource;
        unsigned int no_stall;
        unsigned int trim_percentage;
        enum fio_cs clocksource;
        unsigned int no_stall;
        unsigned int trim_percentage;
@@ -422,7 +423,6 @@ struct thread_options_pack {
        uint32_t iodepth_batch_complete_min;
        uint32_t iodepth_batch_complete_max;
        uint32_t serialize_overlap;
        uint32_t iodepth_batch_complete_min;
        uint32_t iodepth_batch_complete_max;
        uint32_t serialize_overlap;
-       uint32_t pad;
 
        uint64_t size;
        uint64_t io_size;
 
        uint64_t size;
        uint64_t io_size;
@@ -433,13 +433,11 @@ struct thread_options_pack {
        uint32_t fill_device;
        uint32_t file_append;
        uint32_t unique_filename;
        uint32_t fill_device;
        uint32_t file_append;
        uint32_t unique_filename;
-       uint32_t pad3;
        uint64_t file_size_low;
        uint64_t file_size_high;
        uint64_t start_offset;
        uint64_t start_offset_align;
        uint32_t start_offset_nz;
        uint64_t file_size_low;
        uint64_t file_size_high;
        uint64_t start_offset;
        uint64_t start_offset_align;
        uint32_t start_offset_nz;
-       uint32_t pad4;
 
        uint64_t bs[DDIR_RWDIR_CNT];
        uint64_t ba[DDIR_RWDIR_CNT];
 
        uint64_t bs[DDIR_RWDIR_CNT];
        uint64_t ba[DDIR_RWDIR_CNT];
@@ -511,6 +509,7 @@ struct thread_options_pack {
        struct zone_split zone_split[DDIR_RWDIR_CNT][ZONESPLIT_MAX];
        uint32_t zone_split_nr[DDIR_RWDIR_CNT];
 
        struct zone_split zone_split[DDIR_RWDIR_CNT][ZONESPLIT_MAX];
        uint32_t zone_split_nr[DDIR_RWDIR_CNT];
 
+       uint32_t pad;
        fio_fp64_t zipf_theta;
        fio_fp64_t pareto_h;
        fio_fp64_t gauss_dev;
        fio_fp64_t zipf_theta;
        fio_fp64_t pareto_h;
        fio_fp64_t gauss_dev;
@@ -593,6 +592,7 @@ struct thread_options_pack {
        uint32_t unified_rw_rep;
        uint32_t gtod_reduce;
        uint32_t gtod_cpu;
        uint32_t unified_rw_rep;
        uint32_t gtod_reduce;
        uint32_t gtod_cpu;
+       uint32_t job_start_clock_id;
        uint32_t clocksource;
        uint32_t no_stall;
        uint32_t trim_percentage;
        uint32_t clocksource;
        uint32_t no_stall;
        uint32_t trim_percentage;
@@ -603,6 +603,7 @@ struct thread_options_pack {
        uint32_t lat_percentiles;
        uint32_t slat_percentiles;
        uint32_t percentile_precision;
        uint32_t lat_percentiles;
        uint32_t slat_percentiles;
        uint32_t percentile_precision;
+       uint32_t pad2;
        fio_fp64_t percentile_list[FIO_IO_U_LIST_MAX_LEN];
 
        uint8_t read_iolog_file[FIO_TOP_STR_MAX];
        fio_fp64_t percentile_list[FIO_IO_U_LIST_MAX_LEN];
 
        uint8_t read_iolog_file[FIO_TOP_STR_MAX];
diff --git a/time.c b/time.c
index 5c4d6de0399c5cc681e9ca362c019870baf9db98..7cbab6ff4f9ea1d029e7d9d2c0253278061ffe60 100644 (file)
--- a/time.c
+++ b/time.c
@@ -172,14 +172,22 @@ void set_genesis_time(void)
        fio_gettime(&genesis, NULL);
 }
 
        fio_gettime(&genesis, NULL);
 }
 
-void set_epoch_time(struct thread_data *td, int log_alternate_epoch, clockid_t clock_id)
+void set_epoch_time(struct thread_data *td, clockid_t log_alternate_epoch_clock_id, clockid_t job_start_clock_id)
 {
 {
+       struct timespec ts;
        fio_gettime(&td->epoch, NULL);
        fio_gettime(&td->epoch, NULL);
-       if (log_alternate_epoch) {
-               struct timespec ts;
-               clock_gettime(clock_id, &ts);
-               td->alternate_epoch = (unsigned long long)(ts.tv_sec) * 1000 +
-                                (unsigned long long)(ts.tv_nsec) / 1000000;
+       clock_gettime(log_alternate_epoch_clock_id, &ts);
+       td->alternate_epoch = (unsigned long long)(ts.tv_sec) * 1000 +
+                                                 (unsigned long long)(ts.tv_nsec) / 1000000;
+       if (job_start_clock_id == log_alternate_epoch_clock_id)
+       {
+               td->job_start = td->alternate_epoch;
+       }
+       else
+       {
+               clock_gettime(job_start_clock_id, &ts);
+               td->job_start = (unsigned long long)(ts.tv_sec) * 1000 +
+                                               (unsigned long long)(ts.tv_nsec) / 1000000;
        }
 }
 
        }
 }