1k:4k. If the option allows two sets of ranges, they can be
specified with a ',' or '/' delimiter: 1k-4k/8k-32k. Also see
int.
-float_list A list of floating numbers, separated by a ':' character.
+float_list A list of floating point numbers, separated by a ':' character.
With the above in mind, here follows the complete list of fio job
parameters.
cannot be modified. So random writes are not
possible. To imitate this, libhdfs engine
creates bunch of small files, and engine will
- pick a file out of those files based on the
- offset enerated by fio backend. Each jobs uses
+ pick a file out of those files based on the
+ offset generated by fio backend. Each jobs uses
it's own connection to HDFS.
mtd Read, write and erase an MTD character device
iodepth_batch_complete_min=1
iodepth_batch_complete_max=<iodepth>
- which means that we will retrieve at leat 1 IO and up to the
+ which means that we will retrieve at least 1 IO and up to the
whole submitted queue depth. If none of IO has been completed
yet, we will wait.
random Uniform random distribution
zipf Zipf distribution
pareto Pareto distribution
- gauss Normal (guassian) distribution
+ gauss Normal (gaussian) distribution
zoned Zoned random distribution
When using a zipf or pareto distribution, an input value
and random IO, at the given percentages. It is possible to
set different values for reads, writes, and trim. To do so,
simply use a comma separated list. See blocksize.
-
+
norandommap Normally fio will cover every block of the file when doing
random IO. If this option is given, fio will just get a
new random offset without looking at past io history. This
nice=int Run the job with the given nice value. See man nice(2).
+ On Windows, values less than -15 set the process class to "High";
+ -1 through -15 set "Above Normal"; 1 through 15 "Below Normal";
+ and above 15 "Idle" priority class.
+
prio=int Set the io priority value of this job. Linux limits us to
a positive value between 0 and 7, with 0 being the highest.
See man ionice(1). Refer to an appropriate manpage for
fio must be built on a system with libnuma-dev(el) installed.
numa_mem_policy=str Set this job's memory policy and corresponding NUMA
- nodes. Format of the argements:
+ nodes. Format of the arguments:
<mode>[:<nodelist>]
`mode' is one of the following memory policy:
default, prefer, bind, interleave, local
thus it will increase the total runtime if a special timeout
or runtime is specified.
+steadystate=str:float
+ss=str:float Define the criterion and limit for assessing steady state
+ performance. The first parameter designates the criterion
+ whereas the second parameter sets the threshold. When the
+ criterion falls below the threshold for the specified duration,
+ the job will stop. For example, iops_slope:0.1% will direct fio
+ to terminate the job when the least squares regression slope
+ falls below 0.1% of the mean IOPS. If group_reporting is
+ enabled this will apply to all jobs in the group. Below is the
+ list of available steady state assessment criteria. All
+ assessments are carried out using only data from the rolling
+ collection window. Threshold limits can be expressed as a fixed
+ value or as a percentage of the mean in the collection window.
+ iops Collect IOPS data. Stop the job if all
+ individual IOPS measurements are within the
+ specified limit of the mean IOPS (e.g., iops:2
+ means that all individual IOPS values must be
+ within 2 of the mean, whereas iops:0.2% means
+ that all individual IOPS values must be within
+ 0.2% of the mean IOPS to terminate the job).
+ iops_slope
+ Collect IOPS data and calculate the least
+ squares regression slope. Stop the job if the
+ slope falls below the specified limit.
+ bw Collect bandwidth data. Stop the job if all
+ individual bandwidth measurements are within
+ the specified limit of the mean bandwidth.
+ bw_slope
+ Collect bandwidth data and calculate the least
+ squares regression slope. Stop the job if the
+ slope falls below the specified limit.
+
+steadystate_duration=time
+ss_dur=time A rolling window of this duration will be used to judge whether
+ steady state has been reached. Data will be collected once per
+ second. The default is 0 which disables steady state detection.
+
+steadystate_ramp_time=time
+ss_ramp=time Allow the job to run for the specified duration before
+ beginning data collection for checking the steady state job
+ termination criterion. The default is 0.
+
invalidate=bool Invalidate the buffer/page cache parts for this file prior
to starting io. Defaults to true.
location should point there. So if it's mounted in /huge,
you would use mem=mmaphuge:/huge/somefile.
- iomem_align=int This indiciates the memory alignment of the IO memory buffers.
+ iomem_align=int This indicates the memory alignment of the IO memory buffers.
Note that the given alignment is applied to the first IO unit
buffer, if using iodepth the alignment of the following buffers
are given by the bs used. In other words, if using a bs that is
starting the given IO operation. This will also clear
the 'invalidate' flag, since it is pointless to pre-read
and then drop the cache. This will only work for IO engines
- that are seekable, since they allow you to read the same data
+ that are seek-able, since they allow you to read the same data
multiple times. Thus it will not work on eg network or splice
IO.
crc32c Use a crc32c sum of the data area and store
it in the header of each block.
- crc32c-intel Use hardware assisted crc32c calcuation
+ crc32c-intel Use hardware assisted crc32c calculation
provided on SSE4.2 enabled processors. Falls
back to regular software crc32c, if not
supported by the system.
be a hex number that starts with either "0x" or "0X". Use
with verify=str. Also, verify_pattern supports %o format,
which means that for each block offset will be written and
- then verifyied back, e.g.:
+ then verified back, e.g.:
verify_pattern=%o
replay_no_stall=int When replaying I/O with read_iolog the default behavior
is to attempt to respect the time stamps within the log and
- replay them with the appropriate delay between IOPS. By
+ replay them with the appropriate delay between IOPS. By
setting this variable fio will not respect the timestamps and
attempt to replay them as fast as possible while still
- respecting ordering. The result is the same I/O pattern to a
+ respecting ordering. The result is the same I/O pattern to a
given device, but different timings.
replay_redirect=str While replaying I/O patterns using read_iolog the
mapping. Replay_redirect causes all IOPS to be replayed onto
the single specified device regardless of the device it was
recorded from. i.e. replay_redirect=/dev/sdc would cause all
- IO in the blktrace to be replayed onto /dev/sdc. This means
- multiple devices will be replayed onto a single, if the trace
- contains multiple devices. If you want multiple devices to be
- replayed concurrently to multiple redirected devices you must
- blkparse your trace into separate traces and replay them with
- independent fio invocations. Unfortuantely this also breaks
- the strict time ordering between multiple device accesses.
+ IO in the blktrace or iolog to be replayed onto /dev/sdc.
+ This means multiple devices will be replayed onto a single
+ device, if the trace contains multiple devices. If you want
+ multiple devices to be replayed concurrently to multiple
+ redirected devices you must blkparse your trace into separate
+ traces and replay them with independent fio invocations.
+ Unfortunately this also breaks the strict time ordering
+ between multiple device accesses.
replay_align=int Force alignment of IO offsets and lengths in a trace
to this power of 2 value.
the --inflate-log command line parameter. The files will be
stored with a .fz suffix.
+ log_unix_epoch=bool If set, fio will log Unix timestamps to the log
+ files produced by enabling write_type_log for each log type, instead
+ of the default zero-based timestamps.
+
block_error_percentiles=bool 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
connections rather than initiating an outgoing connection. The
hostname must be omitted if this option is used.
- [net] pingpong Normaly a network writer will just continue writing data, and
+ [net] pingpong Normally a network writer will just continue writing data, and
a network reader will just consume packages. If pingpong=1
is set, a writer will send its normal payload to the reader,
then wait for the reader to send the same payload back. This
[e4defrag] inplace=int
Configure donor file blocks allocation strategy
0(default): Preallocate donor's file on init
- 1 : allocate space immidietly inside defragment event,
+ 1 : allocate space immediately inside defragment event,
and free right after event
[rbd] clustername=str Specifies the name of the Ceph cluster.
[rbd] rbdname=str Specifies the name of the RBD.
- [rbd] pool=str Specifies the naem of the Ceph pool containing RBD.
+ [rbd] pool=str Specifies the name of the Ceph pool containing RBD.
[rbd] clientname=str Specifies the username (without the 'client.' prefix)
used to access the Ceph cluster. If the clustername is
- specified, the clientmae shall be the full type.id
+ specified, the clientname shall be the full type.id
string. If no type. prefix is given, fio will add
'client.' by default.
[mtd] skip_bad=bool Skip operations against known bad blocks.
[libhdfs] hdfsdirectory libhdfs will create chunk in this HDFS directory
- [libhdfs] chunck_size the size of the chunck to use for each file.
+ [libhdfs] chunk_size the size of the chunk to use for each file.
6.0 Interpreting the output
The offset is the offset, in bytes, from the start of the file, for that
particular IO. The logging of the offset can be toggled with 'log_offset'.
- If windowed logging is enabled though 'log_avg_msec', then fio doesn't log
+ If windowed logging is enabled through 'log_avg_msec', then fio doesn't log
individual IOs. Instead of logs the average values over the specified
period of time. Since 'data direction' and 'offset' are per-IO values,
they aren't applicable if windowed logging is enabled. If windowed logging
is enabled and 'log_max_value' is set, then fio logs maximum values in
that window instead of averages.
-
CFLAGS = -std=gnu99 -Wwrite-strings -Wall -Wdeclaration-after-statement $(OPTFLAGS) $(EXTFLAGS) $(BUILD_CFLAGS) -I. -I$(SRCDIR)
LIBS += -lm $(EXTLIBS)
PROGS = fio
- SCRIPTS = $(addprefix $(SRCDIR)/,tools/fio_generate_plots tools/plot/fio2gnuplot tools/genfio tools/fiologparser.py tools/fio_latency2csv.py)
+ SCRIPTS = $(addprefix $(SRCDIR)/,tools/fio_generate_plots tools/plot/fio2gnuplot tools/genfio tools/fiologparser.py tools/fio_latency2csv.py tools/hist/fiologparser_hist.py)
ifndef CONFIG_FIO_NO_OPT
CFLAGS += -O3
server.c client.c iolog.c backend.c libfio.c flow.c cconv.c \
gettime-thread.c helpers.c json.c idletime.c td_error.c \
profiles/tiobench.c profiles/act.c io_u_queue.c filelock.c \
- workqueue.c rate-submit.c optgroup.c helper_thread.c
+ workqueue.c rate-submit.c optgroup.c helper_thread.c \
+ steadystate.c
ifdef CONFIG_LIBHDFS
HDFSFLAGS= -I $(JAVA_HOME)/include -I $(JAVA_HOME)/include/linux -I $(FIO_LIBHDFS_INCLUDE)
- HDFSLIB= -Wl,-rpath $(JAVA_HOME)/jre/lib/`uname -m`/server -L$(JAVA_HOME)/jre/lib/`uname -m`/server -ljvm $(FIO_LIBHDFS_LIB)/libhdfs.a
+ HDFSLIB= -Wl,-rpath $(JAVA_HOME)/jre/lib/$(FIO_HDFS_CPU)/server -L$(JAVA_HOME)/jre/lib/$(FIO_HDFS_CPU)/server -ljvm $(FIO_LIBHDFS_LIB)/libhdfs.a
CFLAGS += $(HDFSFLAGS)
SOURCE += engines/libhdfs.c
endif
@$(CC) -MM $(CFLAGS) $(CPPFLAGS) $(SRCDIR)/$*.c > $*.d
@mv -f $*.d $*.d.tmp
@sed -e 's|.*:|$*.o:|' < $*.d.tmp > $*.d
- @sed -e 's/.*://' -e 's/\\$$//' < $*.d.tmp | fmt -1 | \
+ @sed -e 's/.*://' -e 's/\\$$//' < $*.d.tmp | fmt -w 1 | \
sed -e 's/^ *//' -e 's/$$/:/' >> $*.d
@rm -f $*.d.tmp
@$(CC) -MM $(CFLAGS) $(CPPFLAGS) $(SRCDIR)/$*.c > $*.d
@mv -f $*.d $*.d.tmp
@sed -e 's|.*:|$*.o:|' < $*.d.tmp > $*.d
- @sed -e 's/.*://' -e 's/\\$$//' < $*.d.tmp | fmt -1 | \
+ @sed -e 's/.*://' -e 's/\\$$//' < $*.d.tmp | fmt -w 1 | \
sed -e 's/^ *//' -e 's/$$/:/' >> $*.d
@rm -f $*.d.tmp
@rm -f .depend $(FIO_OBJS) $(GFIO_OBJS) $(OBJS) $(T_OBJS) $(PROGS) $(T_PROGS) $(T_TEST_PROGS) core.* core gfio FIO-VERSION-FILE *.d lib/*.d oslib/*.d crc/*.d engines/*.d profiles/*.d t/*.d config-host.mak config-host.h y.tab.[ch] lex.yy.c exp/*.[do] lexer.h
distclean: clean FORCE
- @rm -f cscope.out fio.pdf fio_generate_plots.pdf fio2gnuplot.pdf
+ @rm -f cscope.out fio.pdf fio_generate_plots.pdf fio2gnuplot.pdf fiologparser_hist.pdf
cscope:
@cscope -b -R
@man -t ./fio.1 | ps2pdf - fio.pdf
@man -t tools/fio_generate_plots.1 | ps2pdf - fio_generate_plots.pdf
@man -t tools/plot/fio2gnuplot.1 | ps2pdf - fio2gnuplot.pdf
+ @man -t tools/hist/fiologparser_hist.py.1 | ps2pdf - fiologparser_hist.pdf
test:
$(INSTALL) -m 644 $(SRCDIR)/fio.1 $(DESTDIR)$(mandir)/man1
$(INSTALL) -m 644 $(SRCDIR)/tools/fio_generate_plots.1 $(DESTDIR)$(mandir)/man1
$(INSTALL) -m 644 $(SRCDIR)/tools/plot/fio2gnuplot.1 $(DESTDIR)$(mandir)/man1
+ $(INSTALL) -m 644 $(SRCDIR)/tools/hist/fiologparser_hist.py.1 $(DESTDIR)$(mandir)/man1
$(INSTALL) -m 755 -d $(DESTDIR)$(sharedir)
$(INSTALL) -m 644 $(SRCDIR)/tools/plot/*gpm $(DESTDIR)$(sharedir)/
int min_evts = 0;
int ret;
- if (td->flags & TD_F_REGROW_LOGS) {
- ret = io_u_quiesce(td);
- regrow_logs(td);
- return ret;
- }
+ if (td->flags & TD_F_REGROW_LOGS)
+ return io_u_quiesce(td);
/*
* if the queue is full, we MUST reap at least 1 event
return number_ios >= (td->o.number_ios * td->loops);
}
- static bool io_issue_bytes_exceeded(struct thread_data *td)
+ static bool io_bytes_exceeded(struct thread_data *td, uint64_t *this_bytes)
{
unsigned long long bytes, limit;
if (td_rw(td))
- bytes = td->io_issue_bytes[DDIR_READ] + td->io_issue_bytes[DDIR_WRITE];
+ bytes = this_bytes[DDIR_READ] + this_bytes[DDIR_WRITE];
else if (td_write(td))
- bytes = td->io_issue_bytes[DDIR_WRITE];
+ bytes = this_bytes[DDIR_WRITE];
else if (td_read(td))
- bytes = td->io_issue_bytes[DDIR_READ];
+ bytes = this_bytes[DDIR_READ];
else
- bytes = td->io_issue_bytes[DDIR_TRIM];
+ bytes = this_bytes[DDIR_TRIM];
if (td->o.io_limit)
limit = td->o.io_limit;
return bytes >= limit || exceeds_number_ios(td);
}
- static bool io_complete_bytes_exceeded(struct thread_data *td)
+ static bool io_issue_bytes_exceeded(struct thread_data *td)
{
- unsigned long long bytes, limit;
-
- if (td_rw(td))
- bytes = td->this_io_bytes[DDIR_READ] + td->this_io_bytes[DDIR_WRITE];
- else if (td_write(td))
- bytes = td->this_io_bytes[DDIR_WRITE];
- else if (td_read(td))
- bytes = td->this_io_bytes[DDIR_READ];
- else
- bytes = td->this_io_bytes[DDIR_TRIM];
-
- if (td->o.io_limit)
- limit = td->o.io_limit;
- else
- limit = td->o.size;
+ return io_bytes_exceeded(td, td->io_issue_bytes);
+ }
- limit *= td->loops;
- return bytes >= limit || exceeds_number_ios(td);
+ static bool io_complete_bytes_exceeded(struct thread_data *td)
+ {
+ return io_bytes_exceeded(td, td->this_io_bytes);
}
/*
struct thread_data *td = fd->td;
struct thread_options *o = &td->o;
struct sk_out *sk_out = fd->sk_out;
+ int deadlock_loop_cnt;
int clear_state;
int ret;
if (rate_submit_init(td, sk_out))
goto err;
- fio_gettime(&td->epoch, NULL);
+ set_epoch_time(td, o->log_unix_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));
+ memcpy(&td->ss.prev_time, &td->epoch, sizeof(td->epoch));
if (o->ratemin[DDIR_READ] || o->ratemin[DDIR_WRITE] ||
o->ratemin[DDIR_TRIM]) {
}
}
+ /*
+ * If we took too long to shut down, the main thread could
+ * already consider us reaped/exited. If that happens, break
+ * out and clean up.
+ */
+ if (td->runstate >= TD_EXITED)
+ break;
+
clear_state = 1;
/*
* the rusage_sem, which would never get upped because
* this thread is waiting for the stat mutex.
*/
- check_update_rusage(td);
+ deadlock_loop_cnt = 0;
+ do {
+ check_update_rusage(td);
+ if (!fio_mutex_down_trylock(stat_mutex))
+ break;
+ usleep(1000);
+ if (deadlock_loop_cnt++ > 5000) {
+ log_err("fio seems to be stuck grabbing stat_mutex, forcibly exiting\n");
+ td->error = EDEADLK;
+ goto err;
+ }
+ } while (1);
- fio_mutex_down(stat_mutex);
if (td_read(td) && td->io_bytes[DDIR_READ])
update_runtime(td, elapsed_us, DDIR_READ);
if (td_write(td) && td->io_bytes[DDIR_WRITE])
/*
* Run over the job map and reap the threads that have exited, if any.
*/
- static void reap_threads(unsigned int *nr_running, unsigned int *t_rate,
- unsigned int *m_rate)
+ static void reap_threads(unsigned int *nr_running, uint64_t *t_rate,
+ uint64_t *m_rate)
{
struct thread_data *td;
unsigned int cputhreads, realthreads, pending;
static void run_threads(struct sk_out *sk_out)
{
struct thread_data *td;
- unsigned int i, todo, nr_running, m_rate, t_rate, nr_started;
+ unsigned int i, todo, nr_running, nr_started;
+ uint64_t m_rate, t_rate;
uint64_t spent;
if (fio_gtod_offload && fio_start_gtod_thread())
}
for_each_td(td, i) {
+ if (td->ss.dur) {
+ if (td->ss.iops_data != NULL) {
+ free(td->ss.iops_data);
+ free(td->ss.bw_data);
+ }
+ }
fio_options_free(td);
if (td->rusage_sem) {
fio_mutex_remove(td->rusage_sem);
}
o->rwmix[i] = le32_to_cpu(top->rwmix[i]);
- o->rate[i] = le32_to_cpu(top->rate[i]);
- o->ratemin[i] = le32_to_cpu(top->ratemin[i]);
+ o->rate[i] = le64_to_cpu(top->rate[i]);
+ o->ratemin[i] = le64_to_cpu(top->ratemin[i]);
o->rate_iops[i] = le32_to_cpu(top->rate_iops[i]);
o->rate_iops_min[i] = le32_to_cpu(top->rate_iops_min[i]);
o->log_offset = le32_to_cpu(top->log_offset);
o->log_gz = le32_to_cpu(top->log_gz);
o->log_gz_store = le32_to_cpu(top->log_gz_store);
+ o->log_unix_epoch = le32_to_cpu(top->log_unix_epoch);
o->norandommap = le32_to_cpu(top->norandommap);
o->softrandommap = le32_to_cpu(top->softrandommap);
o->bs_unaligned = le32_to_cpu(top->bs_unaligned);
o->start_delay_high = le64_to_cpu(top->start_delay_high);
o->timeout = le64_to_cpu(top->timeout);
o->ramp_time = le64_to_cpu(top->ramp_time);
+ o->ss_dur = le64_to_cpu(top->ss_dur);
+ o->ss_ramp_time = le64_to_cpu(top->ss_ramp_time);
+ o->ss_state = le32_to_cpu(top->ss_state);
+ o->ss_limit.u.f = fio_uint64_to_double(le64_to_cpu(top->ss_limit.u.i));
o->zone_range = le64_to_cpu(top->zone_range);
o->zone_size = le64_to_cpu(top->zone_size);
o->zone_skip = le64_to_cpu(top->zone_skip);
o->replay_align = le32_to_cpu(top->replay_align);
o->replay_scale = le32_to_cpu(top->replay_scale);
o->per_job_logs = le32_to_cpu(top->per_job_logs);
+ o->write_bw_log = le32_to_cpu(top->write_bw_log);
+ o->write_lat_log = le32_to_cpu(top->write_lat_log);
+ o->write_iops_log = le32_to_cpu(top->write_iops_log);
+ o->write_hist_log = le32_to_cpu(top->write_hist_log);
o->trim_backlog = le64_to_cpu(top->trim_backlog);
o->rate_process = le32_to_cpu(top->rate_process);
top->log_offset = cpu_to_le32(o->log_offset);
top->log_gz = cpu_to_le32(o->log_gz);
top->log_gz_store = cpu_to_le32(o->log_gz_store);
+ top->log_unix_epoch = cpu_to_le32(o->log_unix_epoch);
top->norandommap = cpu_to_le32(o->norandommap);
top->softrandommap = cpu_to_le32(o->softrandommap);
top->bs_unaligned = cpu_to_le32(o->bs_unaligned);
top->replay_align = cpu_to_le32(o->replay_align);
top->replay_scale = cpu_to_le32(o->replay_scale);
top->per_job_logs = cpu_to_le32(o->per_job_logs);
+ top->write_bw_log = cpu_to_le32(o->write_bw_log);
+ top->write_lat_log = cpu_to_le32(o->write_lat_log);
+ top->write_iops_log = cpu_to_le32(o->write_iops_log);
+ top->write_hist_log = cpu_to_le32(o->write_hist_log);
for (i = 0; i < DDIR_RWDIR_CNT; i++) {
top->bs[i] = cpu_to_le32(o->bs[i]);
}
top->rwmix[i] = cpu_to_le32(o->rwmix[i]);
- top->rate[i] = cpu_to_le32(o->rate[i]);
- top->ratemin[i] = cpu_to_le32(o->ratemin[i]);
+ top->rate[i] = cpu_to_le64(o->rate[i]);
+ top->ratemin[i] = cpu_to_le64(o->ratemin[i]);
top->rate_iops[i] = cpu_to_le32(o->rate_iops[i]);
top->rate_iops_min[i] = cpu_to_le32(o->rate_iops_min[i]);
top->start_delay_high = __cpu_to_le64(o->start_delay_high);
top->timeout = __cpu_to_le64(o->timeout);
top->ramp_time = __cpu_to_le64(o->ramp_time);
+ top->ss_dur = __cpu_to_le64(top->ss_dur);
+ top->ss_ramp_time = __cpu_to_le64(top->ss_ramp_time);
+ top->ss_state = cpu_to_le32(top->ss_state);
+ top->ss_limit.u.i = __cpu_to_le64(fio_double_to_uint64(o->ss_limit.u.f));
top->zone_range = __cpu_to_le64(o->zone_range);
top->zone_size = __cpu_to_le64(o->zone_size);
top->zone_skip = __cpu_to_le64(o->zone_skip);
return fio_net_send_quit(client->fd);
}
- void fio_clients_terminate(void)
+ static void fio_clients_terminate(void)
{
struct flist_head *entry;
struct fio_client *client;
dst->nr_block_infos = le64_to_cpu(src->nr_block_infos);
for (i = 0; i < dst->nr_block_infos; i++)
dst->block_infos[i] = le32_to_cpu(src->block_infos[i]);
+
+ dst->ss_dur = le64_to_cpu(src->ss_dur);
+ dst->ss_state = le32_to_cpu(src->ss_state);
+ dst->ss_head = le32_to_cpu(src->ss_head);
+ dst->ss_limit.u.f = fio_uint64_to_double(le64_to_cpu(src->ss_limit.u.i));
+ dst->ss_slope.u.f = fio_uint64_to_double(le64_to_cpu(src->ss_slope.u.i));
+ dst->ss_deviation.u.f = fio_uint64_to_double(le64_to_cpu(src->ss_deviation.u.i));
+ dst->ss_criterion.u.f = fio_uint64_to_double(le64_to_cpu(src->ss_criterion.u.i));
+
+ if (dst->ss_state & __FIO_SS_DATA) {
+ for (i = 0; i < dst->ss_dur; i++ ) {
+ dst->ss_iops_data[i] = le64_to_cpu(src->ss_iops_data[i]);
+ dst->ss_bw_data[i] = le64_to_cpu(src->ss_bw_data[i]);
+ }
+ }
}
static void convert_gs(struct group_run_stats *dst, struct group_run_stats *src)
je->files_open = le32_to_cpu(je->files_open);
for (i = 0; i < DDIR_RWDIR_CNT; i++) {
- je->m_rate[i] = le32_to_cpu(je->m_rate[i]);
- je->t_rate[i] = le32_to_cpu(je->t_rate[i]);
+ je->m_rate[i] = le64_to_cpu(je->m_rate[i]);
+ je->t_rate[i] = le64_to_cpu(je->t_rate[i]);
je->m_iops[i] = le32_to_cpu(je->m_iops[i]);
je->t_iops[i] = le32_to_cpu(je->t_iops[i]);
- je->rate[i] = le32_to_cpu(je->rate[i]);
+ je->rate[i] = le64_to_cpu(je->rate[i]);
je->iops[i] = le32_to_cpu(je->iops[i]);
}
strcpy((char *) dst->run_str, (char *) je->run_str);
}
- static void remove_reply_cmd(struct fio_client *client, struct fio_net_cmd *cmd)
+ static bool remove_reply_cmd(struct fio_client *client, struct fio_net_cmd *cmd)
{
struct fio_net_cmd_reply *reply = NULL;
struct flist_head *entry;
if (!reply) {
log_err("fio: client: unable to find matching tag (%llx)\n", (unsigned long long) cmd->tag);
- return;
+ return false;
}
flist_del(&reply->list);
cmd->tag = reply->saved_tag;
free(reply);
+ return true;
}
int fio_client_wait_for_reply(struct fio_client *client, uint64_t tag)
fio_client_dec_jobs_eta(eta, client->ops->eta);
}
+ static void client_flush_hist_samples(FILE *f, int hist_coarseness, void *samples,
+ uint64_t sample_size)
+ {
+ struct io_sample *s;
+ int log_offset;
+ uint64_t i, j, nr_samples;
+ struct io_u_plat_entry *entry;
+ unsigned int *io_u_plat;
+
+ int stride = 1 << hist_coarseness;
+
+ if (!sample_size)
+ return;
+
+ s = __get_sample(samples, 0, 0);
+ log_offset = (s->__ddir & LOG_OFFSET_SAMPLE_BIT) != 0;
+
+ nr_samples = sample_size / __log_entry_sz(log_offset);
+
+ for (i = 0; i < nr_samples; i++) {
+
+ s = (struct io_sample *)((char *)__get_sample(samples, log_offset, i) +
+ i * sizeof(struct io_u_plat_entry));
+
+ entry = s->data.plat_entry;
+ io_u_plat = entry->io_u_plat;
+
+ fprintf(f, "%lu, %u, %u, ", (unsigned long) s->time,
+ io_sample_ddir(s), s->bs);
+ for (j = 0; j < FIO_IO_U_PLAT_NR - stride; j += stride) {
+ fprintf(f, "%lu, ", hist_sum(j, stride, io_u_plat, NULL));
+ }
+ fprintf(f, "%lu\n", (unsigned long)
+ hist_sum(FIO_IO_U_PLAT_NR - stride, stride, io_u_plat, NULL));
+
+ }
+ }
+
static int fio_client_handle_iolog(struct fio_client *client,
struct fio_net_cmd *cmd)
{
struct cmd_iolog_pdu *pdu;
bool store_direct;
+ char *log_pathname;
pdu = convert_iolog(cmd, &store_direct);
if (!pdu) {
return 1;
}
+ /* allocate buffer big enough for next sprintf() call */
+ log_pathname = malloc(10 + strlen((char *)pdu->name) +
+ strlen(client->hostname));
+ if (!log_pathname) {
+ log_err("fio: memory allocation of unique pathname failed");
+ return -1;
+ }
+ /* generate a unique pathname for the log file using hostname */
+ sprintf(log_pathname, "%s.%s", pdu->name, client->hostname);
+
if (store_direct) {
ssize_t ret;
size_t sz;
int fd;
- fd = open((const char *) pdu->name,
+ fd = open((const char *) log_pathname,
O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) {
- log_err("fio: open log: %s\n", strerror(errno));
+ log_err("fio: open log %s: %s\n",
+ log_pathname, strerror(errno));
return 1;
}
return 0;
} else {
FILE *f;
-
- f = fopen((const char *) pdu->name, "w");
+ f = fopen((const char *) log_pathname, "w");
if (!f) {
- log_err("fio: fopen log: %s\n", strerror(errno));
+ log_err("fio: fopen log %s : %s\n",
+ log_pathname, strerror(errno));
return 1;
}
- flush_samples(f, pdu->samples,
- pdu->nr_samples * sizeof(struct io_sample));
+ if (pdu->log_type == IO_LOG_TYPE_HIST) {
+ client_flush_hist_samples(f, pdu->log_hist_coarseness, pdu->samples,
+ pdu->nr_samples * sizeof(struct io_sample));
+ } else {
+ flush_samples(f, pdu->samples,
+ pdu->nr_samples * sizeof(struct io_sample));
+ }
fclose(f);
return 0;
}
*/
nr_samples = le64_to_cpu(pdu->nr_samples);
- total = nr_samples * __log_entry_sz(le32_to_cpu(pdu->log_offset));
+ if (pdu->log_type == IO_LOG_TYPE_HIST)
+ total = nr_samples * (__log_entry_sz(le32_to_cpu(pdu->log_offset)) +
+ sizeof(struct io_u_plat_entry));
+ else
+ total = nr_samples * __log_entry_sz(le32_to_cpu(pdu->log_offset));
ret = malloc(total + sizeof(*pdu));
ret->nr_samples = nr_samples;
ret->log_type = le32_to_cpu(ret->log_type);
ret->compressed = le32_to_cpu(ret->compressed);
ret->log_offset = le32_to_cpu(ret->log_offset);
+ ret->log_hist_coarseness = le32_to_cpu(ret->log_hist_coarseness);
if (*store_direct)
return ret;
struct io_sample *s;
s = __get_sample(samples, ret->log_offset, i);
+ if (ret->log_type == IO_LOG_TYPE_HIST)
+ s = (struct io_sample *)((void *)s + sizeof(struct io_u_plat_entry) * i);
+
s->time = le64_to_cpu(s->time);
- s->val = le64_to_cpu(s->val);
+ s->data.val = le64_to_cpu(s->data.val);
s->__ddir = le32_to_cpu(s->__ddir);
s->bs = le32_to_cpu(s->bs);
so->offset = le64_to_cpu(so->offset);
}
+
+ if (ret->log_type == IO_LOG_TYPE_HIST) {
+ s->data.plat_entry = (struct io_u_plat_entry *)(((void *)s) + sizeof(*s));
+ s->data.plat_entry->list.next = NULL;
+ s->data.plat_entry->list.prev = NULL;
+ }
}
return ret;
{
struct client_ops *ops = client->ops;
struct fio_net_cmd *cmd;
+ int size;
dprint(FD_NET, "client: handle %s\n", client->hostname);
case FIO_NET_CMD_TS: {
struct cmd_ts_pdu *p = (struct cmd_ts_pdu *) cmd->payload;
+ dprint(FD_NET, "client: ts->ss_state = %u\n", (unsigned int) le32_to_cpu(p->ts.ss_state));
+ if (le32_to_cpu(p->ts.ss_state) & __FIO_SS_DATA) {
+ dprint(FD_NET, "client: received steadystate ring buffers\n");
+
+ size = le64_to_cpu(p->ts.ss_dur);
+ p->ts.ss_iops_data = (uint64_t *) ((struct cmd_ts_pdu *)cmd->payload + 1);
+ p->ts.ss_bw_data = p->ts.ss_iops_data + size;
+ }
+
convert_ts(&p->ts, &p->ts);
convert_gs(&p->rs, &p->rs);
case FIO_NET_CMD_ETA: {
struct jobs_eta *je = (struct jobs_eta *) cmd->payload;
- remove_reply_cmd(client, cmd);
+ if (!remove_reply_cmd(client, cmd))
+ break;
convert_jobs_eta(je);
handle_eta(client, cmd);
break;
Limit run time to \fIruntime\fR seconds.
.TP
.B \-\-bandwidth\-log
- Generate per-job bandwidth logs.
+ Generate aggregate bandwidth logs.
.TP
.B \-\-minimal
Print statistics in a terse, semicolon-delimited format.
\fBiodepth_batch_complete_max\fR=<iodepth>
.RE
- which means that we will retrieve at leat 1 IO and up to the
+ which means that we will retrieve at least 1 IO and up to the
whole submitted queue depth. If none of IO has been completed
yet, we will wait.
that the \fBramp_time\fR is considered lead in time for a job, thus it will
increase the total runtime if a special timeout or runtime is specified.
.TP
+.BI steadystate \fR=\fPstr:float "\fR,\fP ss" \fR=\fPstr:float
+Define the criterion and limit for assessing steady state performance. The
+first parameter designates the criterion whereas the second parameter sets the
+threshold. When the criterion falls below the threshold for the specified
+duration, the job will stop. For example, iops_slope:0.1% will direct fio
+to terminate the job when the least squares regression slope falls below 0.1%
+of the mean IOPS. If group_reporting is enabled this will apply to all jobs in
+the group. All assessments are carried out using only data from the rolling
+collection window. Threshold limits can be expressed as a fixed value or as a
+percentage of the mean in the collection window. Below are the available steady
+state assessment criteria.
+.RS
+.RS
+.TP
+.B iops
+Collect IOPS data. Stop the job if all individual IOPS measurements are within
+the specified limit of the mean IOPS (e.g., iops:2 means that all individual
+IOPS values must be within 2 of the mean, whereas iops:0.2% means that all
+individual IOPS values must be within 0.2% of the mean IOPS to terminate the
+job).
+.TP
+.B iops_slope
+Collect IOPS data and calculate the least squares regression slope. Stop the
+job if the slope falls below the specified limit.
+.TP
+.B bw
+Collect bandwidth data. Stop the job if all individual bandwidth measurements
+are within the specified limit of the mean bandwidth.
+.TP
+.B bw_slope
+Collect bandwidth data and calculate the least squares regression slope. Stop
+the job if the slope falls below the specified limit.
+.RE
+.RE
+.TP
+.BI steadystate_duration \fR=\fPtime "\fR,\fP ss_dur" \fR=\fPtime
+A rolling window of this duration will be used to judge whether steady state
+has been reached. Data will be collected once per second. The default is 0
+which disables steady state detection.
+.TP
+.BI steadystate_ramp_time \fR=\fPtime "\fR,\fP ss_ramp" \fR=\fPtime
+Allow the job to run for the specified duration before beginning data collection
+for checking the steady state job termination criterion. The default is 0.
+.TP
.BI invalidate \fR=\fPbool
Invalidate buffer-cache for the file prior to starting I/O. Default: true.
.TP
decimal or a hex number). The verify_pattern if larger than a 32-bit quantity
has to be a hex number that starts with either "0x" or "0X". Use with
\fBverify\fP=str. Also, verify_pattern supports %o format, which means that for
- each block offset will be written and then verifyied back, e.g.:
+ each block offset will be written and then verified back, e.g.:
.RS
.RS
\fBverify_pattern\fR=%o
not set, jobs with identical names will share the log filename. Default: true.
.TP
.BI write_bw_log \fR=\fPstr
- If given, write a bandwidth log of the jobs in this job file. Can be used to
- store data of the bandwidth of the jobs in their lifetime. The included
- fio_generate_plots script uses gnuplot to turn these text files into nice
- graphs. See \fBwrite_lat_log\fR for behaviour of given filename. For this
- option, the postfix is _bw.x.log, where x is the index of the job (1..N,
- where N is the number of jobs). If \fBper_job_logs\fR is false, then the
- filename will not include the job index. See the \fBLOG FILE FORMATS\fR
+ If given, write a bandwidth log for this job. Can be used to store data of the
+ bandwidth of the jobs in their lifetime. The included fio_generate_plots script
+ uses gnuplot to turn these text files into nice graphs. See \fBwrite_lat_log\fR
+ for behaviour of given filename. For this option, the postfix is _bw.x.log,
+ where x is the index of the job (1..N, where N is the number of jobs). If
+ \fBper_job_logs\fR is false, then the filename will not include the job index.
+ See the \fBLOG FILE FORMATS\fR
section.
.TP
.BI write_lat_log \fR=\fPstr
decompressed with fio, using the \fB\-\-inflate-log\fR command line parameter.
The files will be stored with a \fB\.fz\fR suffix.
.TP
+ .BI log_unix_epoch \fR=\fPbool
+ If set, fio will log Unix timestamps to the log files produced by enabling
+ \fBwrite_type_log\fR for each log type, instead of the default zero-based
+ timestamps.
+ .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 of error
The \fIoffset\fR is the offset, in bytes, from the start of the file, for that
particular IO. The logging of the offset can be toggled with \fBlog_offset\fR.
- If windowed logging is enabled though \fBlog_avg_msec\fR, then fio doesn't log
+ If windowed logging is enabled through \fBlog_avg_msec\fR, then fio doesn't log
individual IOs. Instead of logs the average values over the specified
period of time. Since \fIdata direction\fR and \fIoffset\fR are per-IO values,
they aren't applicable if windowed logging is enabled. If windowed logging
#include "flow.h"
#include "io_u_queue.h"
#include "workqueue.h"
+#include "steadystate.h"
#ifdef CONFIG_SOLARISAIO
#include <sys/asynch.h>
* Rate state
*/
uint64_t rate_bps[DDIR_RWDIR_CNT];
- unsigned long rate_next_io_time[DDIR_RWDIR_CNT];
+ uint64_t rate_next_io_time[DDIR_RWDIR_CNT];
unsigned long rate_bytes[DDIR_RWDIR_CNT];
unsigned long rate_blocks[DDIR_RWDIR_CNT];
- unsigned long rate_io_issue_bytes[DDIR_RWDIR_CNT];
+ unsigned long long rate_io_issue_bytes[DDIR_RWDIR_CNT];
struct timeval lastrate[DDIR_RWDIR_CNT];
int64_t last_usec;
struct frand_state poisson_state;
struct timeval start; /* start of this loop */
struct timeval epoch; /* time job was started */
+ unsigned long long unix_epoch; /* Time job was started, unix epoch based. */
struct timeval last_issue;
long time_offset;
struct timeval tv_cache;
void *pinned_mem;
+ struct steadystate_data ss;
+
char verror[FIO_VERROR_SIZE];
};
extern int eta_print;
extern int eta_new_line;
extern unsigned long done_secs;
- extern char *job_section;
extern int fio_gtod_offload;
extern int fio_gtod_cpu;
extern enum fio_cs fio_clock_source;
assert(!(io_u->ddir == DDIR_WRITE && !td_write(td)));
}
- #define REAL_MAX_JOBS 2048
+ #define REAL_MAX_JOBS 4096
static inline int should_fsync(struct thread_data *td)
{
extern void add_job_opts(const char **, int);
extern char *num2str(uint64_t, int, int, int, int);
extern int ioengine_load(struct thread_data *);
- extern int parse_dryrun(void);
+ extern bool parse_dryrun(void);
extern int fio_running_or_pending_io_threads(void);
extern int fio_set_fd_nonblocking(int, const char *);
extern void sig_show_status(int sig);
static inline enum fio_ioengine_flags td_ioengine_flags(struct thread_data *td)
{
- return (td->flags >> TD_ENG_FLAG_SHIFT) & TD_ENG_FLAG_MASK;
+ return (enum fio_ioengine_flags)
+ ((td->flags >> TD_ENG_FLAG_SHIFT) & TD_ENG_FLAG_MASK);
}
static inline void td_set_ioengine_flags(struct thread_data *td)
td->flags |= (td->io_ops->flags << TD_ENG_FLAG_SHIFT);
}
- static inline bool td_ioengine_flagged(struct thread_data *td, unsigned int val)
+ static inline bool td_ioengine_flagged(struct thread_data *td,
+ enum fio_ioengine_flags flags)
{
- return ((td->flags >> TD_ENG_FLAG_SHIFT) & val) != 0;
+ return ((td->flags >> TD_ENG_FLAG_SHIFT) & flags) != 0;
}
extern void td_set_runstate(struct thread_data *, int);
* Allow 60 seconds for a job to quit on its own, otherwise reap with
* a vengeance.
*/
- #define FIO_REAP_TIMEOUT 60
+ #define FIO_REAP_TIMEOUT 300
#define TERMINATE_ALL (-1U)
extern void fio_terminate_threads(unsigned int);
} \
} while (0)
- static inline int fio_fill_issue_time(struct thread_data *td)
+ static inline bool fio_fill_issue_time(struct thread_data *td)
{
if (td->o.read_iolog_file ||
!td->o.disable_clat || !td->o.disable_slat || !td->o.disable_bw)
- return 1;
+ return true;
- return 0;
+ return false;
}
- static inline int __should_check_rate(struct thread_data *td,
- enum fio_ddir ddir)
+ static inline bool __should_check_rate(struct thread_data *td,
+ enum fio_ddir ddir)
{
struct thread_options *o = &td->o;
*/
if (o->rate[ddir] || o->ratemin[ddir] || o->rate_iops[ddir] ||
o->rate_iops_min[ddir])
- return 1;
+ return true;
- return 0;
+ return false;
}
- static inline int should_check_rate(struct thread_data *td)
+ static inline bool should_check_rate(struct thread_data *td)
{
- int ret = 0;
-
- if (td->bytes_done[DDIR_READ])
- ret |= __should_check_rate(td, DDIR_READ);
- if (td->bytes_done[DDIR_WRITE])
- ret |= __should_check_rate(td, DDIR_WRITE);
- if (td->bytes_done[DDIR_TRIM])
- ret |= __should_check_rate(td, DDIR_TRIM);
-
- return ret;
+ if (td->bytes_done[DDIR_READ] && __should_check_rate(td, DDIR_READ))
+ return true;
+ if (td->bytes_done[DDIR_WRITE] && __should_check_rate(td, DDIR_WRITE))
+ return true;
+ if (td->bytes_done[DDIR_TRIM] && __should_check_rate(td, DDIR_TRIM))
+ return true;
+
+ return false;
}
static inline unsigned int td_max_bs(struct thread_data *td)
#include "server.h"
#include "idletime.h"
#include "filelock.h"
+#include "steadystate.h"
#include "oslib/getopt.h"
#include "oslib/strcasestr.h"
static int nr_job_sections;
int exitall_on_terminate = 0;
- int exitall_on_terminate_error = 0;
int output_format = FIO_OUTPUT_NORMAL;
int eta_print = FIO_ETA_AUTO;
int eta_new_line = 0;
},
{
.name = (char *) "bandwidth-log",
- .has_arg = required_argument,
+ .has_arg = no_argument,
.val = 'b' | FIO_CLIENT_FLAG,
},
{
static void free_shm(void)
{
if (threads) {
- file_hash_exit();
flow_exit();
fio_debug_jobp = NULL;
free_threads_shm();
free(trigger_remote_cmd);
trigger_file = trigger_cmd = trigger_remote_cmd = NULL;
- options_free(fio_options, &def_thread);
+ options_free(fio_options, &def_thread.o);
fio_filelock_exit();
+ file_hash_exit();
scleanup();
}
*/
static int setup_thread_area(void)
{
- void *hash;
-
if (threads)
return 0;
do {
size_t size = max_jobs * sizeof(struct thread_data);
- size += file_hash_size;
size += sizeof(unsigned int);
#ifndef CONFIG_NO_SHM
#endif
memset(threads, 0, max_jobs * sizeof(struct thread_data));
- hash = (void *) threads + max_jobs * sizeof(struct thread_data);
- fio_debug_jobp = (void *) hash + file_hash_size;
+ fio_debug_jobp = (void *) threads + max_jobs * sizeof(struct thread_data);
*fio_debug_jobp = -1;
- file_hash_init(hash);
flow_init();
return p;
}
- static int exists_and_not_regfile(const char *filename)
- {
- struct stat sb;
-
- if (lstat(filename, &sb) == -1)
- return 0;
-
- #ifndef WIN32 /* NOT Windows */
- if (S_ISREG(sb.st_mode))
- return 0;
- #else
- /* \\.\ is the device namespace in Windows, where every file
- * is a device node */
- if (S_ISREG(sb.st_mode) && strncmp(filename, "\\\\.\\", 4) != 0)
- return 0;
- #endif
-
- return 1;
- }
-
static void init_rand_file_service(struct thread_data *td)
{
unsigned long nranges = td->o.nr_files << FIO_FSERVICE_SHIFT;
return buf;
}
- int parse_dryrun(void)
+ bool parse_dryrun(void)
{
return dump_cmdline || parse_only;
}
if (setup_rate(td))
goto err;
- if (o->lat_log_file) {
+ if (o->write_lat_log) {
struct log_params p = {
.td = td,
.avg_msec = o->log_avg_msec,
.log_gz = o->log_gz,
.log_gz_store = o->log_gz_store,
};
+ const char *pre = o->lat_log_file ? o->lat_log_file : o->name;
const char *suf;
if (p.log_gz_store)
else
suf = "log";
- gen_log_name(logname, sizeof(logname), "lat", o->lat_log_file,
+ gen_log_name(logname, sizeof(logname), "lat", pre,
td->thread_number, suf, o->per_job_logs);
setup_log(&td->lat_log, &p, logname);
- gen_log_name(logname, sizeof(logname), "slat", o->lat_log_file,
+ gen_log_name(logname, sizeof(logname), "slat", pre,
td->thread_number, suf, o->per_job_logs);
setup_log(&td->slat_log, &p, logname);
- gen_log_name(logname, sizeof(logname), "clat", o->lat_log_file,
+ gen_log_name(logname, sizeof(logname), "clat", pre,
td->thread_number, suf, o->per_job_logs);
setup_log(&td->clat_log, &p, logname);
}
- if (o->hist_log_file) {
+ if (o->write_hist_log) {
struct log_params p = {
.td = td,
.avg_msec = o->log_avg_msec,
.log_gz = o->log_gz,
.log_gz_store = o->log_gz_store,
};
+ const char *pre = o->hist_log_file ? o->hist_log_file : o->name;
const char *suf;
+ #ifndef CONFIG_ZLIB
+ if (td->client_type) {
+ log_err("fio: --write_hist_log requires zlib in client/server mode\n");
+ goto err;
+ }
+ #endif
+
if (p.log_gz_store)
suf = "log.fz";
else
suf = "log";
- gen_log_name(logname, sizeof(logname), "clat_hist", o->hist_log_file,
+ gen_log_name(logname, sizeof(logname), "clat_hist", pre,
td->thread_number, suf, o->per_job_logs);
setup_log(&td->clat_hist_log, &p, logname);
}
- if (o->bw_log_file) {
+ if (o->write_bw_log) {
struct log_params p = {
.td = td,
.avg_msec = o->log_avg_msec,
.log_gz = o->log_gz,
.log_gz_store = o->log_gz_store,
};
+ const char *pre = o->bw_log_file ? o->bw_log_file : o->name;
const char *suf;
if (fio_option_is_set(o, bw_avg_time))
else
suf = "log";
- gen_log_name(logname, sizeof(logname), "bw", o->bw_log_file,
+ gen_log_name(logname, sizeof(logname), "bw", pre,
td->thread_number, suf, o->per_job_logs);
setup_log(&td->bw_log, &p, logname);
}
- if (o->iops_log_file) {
+ if (o->write_iops_log) {
struct log_params p = {
.td = td,
.avg_msec = o->log_avg_msec,
.log_gz = o->log_gz,
.log_gz_store = o->log_gz_store,
};
+ const char *pre = o->iops_log_file ? o->iops_log_file : o->name;
const char *suf;
if (fio_option_is_set(o, iops_avg_time))
else
suf = "log";
- gen_log_name(logname, sizeof(logname), "iops", o->iops_log_file,
+ gen_log_name(logname, sizeof(logname), "iops", pre,
td->thread_number, suf, o->per_job_logs);
setup_log(&td->iops_log, &p, logname);
}
log_info("...\n");
}
+ if (td_steadystate_init(td))
+ goto err;
+
/*
* recurse add identical jobs, clear numjobs and stonewall options
* as they don't apply to sub-jobs
td_new->o.stonewall = 0;
td_new->o.new_group = 0;
td_new->subjob_number = numjobs;
+ td_new->o.ss_dur = o->ss_dur * 1000000l;
+ td_new->o.ss_limit = o->ss_limit;
if (file_alloced) {
if (td_new->files) {
/*
* This is our [ini] type file parser.
*/
- int __parse_jobs_ini(struct thread_data *td,
+ static int __parse_jobs_ini(struct thread_data *td,
char *file, int is_buf, int stonewall_flag, int type,
int nested, char *name, char ***popts, int *aopts, int *nopts)
{
printf(" --parse-only\t\tParse options only, don't start any IO\n");
printf(" --output\t\tWrite output to file\n");
printf(" --runtime\t\tRuntime in seconds\n");
- printf(" --bandwidth-log\tGenerate per-job bandwidth logs\n");
+ printf(" --bandwidth-log\tGenerate aggregate bandwidth logs\n");
printf(" --minimal\t\tMinimal (terse) output\n");
printf(" --output-format=x\tOutput format (terse,json,json+,normal)\n");
printf(" --terse-version=x\tSet terse version output format to 'x'\n");
.help = "Log compression logging",
.shift = FD_COMPRESS,
},
+ { .name = "steadystate",
+ .help = "Steady state detection logging",
+ .shift = FD_STEADYSTATE,
+ },
+ { .name = "helperthread",
+ .help = "Helper thread logging",
+ .shift = FD_HELPERTHREAD,
+ },
{ .name = NULL, },
};
switch (c) {
case 'a':
smalloc_pool_size = atoi(optarg);
+ smalloc_pool_size <<= 10;
+ sinit();
break;
case 't':
if (check_str_time(optarg, &def_timeout, 1)) {
#include "os/os.h"
#include "filelock.h"
#include "helper_thread.h"
+ #include "filehash.h"
/*
* Just expose an empty list, if the OS does not support disk util stats
void reset_all_stats(struct thread_data *td)
{
- struct timeval tv;
int i;
reset_io_counters(td, 1);
td->rwmix_issues = 0;
}
- fio_gettime(&tv, NULL);
- memcpy(&td->epoch, &tv, sizeof(tv));
- memcpy(&td->start, &tv, sizeof(tv));
- memcpy(&td->iops_sample_time, &tv, sizeof(tv));
- memcpy(&td->bw_sample_time, &tv, sizeof(tv));
- memcpy(&td->ss.prev_time, &tv, sizeof(tv));
+ set_epoch_time(td, td->o.log_unix_epoch);
+ memcpy(&td->start, &td->epoch, sizeof(struct timeval));
+ memcpy(&td->iops_sample_time, &td->epoch, sizeof(struct timeval));
+ memcpy(&td->bw_sample_time, &td->epoch, sizeof(struct timeval));
++ memcpy(&td->ss.prev_time, &td->epoch, sizeof(struct timeval));
lat_target_reset(td);
clear_rusage_stat(td);
return 1;
}
+ file_hash_init();
+
/*
* We need locale for number printing, if it isn't set then just
* go with the US format.
#define cb_data_to_td(data) container_of(data, struct thread_data, o)
- struct pattern_fmt_desc fmt_desc[] = {
+ static struct pattern_fmt_desc fmt_desc[] = {
{
.fmt = "%o",
.len = FIELD_SIZE(struct io_u *, offset),
return 0;
}
+static int str_steadystate_cb(void *data, const char *str)
+{
+ struct thread_data *td = cb_data_to_td(data);
+ double val;
+ char *nr;
+ char *pct;
+ long long ll;
+
+ if (td->o.ss_state != FIO_SS_IOPS && td->o.ss_state != FIO_SS_IOPS_SLOPE &&
+ td->o.ss_state != FIO_SS_BW && td->o.ss_state != FIO_SS_BW_SLOPE) {
+ /* should be impossible to get here */
+ log_err("fio: unknown steady state criterion\n");
+ return 1;
+ }
+
+ nr = get_opt_postfix(str);
+ if (!nr) {
+ log_err("fio: steadystate threshold must be specified in addition to criterion\n");
+ free(nr);
+ return 1;
+ }
+
+ /* ENHANCEMENT Allow fio to understand size=10.2% and use here */
+ pct = strstr(nr, "%");
+ if (pct) {
+ *pct = '\0';
+ strip_blank_end(nr);
+ if (!str_to_float(nr, &val, 0)) {
+ log_err("fio: could not parse steadystate threshold percentage\n");
+ free(nr);
+ return 1;
+ }
+
+ dprint(FD_PARSE, "set steady state threshold to %f%%\n", val);
+ free(nr);
+ if (parse_dryrun())
+ return 0;
+
+ td->o.ss_state |= __FIO_SS_PCT;
+ td->o.ss_limit.u.f = val;
+ } else if (td->o.ss_state & __FIO_SS_IOPS) {
+ if (!str_to_float(nr, &val, 0)) {
+ log_err("fio: steadystate IOPS threshold postfix parsing failed\n");
+ free(nr);
+ return 1;
+ }
+
+ dprint(FD_PARSE, "set steady state IOPS threshold to %f\n", val);
+ free(nr);
+ if (parse_dryrun())
+ return 0;
+
+ td->o.ss_limit.u.f = val;
+ } else { /* bandwidth criterion */
+ if (str_to_decimal(nr, &ll, 1, td, 0, 0)) {
+ log_err("fio: steadystate BW threshold postfix parsing failed\n");
+ free(nr);
+ return 1;
+ }
+
+ dprint(FD_PARSE, "set steady state BW threshold to %lld\n", ll);
+ free(nr);
+ if (parse_dryrun())
+ return 0;
+
+ td->o.ss_limit.u.f = (double) ll;
+ }
+
+ td->ss.state = td->o.ss_state;
+ return 0;
+}
+
/*
* Return next name in the string. Files are separated with ':'. If the ':'
* is escaped with a '\', then that ':' is part of the filename and does not
return 0;
}
+ static int str_write_bw_log_cb(void *data, const char *str)
+ {
+ struct thread_data *td = cb_data_to_td(data);
+
+ if (str)
+ td->o.bw_log_file = strdup(str);
+
+ td->o.write_bw_log = 1;
+ return 0;
+ }
+
+ static int str_write_lat_log_cb(void *data, const char *str)
+ {
+ struct thread_data *td = cb_data_to_td(data);
+
+ if (str)
+ td->o.lat_log_file = strdup(str);
+
+ td->o.write_lat_log = 1;
+ return 0;
+ }
+
+ static int str_write_iops_log_cb(void *data, const char *str)
+ {
+ struct thread_data *td = cb_data_to_td(data);
+
+ if (str)
+ td->o.iops_log_file = strdup(str);
+
+ td->o.write_iops_log = 1;
+ return 0;
+ }
+
+ static int str_write_hist_log_cb(void *data, const char *str)
+ {
+ struct thread_data *td = cb_data_to_td(data);
+
+ if (str)
+ td->o.hist_log_file = strdup(str);
+
+ td->o.write_hist_log = 1;
+ return 0;
+ }
+
static int rw_verify(struct fio_option *o, void *data)
{
struct thread_data *td = cb_data_to_td(data);
},
{ .ival = "gauss",
.oval = FIO_FSERVICE_GAUSS,
- .help = "Normal (guassian) distribution",
+ .help = "Normal (gaussian) distribution",
},
{ .ival = "roundrobin",
.oval = FIO_FSERVICE_RR,
{
.name = "write_bw_log",
.lname = "Write bandwidth log",
- .type = FIO_OPT_STR_STORE,
+ .type = FIO_OPT_STR,
.off1 = offsetof(struct thread_options, bw_log_file),
+ .cb = str_write_bw_log_cb,
.help = "Write log of bandwidth during run",
.category = FIO_OPT_C_LOG,
.group = FIO_OPT_G_INVALID,
{
.name = "write_lat_log",
.lname = "Write latency log",
- .type = FIO_OPT_STR_STORE,
+ .type = FIO_OPT_STR,
.off1 = offsetof(struct thread_options, lat_log_file),
+ .cb = str_write_lat_log_cb,
.help = "Write log of latency during run",
.category = FIO_OPT_C_LOG,
.group = FIO_OPT_G_INVALID,
{
.name = "write_iops_log",
.lname = "Write IOPS log",
- .type = FIO_OPT_STR_STORE,
+ .type = FIO_OPT_STR,
.off1 = offsetof(struct thread_options, iops_log_file),
+ .cb = str_write_iops_log_cb,
.help = "Write log of IOPS during run",
.category = FIO_OPT_C_LOG,
.group = FIO_OPT_G_INVALID,
{
.name = "write_hist_log",
.lname = "Write latency histogram logs",
- .type = FIO_OPT_STR_STORE,
+ .type = FIO_OPT_STR,
.off1 = offsetof(struct thread_options, hist_log_file),
+ .cb = str_write_hist_log_cb,
.help = "Write log of latency histograms during run",
.category = FIO_OPT_C_LOG,
.group = FIO_OPT_G_INVALID,
.help = "Install libz-dev(el) to get compression support",
},
#endif
+ {
+ .name = "log_unix_epoch",
+ .lname = "Log epoch unix",
+ .type = FIO_OPT_BOOL,
+ .off1 = offsetof(struct thread_options, log_unix_epoch),
+ .help = "Use Unix time in log files",
+ .category = FIO_OPT_C_LOG,
+ .group = FIO_OPT_G_INVALID,
+ },
{
.name = "block_error_percentiles",
.lname = "Block error percentiles",
},
{
.name = "disable_bw_measurement",
+ .alias = "disable_bw",
.lname = "Disable bandwidth stats",
.type = FIO_OPT_BOOL,
.off1 = offsetof(struct thread_options, disable_bw),
.category = FIO_OPT_C_IO,
.group = FIO_OPT_G_MTD,
},
+ {
+ .name = "steadystate",
+ .lname = "Steady state threshold",
+ .alias = "ss",
+ .type = FIO_OPT_STR,
+ .off1 = offsetof(struct thread_options, ss_state),
+ .cb = str_steadystate_cb,
+ .help = "Define the criterion and limit to judge when a job has reached steady state",
+ .def = "iops_slope:0.01%",
+ .posval = {
+ { .ival = "iops",
+ .oval = FIO_SS_IOPS,
+ .help = "maximum mean deviation of IOPS measurements",
+ },
+ { .ival = "iops_slope",
+ .oval = FIO_SS_IOPS_SLOPE,
+ .help = "slope calculated from IOPS measurements",
+ },
+ { .ival = "bw",
+ .oval = FIO_SS_BW,
+ .help = "maximum mean deviation of bandwidth measurements",
+ },
+ {
+ .ival = "bw_slope",
+ .oval = FIO_SS_BW_SLOPE,
+ .help = "slope calculated from bandwidth measurements",
+ },
+ },
+ .category = FIO_OPT_C_GENERAL,
+ .group = FIO_OPT_G_RUNTIME,
+ },
+ {
+ .name = "steadystate_duration",
+ .lname = "Steady state duration",
+ .alias = "ss_dur",
+ .type = FIO_OPT_STR_VAL_TIME,
+ .off1 = offsetof(struct thread_options, ss_dur),
+ .help = "Stop workload upon attaining steady state for specified duration",
+ .def = "0",
+ .is_seconds = 1,
+ .is_time = 1,
+ .category = FIO_OPT_C_GENERAL,
+ .group = FIO_OPT_G_RUNTIME,
+ },
+ {
+ .name = "steadystate_ramp_time",
+ .lname = "Steady state ramp time",
+ .alias = "ss_ramp",
+ .type = FIO_OPT_STR_VAL_TIME,
+ .off1 = offsetof(struct thread_options, ss_ramp_time),
+ .help = "Delay before initiation of data collection for steady state job termination testing",
+ .def = "0",
+ .is_seconds = 1,
+ .is_time = 1,
+ .category = FIO_OPT_C_GENERAL,
+ .group = FIO_OPT_G_RUNTIME,
+ },
{
.name = NULL,
},
void fio_options_free(struct thread_data *td)
{
- options_free(fio_options, td);
+ options_free(fio_options, &td->o);
if (td->eo && td->io_ops && td->io_ops->options) {
options_free(td->io_ops->options, td->eo);
free(td->eo);
struct sk_entry *entry;
entry = fio_net_prep_cmd(opcode, buf, size, tagptr, flags);
- fio_net_queue_entry(entry);
- return 0;
+ if (entry) {
+ fio_net_queue_entry(entry);
+ return 0;
+ }
+
+ return 1;
}
static int fio_net_send_simple_stack_cmd(int sk, uint16_t opcode, uint64_t tag)
{
dprint(FD_NET, "server: sending quit\n");
- return fio_net_queue_cmd(FIO_NET_CMD_QUIT, NULL, 0, 0, SK_F_SIMPLE);
+ return fio_net_queue_cmd(FIO_NET_CMD_QUIT, NULL, 0, NULL, SK_F_SIMPLE);
}
int fio_net_send_quit(int sk)
je->files_open = cpu_to_le32(je->files_open);
for (i = 0; i < DDIR_RWDIR_CNT; i++) {
- je->m_rate[i] = cpu_to_le32(je->m_rate[i]);
- je->t_rate[i] = cpu_to_le32(je->t_rate[i]);
+ je->m_rate[i] = cpu_to_le64(je->m_rate[i]);
+ je->t_rate[i] = cpu_to_le64(je->t_rate[i]);
je->m_iops[i] = cpu_to_le32(je->m_iops[i]);
je->t_iops[i] = cpu_to_le32(je->t_iops[i]);
- je->rate[i] = cpu_to_le32(je->rate[i]);
+ je->rate[i] = cpu_to_le64(je->rate[i]);
je->iops[i] = cpu_to_le32(je->iops[i]);
}
{
struct cmd_ts_pdu p;
int i, j;
+ void *ss_buf;
+ uint64_t *ss_iops, *ss_bw;
dprint(FD_NET, "server sending end stats\n");
p.ts.latency_window = cpu_to_le64(ts->latency_window);
p.ts.latency_percentile.u.i = cpu_to_le64(fio_double_to_uint64(ts->latency_percentile.u.f));
- p.ts.nr_block_infos = le64_to_cpu(ts->nr_block_infos);
+ p.ts.nr_block_infos = cpu_to_le64(ts->nr_block_infos);
for (i = 0; i < p.ts.nr_block_infos; i++)
- p.ts.block_infos[i] = le32_to_cpu(ts->block_infos[i]);
+ p.ts.block_infos[i] = cpu_to_le32(ts->block_infos[i]);
+ p.ts.ss_dur = cpu_to_le64(ts->ss_dur);
+ p.ts.ss_state = cpu_to_le32(ts->ss_state);
+ p.ts.ss_head = cpu_to_le32(ts->ss_head);
+ p.ts.ss_limit.u.i = cpu_to_le64(fio_double_to_uint64(ts->ss_limit.u.f));
+ p.ts.ss_slope.u.i = cpu_to_le64(fio_double_to_uint64(ts->ss_slope.u.f));
+ p.ts.ss_deviation.u.i = cpu_to_le64(fio_double_to_uint64(ts->ss_deviation.u.f));
+ p.ts.ss_criterion.u.i = cpu_to_le64(fio_double_to_uint64(ts->ss_criterion.u.f));
+
convert_gs(&p.rs, rs);
- fio_net_queue_cmd(FIO_NET_CMD_TS, &p, sizeof(p), NULL, SK_F_COPY);
+ dprint(FD_NET, "ts->ss_state = %d\n", ts->ss_state);
+ if (ts->ss_state & __FIO_SS_DATA) {
+ dprint(FD_NET, "server sending steadystate ring buffers\n");
+
+ ss_buf = malloc(sizeof(p) + 2*ts->ss_dur*sizeof(uint64_t));
+
+ memcpy(ss_buf, &p, sizeof(p));
+
+ ss_iops = (uint64_t *) ((struct cmd_ts_pdu *)ss_buf + 1);
+ ss_bw = ss_iops + (int) ts->ss_dur;
+ for (i = 0; i < ts->ss_dur; i++) {
+ ss_iops[i] = cpu_to_le64(ts->ss_iops_data[i]);
+ ss_bw[i] = cpu_to_le64(ts->ss_bw_data[i]);
+ }
+
+ fio_net_queue_cmd(FIO_NET_CMD_TS, ss_buf, sizeof(p) + 2*ts->ss_dur*sizeof(uint64_t), NULL, SK_F_COPY);
+
+ free(ss_buf);
+ }
+ else
+ fio_net_queue_cmd(FIO_NET_CMD_TS, &p, sizeof(p), NULL, SK_F_COPY);
}
void fio_server_send_gs(struct group_run_stats *rs)
}
#ifdef CONFIG_ZLIB
+
+ static inline void __fio_net_prep_tail(z_stream *stream, void *out_pdu,
+ struct sk_entry **last_entry,
+ struct sk_entry *first)
+ {
+ unsigned int this_len = FIO_SERVER_MAX_FRAGMENT_PDU - stream->avail_out;
+
+ *last_entry = fio_net_prep_cmd(FIO_NET_CMD_IOLOG, out_pdu, this_len,
+ NULL, SK_F_VEC | SK_F_INLINE | SK_F_FREE);
+ flist_add_tail(&(*last_entry)->list, &first->next);
+
+ }
+
+ /*
+ * Deflates the next input given, creating as many new packets in the
+ * linked list as necessary.
+ */
+ static int __deflate_pdu_buffer(void *next_in, unsigned int next_sz, void **out_pdu,
+ struct sk_entry **last_entry, z_stream *stream,
+ struct sk_entry *first)
+ {
+ int ret;
+
+ stream->next_in = next_in;
+ stream->avail_in = next_sz;
+ do {
+ if (! stream->avail_out) {
+
+ __fio_net_prep_tail(stream, *out_pdu, last_entry, first);
+
+ *out_pdu = malloc(FIO_SERVER_MAX_FRAGMENT_PDU);
+
+ stream->avail_out = FIO_SERVER_MAX_FRAGMENT_PDU;
+ stream->next_out = *out_pdu;
+ }
+
+ ret = deflate(stream, Z_BLOCK);
+
+ if (ret < 0) {
+ free(*out_pdu);
+ return 1;
+ }
+ } while (stream->avail_in);
+
+ return 0;
+ }
+
+ static int __fio_append_iolog_gz_hist(struct sk_entry *first, struct io_log *log,
+ struct io_logs *cur_log, z_stream *stream)
+ {
+ struct sk_entry *entry;
+ void *out_pdu;
+ int ret, i, j;
+ int sample_sz = log_entry_sz(log);
+
+ out_pdu = malloc(FIO_SERVER_MAX_FRAGMENT_PDU);
+ stream->avail_out = FIO_SERVER_MAX_FRAGMENT_PDU;
+ stream->next_out = out_pdu;
+
+ for (i = 0; i < cur_log->nr_samples; i++) {
+ struct io_sample *s;
+ struct io_u_plat_entry *cur_plat_entry, *prev_plat_entry;
+ unsigned int *cur_plat, *prev_plat;
+
+ s = get_sample(log, cur_log, i);
+ ret = __deflate_pdu_buffer(s, sample_sz, &out_pdu, &entry, stream, first);
+ if (ret)
+ return ret;
+
+ /* Do the subtraction on server side so that client doesn't have to
+ * reconstruct our linked list from packets.
+ */
+ cur_plat_entry = s->data.plat_entry;
+ prev_plat_entry = flist_first_entry(&cur_plat_entry->list, struct io_u_plat_entry, list);
+ cur_plat = cur_plat_entry->io_u_plat;
+ prev_plat = prev_plat_entry->io_u_plat;
+
+ for (j = 0; j < FIO_IO_U_PLAT_NR; j++) {
+ cur_plat[j] -= prev_plat[j];
+ }
+
+ flist_del(&prev_plat_entry->list);
+ free(prev_plat_entry);
+
+ ret = __deflate_pdu_buffer(cur_plat_entry, sizeof(*cur_plat_entry),
+ &out_pdu, &entry, stream, first);
+
+ if (ret)
+ return ret;
+ }
+
+ __fio_net_prep_tail(stream, out_pdu, &entry, first);
+
+ return 0;
+ }
+
static int __fio_append_iolog_gz(struct sk_entry *first, struct io_log *log,
struct io_logs *cur_log, z_stream *stream)
{
void *out_pdu;
int ret;
+ if (log->log_type == IO_LOG_TYPE_HIST)
+ return __fio_append_iolog_gz_hist(first, log, cur_log, stream);
+
stream->next_in = (void *) cur_log->log;
stream->avail_in = cur_log->nr_samples * log_entry_sz(log);
pdu.nr_samples = cpu_to_le64(iolog_nr_samples(log));
pdu.thread_number = cpu_to_le32(td->thread_number);
pdu.log_type = cpu_to_le32(log->log_type);
+ pdu.log_hist_coarseness = cpu_to_le32(log->hist_coarseness);
if (!flist_empty(&log->chunk_list))
pdu.compressed = __cpu_to_le32(STORE_COMPRESSED);
struct io_sample *s = get_sample(log, cur_log, i);
s->time = cpu_to_le64(s->time);
- s->val = cpu_to_le64(s->val);
+ s->data.val = cpu_to_le64(s->data.val);
s->__ddir = cpu_to_le32(s->__ddir);
s->bs = cpu_to_le32(s->bs);
assert(sk_out->sk != -1);
- fio_net_queue_cmd(FIO_NET_CMD_SERVER_START, NULL, 0, 0, SK_F_SIMPLE);
+ fio_net_queue_cmd(FIO_NET_CMD_SERVER_START, NULL, 0, NULL, SK_F_SIMPLE);
}
int fio_server_get_verify_state(const char *name, int threadnumber,
dprint(FD_NET, "server: request verify state\n");
rep = smalloc(sizeof(*rep));
- if (!rep) {
- log_err("fio: smalloc pool too small\n");
+ if (!rep)
return ENOMEM;
- }
__fio_mutex_init(&rep->lock, FIO_MUTEX_LOCKED);
rep->data = NULL;
};
enum {
- FIO_SERVER_VER = 57,
- FIO_SERVER_VER = 59,
++ FIO_SERVER_VER = 60,
FIO_SERVER_MAX_FRAGMENT_PDU = 1024,
FIO_SERVER_MAX_CMD_MB = 2048,
uint32_t log_type;
uint32_t compressed;
uint32_t log_offset;
+ uint32_t log_hist_coarseness;
uint8_t name[FIO_NET_NAME_MAX];
struct io_sample samples[0];
};
free(ovals);
}
- int calc_lat(struct io_stat *is, unsigned long *min, unsigned long *max,
- double *mean, double *dev)
+ bool calc_lat(struct io_stat *is, unsigned long *min, unsigned long *max,
+ double *mean, double *dev)
{
double n = (double) is->samples;
if (n == 0)
- return 0;
+ return false;
*min = is->min_val;
*max = is->max_val;
else
*dev = 0;
- return 1;
+ return true;
}
void show_group_stats(struct group_run_stats *rs, struct buf_output *out)
const char *base = "(usec)";
char *minp, *maxp;
- if (!usec_to_msec(&min, &max, &mean, &dev))
+ if (usec_to_msec(&min, &max, &mean, &dev))
base = "(msec)";
minp = num2str(min, 6, 1, 0, 0);
i == BLOCK_STATE_COUNT - 1 ? '\n' : ',');
}
+static void show_ss_normal(struct thread_stat *ts, struct buf_output *out)
+{
+ char *p1, *p2;
+ unsigned long long bw_mean, iops_mean;
+ const int i2p = is_power_of_2(ts->kb_base);
+
+ if (!ts->ss_dur)
+ return;
+
+ bw_mean = steadystate_bw_mean(ts);
+ iops_mean = steadystate_iops_mean(ts);
+
+ p1 = num2str(bw_mean / ts->kb_base, 6, ts->kb_base, i2p, ts->unit_base);
+ p2 = num2str(iops_mean, 6, 1, 0, 0);
+
+ log_buf(out, " steadystate : attained=%s, bw=%s/s, iops=%s, %s%s=%.3f%s\n",
+ ts->ss_state & __FIO_SS_ATTAINED ? "yes" : "no",
+ p1, p2,
+ ts->ss_state & __FIO_SS_IOPS ? "iops" : "bw",
+ ts->ss_state & __FIO_SS_SLOPE ? " slope": " mean dev",
+ ts->ss_criterion.u.f,
+ ts->ss_state & __FIO_SS_PCT ? "%" : "");
+
+ free(p1);
+ free(p2);
+}
+
static void show_thread_status_normal(struct thread_stat *ts,
struct group_run_stats *rs,
struct buf_output *out)
if (!ddir_rw_sum(ts->io_bytes) && !ddir_rw_sum(ts->total_io_u))
return;
+
+ memset(time_buf, 0, sizeof(time_buf));
time(&time_p);
os_ctime_r((const time_t *) &time_p, time_buf, sizeof(time_buf));
if (ts->nr_block_infos)
show_block_infos(ts->nr_block_infos, ts->block_infos,
ts->percentile_list, out);
+
+ if (ts->ss_dur)
+ show_ss_normal(ts, out);
}
static void show_ddir_status_terse(struct thread_stat *ts,
log_buf(out, "\n");
}
- void json_add_job_opts(struct json_object *root, const char *name,
- struct flist_head *opt_list, bool num_jobs)
+ static void json_add_job_opts(struct json_object *root, const char *name,
+ struct flist_head *opt_list, bool num_jobs)
{
struct json_object *dir_object;
struct flist_head *entry;
}
}
+ if (ts->ss_dur) {
+ struct json_object *data;
+ struct json_array *iops, *bw;
+ int i, j, k;
+ char ss_buf[64];
+
+ snprintf(ss_buf, sizeof(ss_buf), "%s%s:%f%s",
+ ts->ss_state & __FIO_SS_IOPS ? "iops" : "bw",
+ ts->ss_state & __FIO_SS_SLOPE ? "_slope" : "",
+ (float) ts->ss_limit.u.f,
+ ts->ss_state & __FIO_SS_PCT ? "%" : "");
+
+ tmp = json_create_object();
+ json_object_add_value_object(root, "steadystate", tmp);
+ json_object_add_value_string(tmp, "ss", ss_buf);
+ json_object_add_value_int(tmp, "duration", (int)ts->ss_dur);
+ json_object_add_value_int(tmp, "attained", (ts->ss_state & __FIO_SS_ATTAINED) > 0);
+
+ snprintf(ss_buf, sizeof(ss_buf), "%f%s", (float) ts->ss_criterion.u.f,
+ ts->ss_state & __FIO_SS_PCT ? "%" : "");
+ json_object_add_value_string(tmp, "criterion", ss_buf);
+ json_object_add_value_float(tmp, "max_deviation", ts->ss_deviation.u.f);
+ json_object_add_value_float(tmp, "slope", ts->ss_slope.u.f);
+
+ data = json_create_object();
+ json_object_add_value_object(tmp, "data", data);
+ bw = json_create_array();
+ iops = json_create_array();
+
+ /*
+ ** if ss was attained or the buffer is not full,
+ ** ss->head points to the first element in the list.
+ ** otherwise it actually points to the second element
+ ** in the list
+ */
+ if ((ts->ss_state & __FIO_SS_ATTAINED) || !(ts->ss_state & __FIO_SS_BUFFER_FULL))
+ j = ts->ss_head;
+ else
+ j = ts->ss_head == 0 ? ts->ss_dur - 1 : ts->ss_head - 1;
+ for (i = 0; i < ts->ss_dur; i++) {
+ k = (j + i) % ts->ss_dur;
+ json_array_add_value_int(bw, ts->ss_bw_data[k]);
+ json_array_add_value_int(iops, ts->ss_iops_data[k]);
+ }
+ json_object_add_value_int(data, "bw_mean", steadystate_bw_mean(ts));
+ json_object_add_value_int(data, "iops_mean", steadystate_iops_mean(ts));
+ json_object_add_value_array(data, "iops", iops);
+ json_object_add_value_array(data, "bw", bw);
+ }
+
return root;
}
ts->block_infos[k] = td->ts.block_infos[k];
sum_thread_stats(ts, &td->ts, idx == 1);
+
+ if (td->o.ss_dur) {
+ ts->ss_state = td->ss.state;
+ ts->ss_dur = td->ss.dur;
+ ts->ss_head = td->ss.head;
+ ts->ss_bw_data = td->ss.bw_data;
+ ts->ss_iops_data = td->ss.iops_data;
+ ts->ss_limit.u.f = td->ss.limit;
+ ts->ss_slope.u.f = td->ss.slope;
+ ts->ss_deviation.u.f = td->ss.deviation;
+ ts->ss_criterion.u.f = td->ss.criterion;
+ }
+ else
+ ts->ss_dur = ts->ss_state = 0;
}
for (i = 0; i < nr_ts; i++) {
os_ctime_r((const time_t *) &now.tv_sec, time_buf,
sizeof(time_buf));
- time_buf[strlen(time_buf) - 1] = '\0';
+ if (time_buf[strlen(time_buf) - 1] == '\n')
+ time_buf[strlen(time_buf) - 1] = '\0';
root = json_create_object();
json_object_add_value_string(root, "fio version", fio_version_string);
return iolog->pending;
}
- static void __add_log_sample(struct io_log *iolog, unsigned long val,
+ static void __add_log_sample(struct io_log *iolog, union io_sample_data data,
enum fio_ddir ddir, unsigned int bs,
unsigned long t, uint64_t offset)
{
s = get_sample(iolog, cur_log, cur_log->nr_samples);
- s->val = val;
- s->time = t;
+ s->data = data;
+ s->time = t + (iolog->td ? iolog->td->unix_epoch : 0);
io_sample_set_ddir(iolog, s, ddir);
s->bs = bs;
* had actual samples done.
*/
if (iolog->avg_window[ddir].samples) {
- unsigned long val;
+ union io_sample_data data;
if (log_max)
- val = iolog->avg_window[ddir].max_val;
+ data.val = iolog->avg_window[ddir].max_val;
else
- val = iolog->avg_window[ddir].mean.u.f + 0.50;
+ data.val = iolog->avg_window[ddir].mean.u.f + 0.50;
- __add_log_sample(iolog, val, ddir, 0, elapsed, 0);
+ __add_log_sample(iolog, data, ddir, 0, elapsed, 0);
}
reset_io_stat(&iolog->avg_window[ddir]);
}
static long add_log_sample(struct thread_data *td, struct io_log *iolog,
- unsigned long val, enum fio_ddir ddir,
+ union io_sample_data data, enum fio_ddir ddir,
unsigned int bs, uint64_t offset)
{
unsigned long elapsed, this_window;
* If no time averaging, just add the log sample.
*/
if (!iolog->avg_msec) {
- __add_log_sample(iolog, val, ddir, bs, elapsed, offset);
+ __add_log_sample(iolog, data, ddir, bs, elapsed, offset);
return 0;
}
* Add the sample. If the time period has passed, then
* add that entry to the log and clear.
*/
- add_stat_sample(&iolog->avg_window[ddir], val);
+ add_stat_sample(&iolog->avg_window[ddir], data.val);
/*
* If period hasn't passed, adding the above sample is all we
_add_stat_to_log(td->iops_log, elapsed, td->o.log_max != 0);
}
- void add_agg_sample(unsigned long val, enum fio_ddir ddir, unsigned int bs)
+ void add_agg_sample(union io_sample_data data, enum fio_ddir ddir, unsigned int bs)
{
struct io_log *iolog;
return;
iolog = agg_io_log[ddir];
- __add_log_sample(iolog, val, ddir, bs, mtime_since_genesis(), 0);
+ __add_log_sample(iolog, data, ddir, bs, mtime_since_genesis(), 0);
}
static void add_clat_percentile_sample(struct thread_stat *ts,
add_stat_sample(&ts->clat_stat[ddir], usec);
if (td->clat_log)
- add_log_sample(td, td->clat_log, usec, ddir, bs, offset);
+ add_log_sample(td, td->clat_log, sample_val(usec), ddir, bs,
+ offset);
if (ts->clat_percentiles)
add_clat_percentile_sample(ts, usec, ddir);
if (this_window >= iolog->hist_msec) {
unsigned int *io_u_plat;
- unsigned int *dst;
+ struct io_u_plat_entry *dst;
/*
* Make a byte-for-byte copy of the latency histogram
* log file.
*/
io_u_plat = (unsigned int *) td->ts.io_u_plat[ddir];
- dst = malloc(FIO_IO_U_PLAT_NR * sizeof(unsigned int));
- memcpy(dst, io_u_plat,
+ dst = malloc(sizeof(struct io_u_plat_entry));
+ memcpy(&(dst->io_u_plat), io_u_plat,
FIO_IO_U_PLAT_NR * sizeof(unsigned int));
- __add_log_sample(iolog, (unsigned long )dst, ddir, bs,
+ flist_add(&dst->list, &hw->list);
+ __add_log_sample(iolog, sample_plat(dst), ddir, bs,
elapsed, offset);
/*
add_stat_sample(&ts->slat_stat[ddir], usec);
if (td->slat_log)
- add_log_sample(td, td->slat_log, usec, ddir, bs, offset);
+ add_log_sample(td, td->slat_log, sample_val(usec), ddir, bs, offset);
td_io_u_unlock(td);
}
add_stat_sample(&ts->lat_stat[ddir], usec);
if (td->lat_log)
- add_log_sample(td, td->lat_log, usec, ddir, bs, offset);
+ add_log_sample(td, td->lat_log, sample_val(usec), ddir, bs,
+ offset);
td_io_u_unlock(td);
}
add_stat_sample(&ts->bw_stat[io_u->ddir], rate);
if (td->bw_log)
- add_log_sample(td, td->bw_log, rate, io_u->ddir, bytes, io_u->offset);
+ add_log_sample(td, td->bw_log, sample_val(rate), io_u->ddir,
+ bytes, io_u->offset);
td->stat_io_bytes[io_u->ddir] = td->this_io_bytes[io_u->ddir];
td_io_u_unlock(td);
if (td->o.min_bs[ddir] == td->o.max_bs[ddir])
bs = td->o.min_bs[ddir];
- next = add_log_sample(td, td->bw_log, rate, ddir, bs, 0);
+ next = add_log_sample(td, td->bw_log, sample_val(rate),
+ ddir, bs, 0);
next_log = min(next_log, next);
}
add_stat_sample(&ts->iops_stat[io_u->ddir], 1);
if (td->iops_log)
- add_log_sample(td, td->iops_log, 1, io_u->ddir, bytes, io_u->offset);
+ add_log_sample(td, td->iops_log, sample_val(1), io_u->ddir,
+ bytes, io_u->offset);
td->stat_io_blocks[io_u->ddir] = td->this_io_blocks[io_u->ddir];
td_io_u_unlock(td);
if (td->o.min_bs[ddir] == td->o.max_bs[ddir])
bs = td->o.min_bs[ddir];
- next = add_log_sample(td, td->iops_log, iops, ddir, bs, 0);
+ next = add_log_sample(td, td->iops_log,
+ sample_val(iops), ddir, bs, 0);
next_log = min(next_log, next);
}
next = min(td->o.iops_avg_time, td->o.bw_avg_time);
continue;
}
- if (!per_unit_log(td->bw_log)) {
+ if (td->bw_log && !per_unit_log(td->bw_log)) {
tmp = add_bw_samples(td, &now);
if (tmp < next)
next = tmp;
}
- if (!per_unit_log(td->iops_log)) {
+ if (td->iops_log && !per_unit_log(td->iops_log)) {
tmp = add_iops_samples(td, &now);
if (tmp < next)
next = tmp;
#define BLOCK_INFO_STATE(block_info) \
((block_info) >> BLOCK_INFO_STATE_SHIFT)
#define BLOCK_INFO(state, trim_cycles) \
- ((trim_cycles) | ((state) << BLOCK_INFO_STATE_SHIFT))
+ ((trim_cycles) | ((unsigned int) (state) << BLOCK_INFO_STATE_SHIFT))
#define BLOCK_INFO_SET_STATE(block_info, state) \
BLOCK_INFO(state, BLOCK_INFO_TRIMS(block_info))
enum block_info_state {
*/
union {
uint16_t continue_on_error;
- uint64_t pad2;
+ uint32_t pad2;
};
- uint64_t total_err_count;
uint32_t first_error;
+ uint64_t total_err_count;
uint64_t nr_block_infos;
uint32_t block_infos[MAX_NR_BLOCK_INFOS];
uint32_t unit_base;
uint32_t latency_depth;
+ uint32_t pad3;
uint64_t latency_target;
fio_fp64_t latency_percentile;
uint64_t latency_window;
+
+ uint64_t ss_dur;
+ uint32_t ss_state;
+ uint32_t ss_head;
+
+ fio_fp64_t ss_limit;
+ fio_fp64_t ss_slope;
+ fio_fp64_t ss_deviation;
+ fio_fp64_t ss_criterion;
+
+ union {
+ uint64_t *ss_iops_data;
+ uint64_t pad4;
+ };
+
+ union {
+ uint64_t *ss_bw_data;
+ uint64_t pad5;
+ };
} __attribute__((packed));
struct jobs_eta {
uint32_t files_open;
- uint32_t m_rate[DDIR_RWDIR_CNT], t_rate[DDIR_RWDIR_CNT];
+ uint64_t m_rate[DDIR_RWDIR_CNT], t_rate[DDIR_RWDIR_CNT];
uint32_t m_iops[DDIR_RWDIR_CNT], t_iops[DDIR_RWDIR_CNT];
- uint32_t rate[DDIR_RWDIR_CNT];
+ uint64_t rate[DDIR_RWDIR_CNT];
uint32_t iops[DDIR_RWDIR_CNT];
uint64_t elapsed_sec;
uint64_t eta_sec;
uint8_t run_str[];
} __attribute__((packed));
+ struct io_u_plat_entry {
+ struct flist_head list;
+ unsigned int io_u_plat[FIO_IO_U_PLAT_NR];
+ };
+
extern struct fio_mutex *stat_mutex;
extern struct jobs_eta *get_jobs_eta(bool force, size_t *size);
extern struct json_object * show_thread_status(struct thread_stat *ts, struct group_run_stats *rs, struct flist_head *, struct buf_output *);
extern void show_group_stats(struct group_run_stats *rs, struct buf_output *);
- extern int calc_thread_status(struct jobs_eta *je, int force);
+ extern bool calc_thread_status(struct jobs_eta *je, int force);
extern void display_thread_status(struct jobs_eta *je);
extern void show_run_stats(void);
extern void __show_run_stats(void);
extern void init_thread_stat(struct thread_stat *ts);
extern void init_group_run_stat(struct group_run_stats *gs);
extern void eta_to_str(char *str, unsigned long eta_sec);
- extern int calc_lat(struct io_stat *is, unsigned long *min, unsigned long *max, double *mean, double *dev);
+ extern bool calc_lat(struct io_stat *is, unsigned long *min, unsigned long *max, double *mean, double *dev);
extern unsigned int calc_clat_percentiles(unsigned int *io_u_plat, unsigned long nr, fio_fp64_t *plist, unsigned int **output, unsigned int *maxv, unsigned int *minv);
extern void stat_calc_lat_m(struct thread_stat *ts, double *io_u_lat);
extern void stat_calc_lat_u(struct thread_stat *ts, double *io_u_lat);
unsigned int, uint64_t);
extern void add_slat_sample(struct thread_data *, enum fio_ddir, unsigned long,
unsigned int, uint64_t);
- extern void add_agg_sample(unsigned long, enum fio_ddir, unsigned int);
+ extern void add_agg_sample(union io_sample_data, enum fio_ddir, unsigned int);
extern void add_iops_sample(struct thread_data *, struct io_u *,
unsigned int);
extern void add_bw_sample(struct thread_data *, struct io_u *,
extern struct io_log *agg_io_log[DDIR_RWDIR_CNT];
extern int write_bw_log;
- static inline int usec_to_msec(unsigned long *min, unsigned long *max,
- double *mean, double *dev)
+ static inline bool usec_to_msec(unsigned long *min, unsigned long *max,
+ double *mean, double *dev)
{
if (*min > 1000 && *max > 1000 && *mean > 1000.0 && *dev > 1000.0) {
*min /= 1000;
*max /= 1000;
*mean /= 1000.0;
*dev /= 1000.0;
- return 0;
+ return true;
}
- return 1;
+ return false;
}
/*
* Worst level condensing would be 1:5, so allow enough room for that
unsigned int log_offset;
unsigned int log_gz;
unsigned int log_gz_store;
+ unsigned int log_unix_epoch;
unsigned int norandommap;
unsigned int softrandommap;
unsigned int bs_unaligned;
unsigned long long start_delay_high;
unsigned long long timeout;
unsigned long long ramp_time;
+ unsigned int ss_state;
+ fio_fp64_t ss_limit;
+ unsigned long long ss_dur;
+ unsigned long long ss_ramp_time;
unsigned int overwrite;
unsigned int bw_avg_time;
unsigned int iops_avg_time;
char *read_iolog_file;
char *write_iolog_file;
+
+ unsigned int write_bw_log;
+ unsigned int write_lat_log;
+ unsigned int write_iops_log;
+ unsigned int write_hist_log;
+
char *bw_log_file;
char *lat_log_file;
char *iops_log_file;
char *exec_prerun;
char *exec_postrun;
- unsigned int rate[DDIR_RWDIR_CNT];
- unsigned int ratemin[DDIR_RWDIR_CNT];
+ uint64_t rate[DDIR_RWDIR_CNT];
+ uint64_t ratemin[DDIR_RWDIR_CNT];
unsigned int ratecycle;
unsigned int io_submit_mode;
unsigned int rate_iops[DDIR_RWDIR_CNT];
uint32_t log_offset;
uint32_t log_gz;
uint32_t log_gz_store;
+ uint32_t log_unix_epoch;
uint32_t norandommap;
uint32_t softrandommap;
uint32_t bs_unaligned;
uint32_t fsync_on_close;
uint32_t bs_is_seq_rand;
+ uint32_t pad1;
uint32_t random_distribution;
uint32_t exitall_error;
uint64_t start_delay_high;
uint64_t timeout;
uint64_t ramp_time;
+ uint64_t ss_dur;
+ uint64_t ss_ramp_time;
+ uint32_t ss_state;
+ fio_fp64_t ss_limit;
uint32_t overwrite;
uint32_t bw_avg_time;
uint32_t iops_avg_time;
uint64_t trim_backlog;
uint32_t clat_percentiles;
uint32_t percentile_precision;
+ uint32_t padding; /* REMOVE ME when possible to maintain alignment */
fio_fp64_t percentile_list[FIO_IO_U_LIST_MAX_LEN];
uint8_t read_iolog_file[FIO_TOP_STR_MAX];
uint8_t write_iolog_file[FIO_TOP_STR_MAX];
+
+ uint32_t write_bw_log;
+ uint32_t write_lat_log;
+ uint32_t write_iops_log;
+ uint32_t write_hist_log;
+
uint8_t bw_log_file[FIO_TOP_STR_MAX];
uint8_t lat_log_file[FIO_TOP_STR_MAX];
uint8_t iops_log_file[FIO_TOP_STR_MAX];
uint8_t exec_prerun[FIO_TOP_STR_MAX];
uint8_t exec_postrun[FIO_TOP_STR_MAX];
- uint32_t rate[DDIR_RWDIR_CNT];
- uint32_t ratemin[DDIR_RWDIR_CNT];
+ uint64_t rate[DDIR_RWDIR_CNT];
+ uint64_t ratemin[DDIR_RWDIR_CNT];
uint32_t ratecycle;
uint32_t io_submit_mode;
uint32_t rate_iops[DDIR_RWDIR_CNT];