From 420f46508bf861d5279c9b378ca7b08e39a828f6 Mon Sep 17 00:00:00 2001 From: John Rinehart Date: Mon, 18 Aug 2025 11:34:38 -0400 Subject: [PATCH 1/5] fix: add a pyproject.toml to modernize the build system --- bin/bench-fio | 6 ------ bin/fio-plot | 6 ------ pyproject.toml | 22 ++++++++++++++++++++++ requirements.txt | 1 - setup.py | 22 ---------------------- 5 files changed, 22 insertions(+), 35 deletions(-) delete mode 100755 bin/bench-fio delete mode 100755 bin/fio-plot create mode 100644 pyproject.toml delete mode 100644 setup.py diff --git a/bin/bench-fio b/bin/bench-fio deleted file mode 100755 index eb8ac51d33..0000000000 --- a/bin/bench-fio +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env python3 - -import bench_fio - -if __name__ == '__main__': - bench_fio.main() diff --git a/bin/fio-plot b/bin/fio-plot deleted file mode 100755 index 88ca9688b2..0000000000 --- a/bin/fio-plot +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env python3 - -import fio_plot - -if __name__ == '__main__': - fio_plot.main() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000000..e68ba42c93 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,22 @@ +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[project] +version = "1.1.16" +name = "fio-plot" + +dependencies = [ + "numpy", + "matplotlib", + "pillow", + "pyan3", + "rich", +] + +[project.gui-scripts] +fio-plot = "fio_plot:main" +bench_fio = "bench_fio:main" + +[tool.setuptools.packages.find] +exclude = ["bench_fio.scripts"] diff --git a/requirements.txt b/requirements.txt index e9b578ccfa..b5d5488026 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,5 @@ numpy Pillow -pyparsing matplotlib<=3.8 pyan3 rich diff --git a/setup.py b/setup.py deleted file mode 100644 index ae5bf380ef..0000000000 --- a/setup.py +++ /dev/null @@ -1,22 +0,0 @@ -import setuptools - -with open("README.md", "r") as fh: - long_description = fh.read() - -setuptools.setup( - name="fio-plot", - version="1.1.16", - author="louwrentius", - description="Create charts from FIO storage benchmark tool output", - long_description=long_description, - long_description_content_type="text/markdown", - url="https://github.com/louwrentius/fio-plot/", - packages=setuptools.find_packages(), - install_requires=["numpy", "matplotlib", "Pillow", "pyan3", "pyparsing", "rich"], - include_package_data=True, - package_data={"bench_fio": ["templates/*.fio", "scripts/*.sh"]}, - entry_points={ - "console_scripts": ["fio-plot = fio_plot:main", "bench-fio = bench_fio:main"], - }, - scripts=["bin/fio-plot", "bin/bench-fio"], -) From 610fb3b03435fe957f71ddd8f95f51c743e1061d Mon Sep 17 00:00:00 2001 From: John Rinehart Date: Tue, 26 Aug 2025 05:24:16 -0700 Subject: [PATCH 2/5] fix(bench_fio): repair test_number_of_settings FAILED tests/bench_fio_test.py::TestFunctions::test_number_of_settings - AssertionError: 32 != 33 --- bench_fio/benchlib/argparsing.py | 7 ++++++- bench_fio/benchlib/defaultsettings.py | 1 + bench_fio/benchlib/display.py | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/bench_fio/benchlib/argparsing.py b/bench_fio/benchlib/argparsing.py index 6864b513f1..0f1f70c664 100644 --- a/bench_fio/benchlib/argparsing.py +++ b/bench_fio/benchlib/argparsing.py @@ -38,6 +38,11 @@ def get_arguments(settings): nargs="+", type=str, ) + ag.add_argument( + "--template", + help="Benchmark template to use", + default="benchmark.ini", + ) ag.add_argument( "-t", "--type", @@ -230,7 +235,7 @@ def get_arguments(settings): default=settings["invalidate"], ) ag.add_argument( - "--quiet", help="The progresbar will be supressed.", action="store_true" + "--quiet", help="The progress bar will be supressed.", action="store_true" ) ag.add_argument( "--loginterval", diff --git a/bench_fio/benchlib/defaultsettings.py b/bench_fio/benchlib/defaultsettings.py index a93d77242a..ae57bf64cc 100644 --- a/bench_fio/benchlib/defaultsettings.py +++ b/bench_fio/benchlib/defaultsettings.py @@ -8,6 +8,7 @@ def get_default_settings(): settings = {} settings["benchmarks"] = None settings["target"] = [] + settings["template"] = "benchmark.ini" settings["type"] = None settings["engine"] = "libaio" settings["mode"] = ["randread"] diff --git a/bench_fio/benchlib/display.py b/bench_fio/benchlib/display.py index d1ce33c76d..6c5367474a 100644 --- a/bench_fio/benchlib/display.py +++ b/bench_fio/benchlib/display.py @@ -33,7 +33,7 @@ def calculate_duration(settings, tests): number_of_tests = number_of_tests/len(settings["target"]) time_per_test = settings["runtime"] if time_per_test: - duration_in_seconds = number_of_tests * time_per_test + duration_in_seconds = int(number_of_tests * time_per_test) duration = str(datetime.timedelta(seconds=duration_in_seconds)) else: duration = None From 6683208c94e8087a1629c4a7204f9dec5dea5ca9 Mon Sep 17 00:00:00 2001 From: John Rinehart Date: Tue, 26 Aug 2025 05:56:12 -0700 Subject: [PATCH 3/5] fix(bench_fio): resolve a couple of test failures related to the number of bench_fio tests python3.13-fio_plot> =================================== FAILURES =================================== python3.13-fio_plot> ____________________ TestFunctions.test_calculate_duration _____________________ python3.13-fio_plot> self = python3.13-fio_plot> def test_calculate_duration(self): python3.13-fio_plot> > self.assertEqual( python3.13-fio_plot> display.calculate_duration(self.settings, self.tests), "1:38:00" python3.13-fio_plot> ) python3.13-fio_plot> E AssertionError: '0:49:00' != '1:38:00' python3.13-fio_plot> E - 0:49:00 python3.13-fio_plot> E + 1:38:00 python3.13-fio_plot> tests/bench_fio_test.py:34: AssertionError python3.13-fio_plot> ----------------------------- Captured stdout call ----------------------------- python3.13-fio_plot> len(tests)=49 python3.13-fio_plot> ____________________ TestFunctions.test_generate_benchmarks ____________________ python3.13-fio_plot> self = python3.13-fio_plot> def test_generate_benchmarks(self): python3.13-fio_plot> > self.assertEqual(len(supporting.generate_test_list(self.settings)), 98) python3.13-fio_plot> E AssertionError: 49 != 98 python3.13-fio_plot> tests/bench_fio_test.py:21: AssertionError python3.13-fio_plot> __________________ TestFunctions.test_generate_benchmarks_big __________________ python3.13-fio_plot> self = python3.13-fio_plot> def test_generate_benchmarks_big(self): python3.13-fio_plot> self.settings["target"] = ["filea", "fileb", "filec", "filed"] python3.13-fio_plot> self.settings["block_size"] = ["4k", "8k", "16k", "32k"] python3.13-fio_plot> > self.assertEqual(len(supporting.generate_test_list(self.settings)), 1568) python3.13-fio_plot> E AssertionError: 784 != 1568 python3.13-fio_plot> tests/bench_fio_test.py:26: AssertionError --- bench_fio/benchlib/argparsing.py | 2 +- bench_fio/benchlib/display.py | 5 ++--- tests/bench_fio_test.py | 6 +++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/bench_fio/benchlib/argparsing.py b/bench_fio/benchlib/argparsing.py index 0f1f70c664..0f3c34565d 100644 --- a/bench_fio/benchlib/argparsing.py +++ b/bench_fio/benchlib/argparsing.py @@ -97,7 +97,7 @@ def get_arguments(settings): ag.add_argument( "--runtime", - help=f"Override the default test runtime per benchmark" + help=f"Override the default test runtime per benchmark (seconds)" f"(default: {settings['runtime']})", type=int, default=settings["runtime"], diff --git a/bench_fio/benchlib/display.py b/bench_fio/benchlib/display.py index 6c5367474a..3323dd2400 100644 --- a/bench_fio/benchlib/display.py +++ b/bench_fio/benchlib/display.py @@ -56,7 +56,7 @@ def print_options(settings, table): data = parse_settings_for_display(settings) for item in settings.keys(): if item not in settings["filter_items"]: # filter items are internal options that aren't relevant - if item not in descriptions.keys(): + if item not in descriptions.keys(): customitem = item + "*" # These are custom fio options so we mark them as such #print(f"{customitem:<{fl}}: {data[item]:<}") table.add_row(customitem, data[item]) @@ -70,7 +70,6 @@ def print_options(settings, table): def display_header(settings, tests): - duration = calculate_duration(settings, tests) table = Table(title="Bench-fio",title_style=Style(bgcolor="dodger_blue2",bold=True)) table.add_column(no_wrap=True, header="Setting") @@ -79,4 +78,4 @@ def display_header(settings, tests): table.add_row("Estimated Duration",duration) print_options(settings, table) console = Console() - console.print(table) \ No newline at end of file + console.print(table) diff --git a/tests/bench_fio_test.py b/tests/bench_fio_test.py index a9bb8cbd86..657f83c4ed 100644 --- a/tests/bench_fio_test.py +++ b/tests/bench_fio_test.py @@ -18,12 +18,12 @@ def setUp(self): self.settings["output"] = "output_directory" def test_generate_benchmarks(self): - self.assertEqual(len(supporting.generate_test_list(self.settings)), 98) + self.assertEqual(len(supporting.generate_test_list(self.settings)), 49) def test_generate_benchmarks_big(self): self.settings["target"] = ["filea", "fileb", "filec", "filed"] self.settings["block_size"] = ["4k", "8k", "16k", "32k"] - self.assertEqual(len(supporting.generate_test_list(self.settings)), 1568) + self.assertEqual(len(supporting.generate_test_list(self.settings)), 784) def test_are_loop_items_lists(self): for item in self.settings["loop_items"]: @@ -32,7 +32,7 @@ def test_are_loop_items_lists(self): def test_calculate_duration(self): self.assertEqual( - display.calculate_duration(self.settings, self.tests), "1:38:00" + display.calculate_duration(self.settings, self.tests), "0:49:00" ) def test_generate_output_directory_regular(self): From ebfdab83df2b9d435834507106e2151ac99d7ff3 Mon Sep 17 00:00:00 2001 From: John Rinehart Date: Tue, 26 Aug 2025 05:58:10 -0700 Subject: [PATCH 4/5] fix(bench_fio): fix the plot_3d test ``` python3.13-fio_plot> =================================== FAILURES =================================== <...> <...> <...> python3.13-fio_plot> ________________________ Test3D.test_correct_bars_drawn ________________________ python3.13-fio_plot> self = python3.13-fio_plot> def test_correct_bars_drawn(self): python3.13-fio_plot> settings = { python3.13-fio_plot> "type": ["iops"], python3.13-fio_plot> "rw": "read", python3.13-fio_plot> "source": "test", python3.13-fio_plot> "title": "test", python3.13-fio_plot> "subtitle": "", python3.13-fio_plot> "filter": ["read", "write"], python3.13-fio_plot> # intentionally using prime numbers python3.13-fio_plot> "iodepth": [2, 3], python3.13-fio_plot> "numjobs": [5, 11], python3.13-fio_plot> "maxjobs": 32, python3.13-fio_plot> "maxdepth": 32, python3.13-fio_plot> "max_z": None, python3.13-fio_plot> "dpi": 200, python3.13-fio_plot> "disable_fio_version": 2.0, python3.13-fio_plot> "output_filename": "/tmp/test.png" python3.13-fio_plot> } python3.13-fio_plot> python3.13-fio_plot> dataset = [{"data": []} python3.13-fio_plot> for iodepth in settings["iodepth"]: python3.13-fio_plot> for numjobs in settings["numjobs"]: python3.13-fio_plot> dataset[0]["data"].append( python3.13-fio_plot> { python3.13-fio_plot> "fio_version": 3.1, python3.13-fio_plot> "iodepth": str(iodepth), python3.13-fio_plot> "numjobs": str(numjobs), python3.13-fio_plot> "rw": "read", python3.13-fio_plot> "type": "read", python3.13-fio_plot> "iops": iodepth * numjobs, python3.13-fio_plot> } python3.13-fio_plot> ) python3.13-fio_plot> > plot_3d(settings, dataset) python3.13-fio_plot> tests/test_3d.py:38: python3.13-fio_plot> _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ python3.13-fio_plot> fio_plot/fiolib/bar3d.py:174: in plot_3d python3.13-fio_plot> supporting.create_title_and_sub( python3.13-fio_plot> _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ python3.13-fio_plot> settings = {'disable_fio_version': 2.0, 'dpi': 200, 'filter': ['read', 'write'], 'iodepth': [2, 3], ...} python3.13-fio_plot> plt = python3.13-fio_plot> bs = None, skip_keys = ['iodepth', 'numjobs'], sub_x_offset = 0.57 python3.13-fio_plot> sub_y_offset = 1.15 python3.13-fio_plot> def create_title_and_sub( python3.13-fio_plot> settings, plt, bs=None, skip_keys=[], sub_x_offset=0, sub_y_offset=0 python3.13-fio_plot> ): python3.13-fio_plot> # python3.13-fio_plot> # Offset title/subtitle if there is a 3rd y-axis python3.13-fio_plot> # python3.13-fio_plot> number_of_types = len(settings["type"]) python3.13-fio_plot> y_offset = 1.02 python3.13-fio_plot> if number_of_types <= 2: python3.13-fio_plot> x_offset = 0.5 python3.13-fio_plot> else: python3.13-fio_plot> x_offset = 0.425 python3.13-fio_plot> python3.13-fio_plot> if sub_x_offset > 0: python3.13-fio_plot> x_offset = sub_x_offset python3.13-fio_plot> if sub_y_offset > 0: python3.13-fio_plot> y_offset = sub_y_offset python3.13-fio_plot> python3.13-fio_plot> # python3.13-fio_plot> # plt.subtitle sets title and plt.title sets subtitle .... python3.13-fio_plot> # python3.13-fio_plot> > plt.suptitle(settings["title"], fontsize=settings["title_fontsize"]) python3.13-fio_plot> E KeyError: 'title_fontsize' python3.13-fio_plot> fio_plot/fiolib/supporting.py:382: KeyError ``` --- tests/test_3d.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/test_3d.py b/tests/test_3d.py index 6016f9698a..24d56b50b5 100644 --- a/tests/test_3d.py +++ b/tests/test_3d.py @@ -9,6 +9,8 @@ def test_correct_bars_drawn(self): "rw": "read", "source": "test", "title": "test", + "title_fontsize": 12, + "subtitle_fontsize": 20, "subtitle": "", "filter": ["read", "write"], # intentionally using prime numbers @@ -16,10 +18,12 @@ def test_correct_bars_drawn(self): "numjobs": [5, 11], "maxjobs": 32, "maxdepth": 32, - "max": None, + "max_z": None, "dpi": 200, "disable_fio_version": 2.0, - "output_filename": "/tmp/test.png" + "output_filename": "/tmp/test.png", + "truncate_xaxis": False, + "source_fontsize": 12, } dataset = [{"data": []}] From a53d97d5f094ca6bed841cb7e95563d5ad0e2b53 Mon Sep 17 00:00:00 2001 From: John Rinehart Date: Tue, 26 Aug 2025 05:59:25 -0700 Subject: [PATCH 5/5] fix(bench_fio): repair directory creation tests ``` python3.13-fio_plot> =================================== FAILURES =================================== python3.13-fio_plot> ______________ TestFunctions.test_generate_output_directory_mixed ______________ python3.13-fio_plot> self = python3.13-fio_plot> def test_generate_output_directory_mixed(self): python3.13-fio_plot> self.settings["mode"] = ["rw"] python3.13-fio_plot> self.settings["rwmixread"] = [75] python3.13-fio_plot> self.settings["loop_items"].append("rwmixread") python3.13-fio_plot> tests = supporting.generate_test_list(self.settings) python3.13-fio_plot> benchmark = tests[0] python3.13-fio_plot> self.assertEqual( python3.13-fio_plot> > supporting.generate_output_directory(self.settings, benchmark), python3.13-fio_plot> "output_directory/device/rw75/4k", python3.13-fio_plot> ) python3.13-fio_plot> tests/bench_fio_test.py:52: python3.13-fio_plot> _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ python3.13-fio_plot> settings = {'basename_list': ['precondition_template'], 'benchmarks': 49, 'block_size': ['4k'], 'ceph_pool': None, ...} python3.13-fio_plot> benchmark = {'block_size': '4k', 'iodepth': 1, 'mode': 'rw', 'numjobs': 1, ...} python3.13-fio_plot> def generate_output_directory(settings, benchmark): python3.13-fio_plot> settings["output"] = os.path.expanduser(settings["output"]) python3.13-fio_plot> if benchmark["mode"] in settings["mixed"]: python3.13-fio_plot> directory = ( python3.13-fio_plot> > f"{settings['output']}/{os.path.basename(benchmark['target_base'])}/" python3.13-fio_plot> f"{benchmark['mode']}{benchmark['rwmixread']}/{benchmark['block_size']}" python3.13-fio_plot> ) python3.13-fio_plot> E KeyError: 'target_base' python3.13-fio_plot> bench_fio/benchlib/supporting.py:45: KeyError python3.13-fio_plot> _____________ TestFunctions.test_generate_output_directory_regular _____________ python3.13-fio_plot> self = python3.13-fio_plot> def test_generate_output_directory_regular(self): python3.13-fio_plot> benchmark = self.tests[0] python3.13-fio_plot> self.assertEqual( python3.13-fio_plot> > supporting.generate_output_directory(self.settings, benchmark), python3.13-fio_plot> "output_directory/device/4k", python3.13-fio_plot> ) python3.13-fio_plot> tests/bench_fio_test.py:41: python3.13-fio_plot> _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ python3.13-fio_plot> settings = {'basename_list': ['precondition_template'], 'benchmarks': 49, 'block_size': ['4k'], 'ceph_pool': None, ...} python3.13-fio_plot> benchmark = {'block_size': '4k', 'iodepth': 1, 'mode': 'randread', 'numjobs': 1, ...} python3.13-fio_plot> def generate_output_directory(settings, benchmark): python3.13-fio_plot> settings["output"] = os.path.expanduser(settings["output"]) python3.13-fio_plot> if benchmark["mode"] in settings["mixed"]: python3.13-fio_plot> directory = ( python3.13-fio_plot> f"{settings['output']}/{os.path.basename(benchmark['target_base'])}/" python3.13-fio_plot> f"{benchmark['mode']}{benchmark['rwmixread']}/{benchmark['block_size']}" python3.13-fio_plot> ) python3.13-fio_plot> else: python3.13-fio_plot> > directory = f"{settings['output']}/{os.path.basename(benchmark['target_base'])}/{benchmark['block_size']}" python3.13-fio_plot> E KeyError: 'target_base' python3.13-fio_plot> bench_fio/benchlib/supporting.py:49: KeyError ``` --- bench_fio/benchlib/generatefio.py | 2 +- bench_fio/benchlib/runfio.py | 4 ++-- bench_fio/benchlib/supporting.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bench_fio/benchlib/generatefio.py b/bench_fio/benchlib/generatefio.py index 3801ccb636..730b8dc69f 100644 --- a/bench_fio/benchlib/generatefio.py +++ b/bench_fio/benchlib/generatefio.py @@ -28,7 +28,7 @@ def filter_options(settings, config, mapping, benchmark, output_directory): if isinstance(value, bool): value = boolean[str(value)] if key == "type": # we check if we target a file directory or block device - devicetype = checks.check_target_type(benchmark["target_base"], settings) + devicetype = checks.check_target_type(benchmark["target"], settings) config["FIOJOB"][devicetype] = benchmark["target"] if ( value diff --git a/bench_fio/benchlib/runfio.py b/bench_fio/benchlib/runfio.py index a6ccb03f29..be6d14511b 100644 --- a/bench_fio/benchlib/runfio.py +++ b/bench_fio/benchlib/runfio.py @@ -54,8 +54,8 @@ def run_fio(settings, benchmark): # passed to fio's filename but should be removed when checking the # existance of the path, or when writing a job file or log file in # the filesystem. - benchmark.update({"target_base": benchmark['target'].replace("\\", "")}) - tmpjobfile = f"/tmp/{os.path.basename(benchmark['target_base'])}-tmpjobfile.fio" + benchmark.update({"target": benchmark['target'].replace("\\", "")}) + tmpjobfile = f"/tmp/{os.path.basename(benchmark['target'])}-tmpjobfile.fio" output_directory = supporting.generate_output_directory(settings, benchmark) output_file = f"{output_directory}/{benchmark['mode']}-{benchmark['iodepth']}-{benchmark['numjobs']}.json" generatefio.generate_fio_job_file(settings, benchmark, output_directory, tmpjobfile) diff --git a/bench_fio/benchlib/supporting.py b/bench_fio/benchlib/supporting.py index 3b86efe724..e171efc0f7 100644 --- a/bench_fio/benchlib/supporting.py +++ b/bench_fio/benchlib/supporting.py @@ -42,11 +42,11 @@ def generate_output_directory(settings, benchmark): settings["output"] = os.path.expanduser(settings["output"]) if benchmark["mode"] in settings["mixed"]: directory = ( - f"{settings['output']}/{os.path.basename(benchmark['target_base'])}/" + f"{settings['output']}/{os.path.basename(benchmark['target'])}/" f"{benchmark['mode']}{benchmark['rwmixread']}/{benchmark['block_size']}" ) else: - directory = f"{settings['output']}/{os.path.basename(benchmark['target_base'])}/{benchmark['block_size']}" + directory = f"{settings['output']}/{os.path.basename(benchmark['target'])}/{benchmark['block_size']}" if "run" in benchmark.keys(): directory = directory + f"/run-{benchmark['run']}"