tools: Adding fiograph
authorErwan Velu <erwanaliasr1@gmail.com>
Wed, 16 Jun 2021 13:16:25 +0000 (15:16 +0200)
committerErwan Velu <erwanaliasr1@gmail.com>
Sun, 27 Jun 2021 21:15:24 +0000 (23:15 +0200)
This tool generates graphviz graphs of any fio job file.

Reading a fio file can be sometimes tricky when :
  - you have lots of jobs
  - jobs override some options
  - jobs have dependencies

Understanding which jobs are running at the same time, which one waits
for which is sometimes difficult and could easily lead to mistakes.
It's pretty common to find jobs where people think jobs are sequential
while some run in parallel.

For ease the understanding of the scheduling and what's the
configuration status (local + global variables), this tool will use some
graphical helpers to enlighten things.

The configuration file can be used to adjust the rendering but by default :
  - jobs are defined in a blue rounded box
  - jobs running at the same time are inside the same gray background
  - time & size variables are shown as a self arrow
  - dependencies between jobs are shown by up->down black arrows
  - specific engines options are written in blue (top of the job box)
  - standard options are printed in green (bottom of the job box)
  - pre/post actions are printed in red (head/tail of the job box)
  - numjobs is replaced by a 'x' multiplicator on the job name

Several output formats can be defined via --format : list can be found
here https://graphviz.org/docs/outputs/

if --view option is used, the rendered file will be immediately shown.

if --keep is used, the grapvhiz file will be kept.

This commit also adds the rendering of the examples jobs.
If a newcomer gets into the repository, they can immediately better
understand what the jobs do.

Signed-off-by: Erwan Velu <erwanaliasr1@gmail.com>
76 files changed:
examples/1mbs_clients.png [new file with mode: 0644]
examples/aio-read.png [new file with mode: 0644]
examples/backwards-read.png [new file with mode: 0644]
examples/basic-verify.png [new file with mode: 0644]
examples/butterfly.png [new file with mode: 0644]
examples/cpp_null.png [new file with mode: 0644]
examples/cpuio.png [new file with mode: 0644]
examples/cross-stripe-verify.png [new file with mode: 0644]
examples/dev-dax.png [new file with mode: 0644]
examples/dfs.png [new file with mode: 0644]
examples/disk-zone-profile.png [new file with mode: 0644]
examples/e4defrag.png [new file with mode: 0644]
examples/e4defrag2.png [new file with mode: 0644]
examples/enospc-pressure.png [new file with mode: 0644]
examples/exitwhat.png [new file with mode: 0644]
examples/falloc.png [new file with mode: 0644]
examples/filecreate-ioengine.png [new file with mode: 0644]
examples/filedelete-ioengine.png [new file with mode: 0644]
examples/filestat-ioengine.png [new file with mode: 0644]
examples/fio-rand-RW.png [new file with mode: 0644]
examples/fio-rand-read.png [new file with mode: 0644]
examples/fio-rand-write.png [new file with mode: 0644]
examples/fio-seq-RW.png [new file with mode: 0644]
examples/fio-seq-read.png [new file with mode: 0644]
examples/fio-seq-write.png [new file with mode: 0644]
examples/fixed-rate-submission.png [new file with mode: 0644]
examples/flow.png [new file with mode: 0644]
examples/fsx.png [new file with mode: 0644]
examples/ftruncate.png [new file with mode: 0644]
examples/gfapi.png [new file with mode: 0644]
examples/gpudirect-rdmaio-client.png [new file with mode: 0644]
examples/gpudirect-rdmaio-server.png [new file with mode: 0644]
examples/http-s3.png [new file with mode: 0644]
examples/http-swift.png [new file with mode: 0644]
examples/http-webdav.png [new file with mode: 0644]
examples/ime.png [new file with mode: 0644]
examples/iometer-file-access-server.png [new file with mode: 0644]
examples/jesd219.png [new file with mode: 0644]
examples/latency-profile.png [new file with mode: 0644]
examples/libcufile-cufile.png [new file with mode: 0644]
examples/libcufile-posix.png [new file with mode: 0644]
examples/libhdfs.png [new file with mode: 0644]
examples/libiscsi.png [new file with mode: 0644]
examples/libpmem.png [new file with mode: 0644]
examples/librpma_apm-client.png [new file with mode: 0644]
examples/librpma_apm-server.png [new file with mode: 0644]
examples/librpma_gpspm-client.png [new file with mode: 0644]
examples/librpma_gpspm-server.png [new file with mode: 0644]
examples/libzbc-rand-write.png [new file with mode: 0644]
examples/libzbc-seq-read.png [new file with mode: 0644]
examples/mtd.png [new file with mode: 0644]
examples/nbd.png [new file with mode: 0644]
examples/netio.png [new file with mode: 0644]
examples/netio_multicast.png [new file with mode: 0644]
examples/nfs.png [new file with mode: 0644]
examples/null.png [new file with mode: 0644]
examples/numa.png [new file with mode: 0644]
examples/pmemblk.png [new file with mode: 0644]
examples/poisson-rate-submission.png [new file with mode: 0644]
examples/rados.png [new file with mode: 0644]
examples/rand-zones.png [new file with mode: 0644]
examples/rbd.png [new file with mode: 0644]
examples/rdmaio-client.png [new file with mode: 0644]
examples/rdmaio-server.png [new file with mode: 0644]
examples/ssd-steadystate.png [new file with mode: 0644]
examples/ssd-test.png [new file with mode: 0644]
examples/steadystate.png [new file with mode: 0644]
examples/surface-scan.png [new file with mode: 0644]
examples/test.png [new file with mode: 0644]
examples/tiobench-example.png [new file with mode: 0644]
examples/waitfor.png [new file with mode: 0644]
examples/zbd-rand-write.png [new file with mode: 0644]
examples/zbd-seq-read.png [new file with mode: 0644]
examples/zipf.png [new file with mode: 0644]
tools/fiograph/fiograph.conf [new file with mode: 0644]
tools/fiograph/fiograph.py [new file with mode: 0755]

diff --git a/examples/1mbs_clients.png b/examples/1mbs_clients.png
new file mode 100644 (file)
index 0000000..3f972dc
Binary files /dev/null and b/examples/1mbs_clients.png differ
diff --git a/examples/aio-read.png b/examples/aio-read.png
new file mode 100644 (file)
index 0000000..e0c020a
Binary files /dev/null and b/examples/aio-read.png differ
diff --git a/examples/backwards-read.png b/examples/backwards-read.png
new file mode 100644 (file)
index 0000000..81dc920
Binary files /dev/null and b/examples/backwards-read.png differ
diff --git a/examples/basic-verify.png b/examples/basic-verify.png
new file mode 100644 (file)
index 0000000..98f7302
Binary files /dev/null and b/examples/basic-verify.png differ
diff --git a/examples/butterfly.png b/examples/butterfly.png
new file mode 100644 (file)
index 0000000..2c56651
Binary files /dev/null and b/examples/butterfly.png differ
diff --git a/examples/cpp_null.png b/examples/cpp_null.png
new file mode 100644 (file)
index 0000000..5303ac2
Binary files /dev/null and b/examples/cpp_null.png differ
diff --git a/examples/cpuio.png b/examples/cpuio.png
new file mode 100644 (file)
index 0000000..02938db
Binary files /dev/null and b/examples/cpuio.png differ
diff --git a/examples/cross-stripe-verify.png b/examples/cross-stripe-verify.png
new file mode 100644 (file)
index 0000000..90aa630
Binary files /dev/null and b/examples/cross-stripe-verify.png differ
diff --git a/examples/dev-dax.png b/examples/dev-dax.png
new file mode 100644 (file)
index 0000000..2463bca
Binary files /dev/null and b/examples/dev-dax.png differ
diff --git a/examples/dfs.png b/examples/dfs.png
new file mode 100644 (file)
index 0000000..049ccae
Binary files /dev/null and b/examples/dfs.png differ
diff --git a/examples/disk-zone-profile.png b/examples/disk-zone-profile.png
new file mode 100644 (file)
index 0000000..5f7b24c
Binary files /dev/null and b/examples/disk-zone-profile.png differ
diff --git a/examples/e4defrag.png b/examples/e4defrag.png
new file mode 100644 (file)
index 0000000..00a7fef
Binary files /dev/null and b/examples/e4defrag.png differ
diff --git a/examples/e4defrag2.png b/examples/e4defrag2.png
new file mode 100644 (file)
index 0000000..8a128e9
Binary files /dev/null and b/examples/e4defrag2.png differ
diff --git a/examples/enospc-pressure.png b/examples/enospc-pressure.png
new file mode 100644 (file)
index 0000000..da28b7c
Binary files /dev/null and b/examples/enospc-pressure.png differ
diff --git a/examples/exitwhat.png b/examples/exitwhat.png
new file mode 100644 (file)
index 0000000..9fc1883
Binary files /dev/null and b/examples/exitwhat.png differ
diff --git a/examples/falloc.png b/examples/falloc.png
new file mode 100644 (file)
index 0000000..886be22
Binary files /dev/null and b/examples/falloc.png differ
diff --git a/examples/filecreate-ioengine.png b/examples/filecreate-ioengine.png
new file mode 100644 (file)
index 0000000..45d11da
Binary files /dev/null and b/examples/filecreate-ioengine.png differ
diff --git a/examples/filedelete-ioengine.png b/examples/filedelete-ioengine.png
new file mode 100644 (file)
index 0000000..3512ab7
Binary files /dev/null and b/examples/filedelete-ioengine.png differ
diff --git a/examples/filestat-ioengine.png b/examples/filestat-ioengine.png
new file mode 100644 (file)
index 0000000..bed59ab
Binary files /dev/null and b/examples/filestat-ioengine.png differ
diff --git a/examples/fio-rand-RW.png b/examples/fio-rand-RW.png
new file mode 100644 (file)
index 0000000..aa4b099
Binary files /dev/null and b/examples/fio-rand-RW.png differ
diff --git a/examples/fio-rand-read.png b/examples/fio-rand-read.png
new file mode 100644 (file)
index 0000000..d45664a
Binary files /dev/null and b/examples/fio-rand-read.png differ
diff --git a/examples/fio-rand-write.png b/examples/fio-rand-write.png
new file mode 100644 (file)
index 0000000..10e068b
Binary files /dev/null and b/examples/fio-rand-write.png differ
diff --git a/examples/fio-seq-RW.png b/examples/fio-seq-RW.png
new file mode 100644 (file)
index 0000000..a2be35e
Binary files /dev/null and b/examples/fio-seq-RW.png differ
diff --git a/examples/fio-seq-read.png b/examples/fio-seq-read.png
new file mode 100644 (file)
index 0000000..cf8f297
Binary files /dev/null and b/examples/fio-seq-read.png differ
diff --git a/examples/fio-seq-write.png b/examples/fio-seq-write.png
new file mode 100644 (file)
index 0000000..8db1209
Binary files /dev/null and b/examples/fio-seq-write.png differ
diff --git a/examples/fixed-rate-submission.png b/examples/fixed-rate-submission.png
new file mode 100644 (file)
index 0000000..86ca9b3
Binary files /dev/null and b/examples/fixed-rate-submission.png differ
diff --git a/examples/flow.png b/examples/flow.png
new file mode 100644 (file)
index 0000000..26a3d34
Binary files /dev/null and b/examples/flow.png differ
diff --git a/examples/fsx.png b/examples/fsx.png
new file mode 100644 (file)
index 0000000..b4e13c8
Binary files /dev/null and b/examples/fsx.png differ
diff --git a/examples/ftruncate.png b/examples/ftruncate.png
new file mode 100644 (file)
index 0000000..b98895f
Binary files /dev/null and b/examples/ftruncate.png differ
diff --git a/examples/gfapi.png b/examples/gfapi.png
new file mode 100644 (file)
index 0000000..acc6a6a
Binary files /dev/null and b/examples/gfapi.png differ
diff --git a/examples/gpudirect-rdmaio-client.png b/examples/gpudirect-rdmaio-client.png
new file mode 100644 (file)
index 0000000..eac7985
Binary files /dev/null and b/examples/gpudirect-rdmaio-client.png differ
diff --git a/examples/gpudirect-rdmaio-server.png b/examples/gpudirect-rdmaio-server.png
new file mode 100644 (file)
index 0000000..e043d7c
Binary files /dev/null and b/examples/gpudirect-rdmaio-server.png differ
diff --git a/examples/http-s3.png b/examples/http-s3.png
new file mode 100644 (file)
index 0000000..2021e85
Binary files /dev/null and b/examples/http-s3.png differ
diff --git a/examples/http-swift.png b/examples/http-swift.png
new file mode 100644 (file)
index 0000000..9928fb1
Binary files /dev/null and b/examples/http-swift.png differ
diff --git a/examples/http-webdav.png b/examples/http-webdav.png
new file mode 100644 (file)
index 0000000..c37c3de
Binary files /dev/null and b/examples/http-webdav.png differ
diff --git a/examples/ime.png b/examples/ime.png
new file mode 100644 (file)
index 0000000..f636f5e
Binary files /dev/null and b/examples/ime.png differ
diff --git a/examples/iometer-file-access-server.png b/examples/iometer-file-access-server.png
new file mode 100644 (file)
index 0000000..e312455
Binary files /dev/null and b/examples/iometer-file-access-server.png differ
diff --git a/examples/jesd219.png b/examples/jesd219.png
new file mode 100644 (file)
index 0000000..73b5a12
Binary files /dev/null and b/examples/jesd219.png differ
diff --git a/examples/latency-profile.png b/examples/latency-profile.png
new file mode 100644 (file)
index 0000000..50650df
Binary files /dev/null and b/examples/latency-profile.png differ
diff --git a/examples/libcufile-cufile.png b/examples/libcufile-cufile.png
new file mode 100644 (file)
index 0000000..f3758e5
Binary files /dev/null and b/examples/libcufile-cufile.png differ
diff --git a/examples/libcufile-posix.png b/examples/libcufile-posix.png
new file mode 100644 (file)
index 0000000..7818feb
Binary files /dev/null and b/examples/libcufile-posix.png differ
diff --git a/examples/libhdfs.png b/examples/libhdfs.png
new file mode 100644 (file)
index 0000000..e774c91
Binary files /dev/null and b/examples/libhdfs.png differ
diff --git a/examples/libiscsi.png b/examples/libiscsi.png
new file mode 100644 (file)
index 0000000..d0006cc
Binary files /dev/null and b/examples/libiscsi.png differ
diff --git a/examples/libpmem.png b/examples/libpmem.png
new file mode 100644 (file)
index 0000000..8a9a143
Binary files /dev/null and b/examples/libpmem.png differ
diff --git a/examples/librpma_apm-client.png b/examples/librpma_apm-client.png
new file mode 100644 (file)
index 0000000..2fe02cd
Binary files /dev/null and b/examples/librpma_apm-client.png differ
diff --git a/examples/librpma_apm-server.png b/examples/librpma_apm-server.png
new file mode 100644 (file)
index 0000000..f78ae02
Binary files /dev/null and b/examples/librpma_apm-server.png differ
diff --git a/examples/librpma_gpspm-client.png b/examples/librpma_gpspm-client.png
new file mode 100644 (file)
index 0000000..0c975a2
Binary files /dev/null and b/examples/librpma_gpspm-client.png differ
diff --git a/examples/librpma_gpspm-server.png b/examples/librpma_gpspm-server.png
new file mode 100644 (file)
index 0000000..5612453
Binary files /dev/null and b/examples/librpma_gpspm-server.png differ
diff --git a/examples/libzbc-rand-write.png b/examples/libzbc-rand-write.png
new file mode 100644 (file)
index 0000000..1d27741
Binary files /dev/null and b/examples/libzbc-rand-write.png differ
diff --git a/examples/libzbc-seq-read.png b/examples/libzbc-seq-read.png
new file mode 100644 (file)
index 0000000..5a53222
Binary files /dev/null and b/examples/libzbc-seq-read.png differ
diff --git a/examples/mtd.png b/examples/mtd.png
new file mode 100644 (file)
index 0000000..8cb3692
Binary files /dev/null and b/examples/mtd.png differ
diff --git a/examples/nbd.png b/examples/nbd.png
new file mode 100644 (file)
index 0000000..e3bcf61
Binary files /dev/null and b/examples/nbd.png differ
diff --git a/examples/netio.png b/examples/netio.png
new file mode 100644 (file)
index 0000000..81afd41
Binary files /dev/null and b/examples/netio.png differ
diff --git a/examples/netio_multicast.png b/examples/netio_multicast.png
new file mode 100644 (file)
index 0000000..f07ab4b
Binary files /dev/null and b/examples/netio_multicast.png differ
diff --git a/examples/nfs.png b/examples/nfs.png
new file mode 100644 (file)
index 0000000..29dbca0
Binary files /dev/null and b/examples/nfs.png differ
diff --git a/examples/null.png b/examples/null.png
new file mode 100644 (file)
index 0000000..052671d
Binary files /dev/null and b/examples/null.png differ
diff --git a/examples/numa.png b/examples/numa.png
new file mode 100644 (file)
index 0000000..1ef4575
Binary files /dev/null and b/examples/numa.png differ
diff --git a/examples/pmemblk.png b/examples/pmemblk.png
new file mode 100644 (file)
index 0000000..250e254
Binary files /dev/null and b/examples/pmemblk.png differ
diff --git a/examples/poisson-rate-submission.png b/examples/poisson-rate-submission.png
new file mode 100644 (file)
index 0000000..739c256
Binary files /dev/null and b/examples/poisson-rate-submission.png differ
diff --git a/examples/rados.png b/examples/rados.png
new file mode 100644 (file)
index 0000000..91bd61a
Binary files /dev/null and b/examples/rados.png differ
diff --git a/examples/rand-zones.png b/examples/rand-zones.png
new file mode 100644 (file)
index 0000000..13cbfb4
Binary files /dev/null and b/examples/rand-zones.png differ
diff --git a/examples/rbd.png b/examples/rbd.png
new file mode 100644 (file)
index 0000000..f118613
Binary files /dev/null and b/examples/rbd.png differ
diff --git a/examples/rdmaio-client.png b/examples/rdmaio-client.png
new file mode 100644 (file)
index 0000000..4e4bc28
Binary files /dev/null and b/examples/rdmaio-client.png differ
diff --git a/examples/rdmaio-server.png b/examples/rdmaio-server.png
new file mode 100644 (file)
index 0000000..fc34472
Binary files /dev/null and b/examples/rdmaio-server.png differ
diff --git a/examples/ssd-steadystate.png b/examples/ssd-steadystate.png
new file mode 100644 (file)
index 0000000..eb27f8a
Binary files /dev/null and b/examples/ssd-steadystate.png differ
diff --git a/examples/ssd-test.png b/examples/ssd-test.png
new file mode 100644 (file)
index 0000000..a92ed15
Binary files /dev/null and b/examples/ssd-test.png differ
diff --git a/examples/steadystate.png b/examples/steadystate.png
new file mode 100644 (file)
index 0000000..4bb9048
Binary files /dev/null and b/examples/steadystate.png differ
diff --git a/examples/surface-scan.png b/examples/surface-scan.png
new file mode 100644 (file)
index 0000000..0057380
Binary files /dev/null and b/examples/surface-scan.png differ
diff --git a/examples/test.png b/examples/test.png
new file mode 100644 (file)
index 0000000..6be5002
Binary files /dev/null and b/examples/test.png differ
diff --git a/examples/tiobench-example.png b/examples/tiobench-example.png
new file mode 100644 (file)
index 0000000..1441032
Binary files /dev/null and b/examples/tiobench-example.png differ
diff --git a/examples/waitfor.png b/examples/waitfor.png
new file mode 100644 (file)
index 0000000..64e4bf9
Binary files /dev/null and b/examples/waitfor.png differ
diff --git a/examples/zbd-rand-write.png b/examples/zbd-rand-write.png
new file mode 100644 (file)
index 0000000..d58721b
Binary files /dev/null and b/examples/zbd-rand-write.png differ
diff --git a/examples/zbd-seq-read.png b/examples/zbd-seq-read.png
new file mode 100644 (file)
index 0000000..b81a08c
Binary files /dev/null and b/examples/zbd-seq-read.png differ
diff --git a/examples/zipf.png b/examples/zipf.png
new file mode 100644 (file)
index 0000000..cb2a981
Binary files /dev/null and b/examples/zipf.png differ
diff --git a/tools/fiograph/fiograph.conf b/tools/fiograph/fiograph.conf
new file mode 100644 (file)
index 0000000..7b851e1
--- /dev/null
@@ -0,0 +1,102 @@
+[fio_jobs]
+header=<<B><font color="{}"> {} </font></B> >
+header_color=black
+text_color=darkgreen
+shape=box
+shape_color=blue
+style=rounded
+title_style=<<table border='0' cellborder='0' cellspacing='1'> <tr> <td align='center'> <b> {} </b> </td> </tr>
+item_style=<tr> <td align = "left"> <font color="{}" > {} </font> </td> </tr>
+cluster_style=filled
+cluster_color=gainsboro
+
+[exec_prerun]
+text_color=red
+
+[exec_postrun]
+text_color=red
+
+[numjobs]
+text_color=red
+style=<font color="{}" > x {} </font>
+
+[ioengine]
+text_color=darkblue
+specific_options_color=darkblue
+
+# definitions of engine's specific options
+
+[ioengine_cpuio]
+specific_options=cpuload cpumode cpuchunks exit_on_io_done
+
+[ioengine_dfs]
+specific_options=pool  cont  chunk_size  object_class  svcl
+
+[ioengine_e4defrag]
+specific_options=donorname  inplace
+
+[ioengine_filestat]
+specific_options=stat_type
+
+[ioengine_single-instance]
+specific_options=volume  brick
+
+[ioengine_http]
+specific_options=https  http_host  http_user  http_pass  http_s3_key  http_s3_keyid  http_swift_auth_token  http_s3_region  http_mode  http_verbose
+
+[ioengine_ime_aio]
+specific_options=ime_psync  ime_psyncv
+
+[ioengine_io_uring]
+specific_options=hipri  cmdprio_percentage  cmdprio_percentage  fixedbufs  registerfiles  sqthread_poll  sqthread_poll_cpu  nonvectored  uncached  nowait  force_async
+
+[ioengine_libaio]
+specific_options=userspace_reap  cmdprio_percentage  cmdprio_percentage  nowait
+
+[ioengine_libcufile]
+specific_options=gpu_dev_ids  cuda_io
+
+[ioengine_libhdfs]
+specific_options=namenode  hostname  port  hdfsdirectory  chunk_size  single_instance  hdfs_use_direct
+
+[ioengine_libiscsi]
+specific_options=initiator
+
+[ioengine_librpma_apm_server]
+specific_options=librpma_apm_client
+
+[ioengine_busy_wait_polling]
+specific_options=serverip  port  direct_write_to_pmem
+
+[ioengine_librpma_gpspm_server]
+specific_options=librpma_gpspm_client
+
+[ioengine_mmap]
+specific_options=thp
+
+[ioengine_mtd]
+specific_options=skip_bad
+
+[ioengine_nbd]
+specific_options=uri
+
+[ioengine_net]
+specific_options=hostname  port  protocol  nodelay  listen  pingpong  interface  ttl  window_size  mss  netsplice
+
+[ioengine_nfs]
+specific_options=nfs_url
+
+[ioengine_rados]
+specific_options=clustername  pool  clientname  busy_poll  touch_objects
+
+[ioengine_rbd]
+specific_options=clustername  rbdname  pool  clientname  busy_poll
+
+[ioengine_rdma]
+specific_options=hostname  bindname  port  verb
+
+[ioengine_sg]
+specific_options=hipri  readfua  writefua  sg_write_mode  sg
+
+[ioengine_pvsync2]
+specific_options=hipri  hipri_percentage  uncached  nowait  sync  psync  vsync  pvsync
diff --git a/tools/fiograph/fiograph.py b/tools/fiograph/fiograph.py
new file mode 100755 (executable)
index 0000000..7695c96
--- /dev/null
@@ -0,0 +1,305 @@
+#!/usr/bin/env python3
+from graphviz import Digraph
+import argparse
+import configparser
+import os
+
+config_file = None
+fio_file = None
+
+
+def get_section_option(section_name, option_name, default=None):
+    global fio_file
+    if fio_file.has_option(section_name, option_name):
+        return fio_file[section_name][option_name]
+    return default
+
+
+def get_config_option(section_name, option_name, default=None):
+    global config_file
+    if config_file.has_option(section_name, option_name):
+        return config_file[section_name][option_name]
+    return default
+
+
+def get_header_color(keyword='fio_jobs', default_color='black'):
+    return get_config_option(keyword, 'header_color', default_color)
+
+
+def get_shape_color(keyword='fio_jobs', default_color='black'):
+    return get_config_option(keyword, 'shape_color', default_color)
+
+
+def get_text_color(keyword='fio_jobs', default_color='black'):
+    return get_config_option(keyword, 'text_color', default_color)
+
+
+def get_cluster_color(keyword='fio_jobs', default_color='gray92'):
+    return get_config_option(keyword, 'cluster_color', default_color)
+
+
+def get_header(keyword='fio_jobs'):
+    return get_config_option(keyword, 'header')
+
+
+def get_shape(keyword='fio_jobs'):
+    return get_config_option(keyword, 'shape', 'box')
+
+
+def get_style(keyword='fio_jobs'):
+    return get_config_option(keyword, 'style', 'rounded')
+
+
+def get_cluster_style(keyword='fio_jobs'):
+    return get_config_option(keyword, 'cluster_style', 'filled')
+
+
+def get_specific_options(engine):
+    if not engine:
+        return ''
+    return get_config_option('ioengine_{}'.format(engine), 'specific_options', '').split(' ')
+
+
+def render_option(section, label, display, option, color_override=None):
+    # These options are already shown with graphical helpers, no need to report them directly
+    skip_list = ['size', 'stonewall', 'runtime', 'time_based',
+                 'numjobs', 'wait_for', 'wait_for_previous']
+    # If the option doesn't exist or if a special handling is already done
+    # don't render it, just return the current state
+    if option in skip_list or option not in section:
+        return label, display
+    display = option
+    if section[option]:
+        display = '{} = {}'.format(display, section[option])
+
+    # Adding jobs's options into the box, darkgreen is the default color
+    if color_override:
+        color = color_override
+    else:
+        color = get_text_color(option, get_text_color('fio_jobs', 'darkgreen'))
+    label += get_config_option('fio_jobs',
+                               'item_style').format(color, display)
+    return label, display
+
+
+def render_options(fio_file, section_name):
+    """Render all options of a section."""
+    display = section_name
+    section = fio_file[section_name]
+
+    # Add a multiplier to the section_name if numjobs is set
+    numjobs = int(get_section_option(section_name, 'numjobs', '1'))
+    if numjobs > 1:
+        display = display + \
+            get_style('numjobs').format(
+                get_text_color('numjobs'), numjobs)
+
+    # Header of the box
+    label = get_config_option('fio_jobs', 'title_style').format(display)
+
+    # Let's parse all the options of the current fio thread
+    # Some needs to be printed on top or bottom of the job to ease the read
+    to_early_print = ['exec_prerun', 'ioengine']
+    to_late_print = ['exec_postrun']
+
+    # Let's print the options on top of the box
+    for early_print in to_early_print:
+        label, display = render_option(
+            section, label, display, early_print)
+
+    current_io_engine = get_section_option(
+        section_name, 'ioengine', None)
+    if current_io_engine:
+        # Let's print all specifics options for this engine
+        for specific_option in sorted(get_specific_options(current_io_engine)):
+            label, display = render_option(
+                section, label, display, specific_option, get_config_option('ioengine', 'specific_options_color'))
+
+    # Let's print generic options sorted by name
+    for option in sorted(section):
+        if option in to_early_print or option in to_late_print or option in get_specific_options(current_io_engine):
+            continue
+        label, display = render_option(section, label, display, option)
+
+    # let's print options on the bottom of the box
+    for late_print in to_late_print:
+        label, display = render_option(
+            section, label, display, late_print)
+
+    # End of the box content
+    label += '</table>>'
+    return label
+
+
+def render_section(current_graph, fio_file, section_name, label):
+    """Render the section."""
+    attr = None
+    section = fio_file[section_name]
+
+    # Let's render the box associated to a job
+    current_graph.node(section_name, label,
+                       shape=get_shape(),
+                       color=get_shape_color(),
+                       style=get_style())
+
+    # Let's report the duration of the jobs with a self-loop arrow
+    if 'runtime' in section and 'time_based' in section:
+        attr = 'runtime={}'.format(section['runtime'])
+    elif 'size' in section:
+        attr = 'size={}'.format(section['size'])
+    if attr:
+        current_graph.edge(section_name, section_name, attr)
+
+
+def create_sub_graph(name):
+    """Return a new graph."""
+    # We need to put 'cluster' in the name to ensure graphviz consider it as a cluster
+    cluster_name = 'cluster_' + name
+    # Unset the main graph labels to avoid a recopy in each subgraph
+    attr = {}
+    attr['label'] = ''
+    new_graph = Digraph(name=cluster_name, graph_attr=attr)
+    new_graph.attr(style=get_cluster_style(),
+                   color=get_cluster_color())
+    return new_graph
+
+
+def create_legend():
+    """Return a legend."""
+    html_table = "<<table border='0' cellborder='1' cellspacing='0' cellpadding='4'>"
+    html_table += '<tr><td COLSPAN="2"><b>Legend</b></td></tr>'
+    legend_item = '<tr> <td>{}</td> <td><font color="{}">{}</font></td></tr>"'
+    legend_bgcolor_item = '<tr><td>{}</td><td BGCOLOR="{}"></td></tr>'
+    html_table += legend_item.format('numjobs',
+                                     get_text_color('numjobs'), 'x numjobs')
+    html_table += legend_item.format('generic option',
+                                     get_text_color(), 'generic option')
+    html_table += legend_item.format('ioengine option',
+                                     get_text_color('ioengine'), 'ioengine option')
+    html_table += legend_bgcolor_item.format('job', get_shape_color())
+    html_table += legend_bgcolor_item.format(
+        'execution group', get_cluster_color())
+    html_table += '</table>>'
+    legend = Digraph('html_table')
+    legend.node('legend', shape='none', label=html_table)
+    return legend
+
+
+def fio_to_graphviz(filename, format):
+    """Compute the graphviz graph from the fio file."""
+
+    # Let's read the fio file
+    global fio_file
+    fio_file = configparser.RawConfigParser(
+        allow_no_value=True,
+        default_section="global",
+        inline_comment_prefixes="'#', ';'")
+    fio_file.read(filename)
+
+    # Prepare the main graph object
+    # Let's define the header of the document
+    attrs = {}
+    attrs['labelloc'] = 't'
+    attrs['label'] = get_header().format(
+        get_header_color(), os.path.basename(filename))
+    main_graph = Digraph(engine='dot', graph_attr=attrs, format=format)
+
+    # Let's add a legend
+    main_graph.subgraph(create_legend())
+
+    # By default all jobs are run in parallel and depends on "global"
+    depends_on = fio_file.default_section
+
+    # The previous section is by default the global section
+    previous_section = fio_file.default_section
+
+    current_graph = main_graph
+
+    # The first job will be a new execution group
+    new_execution_group = True
+
+    # Let's interate on all sections to create links between them
+    for section_name in fio_file.sections():
+        # The current section
+        section = fio_file[section_name]
+
+        # If the current section is waiting the previous job
+        if ('stonewall' or 'wait_for_previous') in section:
+            # let's remember what was the previous job we depend on
+            depends_on = previous_section
+            new_execution_group = True
+        elif 'wait_for' in section:
+            # This sections depends on a named section pointed by wait_for
+            depends_on = section['wait_for']
+            new_execution_group = True
+
+        if new_execution_group:
+            # Let's link the current graph with the main one
+            main_graph.subgraph(current_graph)
+            # Let's create a new graph to represent all the incoming jobs running at the same time
+            current_graph = create_sub_graph(section_name)
+
+        # Let's render the current section in its execution group
+        render_section(current_graph, fio_file, section_name,
+                       render_options(fio_file, section_name))
+
+        # Let's trace the link between this job and the one it depends on
+        # If we depend on 'global', we can avoid doing adding an arrow as we don't want to see 'global'
+        if depends_on != fio_file.default_section:
+            current_graph.edge(depends_on, section_name)
+
+        # The current section become the parent of the next one
+        previous_section = section_name
+
+        # We are by default in the same execution group
+        new_execution_group = False
+
+    # The last subgraph isn't rendered yet
+    main_graph.subgraph(current_graph)
+
+    # Let's return the main graphviz object
+    return main_graph
+
+
+def setup_commandline():
+    "Prepare the command line."
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--file', action='store',
+                        type=str,
+                        required=True,
+                        help='the fio file to graph')
+    parser.add_argument('--output', action='store',
+                        type=str,
+                        help='the output filename')
+    parser.add_argument('--format', action='store',
+                        type=str,
+                        default='png',
+                        help='the output format')
+    parser.add_argument('--view', action='store_true',
+                        default=False,
+                        help='view the graph')
+    parser.add_argument('--keep', action='store_true',
+                        default=False,
+                        help='keep the graphviz script file')
+    parser.add_argument('--config', action='store',
+                        type=str,
+                        default='fiograph.conf',
+                        help='the configuration filename')
+    args = parser.parse_args()
+    return args
+
+
+def main():
+    global config_file
+    args = setup_commandline()
+    output_file = args.file
+    if args.output is None:
+        output_file = output_file.replace('.fio', '')
+    config_file = configparser.RawConfigParser(allow_no_value=True)
+    config_file.read(args.config)
+    fio_to_graphviz(args.file, args.format).render(output_file, view=args.view)
+    if not args.keep:
+        os.remove(output_file)
+
+
+main()