Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fio: Adding fio engine #32

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ The current version of hwbench supports 3 different engines.
- [stress-ng](https://github.com/ColinIanKing/stress-ng): no need to present this very popular low-level benchmarking tool
- spike: a custom engine used to make fans spike. Very useful to study the cooling strategy of a server.
- sleep: a stupid sleep call used to observe how the system is behaving in idle mode
- [fio](https://github.com/axboe/fio): a flexible storage benchmarking tool, see [documentation](./documentation/fio.md)

Benchmark performance metrics are extracted and saved for later analysis.

Expand Down
15 changes: 15 additions & 0 deletions configs/fio.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# This configuration will :
# - test /dev/nvme0n1 in 4k randread for 40 seconds
# -- first with 4 stressors
# -- then with 6 stressors
[global]
runtime=40
monitor=all

[randread_cmdline]
engine=fio
engine_module=cmdline
engine_module_parameter_base=--filename=/dev/nvme0n1 --direct=1 --rw=randread --bs=4k --ioengine=libaio --iodepth=256 --group_reporting --readonly
hosting_cpu_cores=all
hosting_cpu_cores_scaling=none
stressor_range=4,6
53 changes: 53 additions & 0 deletions documentation/fio.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# FIO

hwbench can use [fio](https://github.com/axboe/fio) to perform storage benchmarking.
The current implementation requires fio >= 3.19.

# Concept
Fio is operated in three(3) different modes by selecting the `engine_module` directive.


## Command line

When `engine_module=cmdline` is used, the content of `engine_module_parameter_base` will be passed directly to fio with some limitations.

The following fio keywords are automatically defined, or replaced if present, by hwbench :

- `--runtime`: set to match the exact duration of the current hwbench benchmark.
- `--time_based`: it's mandatory to have a benchmark lasting `runtime` seconds.
- `--output-format`: hwbench need the output to be set in `json+` for an easy integration.
- `--name`: hwbench will use the current job name to ensure its unique over the runs.
- `--numjobs`: defined by `stressor_range`, can be set as a unique value or a list of values. Each value will generate a new benchmark.
- `--write_{bw|lat|hist|iops}_logs`: hwbench will automatically collect the performance logs to let hwgraph doing time-based graphs.
- `--invalidate`: hwbench ensure that every benchmark will be done out of cache.

### Sample configuration file

The following job defines two benchmarks on the same device (nvme0n1).

The `randread_cmdline` job will create :
- `randread_cmdline_0` benchmark with ``numjobs=4`` extracted from `stressor_range` list
- `randread_cmdline_1` benchmark with ``numjobs=6`` extracted from `stressor_range` list

```
[randread_cmdline]
runtime=600
engine=fio
engine_module=cmdline
engine_module_parameter_base=--filename=/dev/nvme0n1 --direct=1 --rw=randread --bs=4k --ioengine=libaio --iodepth=256 --group_reporting --readonly
hosting_cpu_cores=all
hosting_cpu_cores_scaling=none
stressor_range=4,6
```

Please note the `hosting_cpu_cores` only selects a set of cores to pin fio. A possible usage would be using a list of cores with a `hosting_cpu_cores_scaling` to study the performance of the same storage device from different NUMA domains.

## External file execution
Hwbench execute an already existing fio job file.

Not yet implemented.

## Automatic job definition
Hwbench automatically creates jobs based on some hardware detection and profiles.

Not yet implemented.
7 changes: 4 additions & 3 deletions hwbench/bench/benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ def __init__(
self.job_number = job_number
self.enginemodule = enginemodule
self.parameters = parameters
self.parameters.benchmark = self
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a fan of unconditional set/get accessors in general, but since you added a set_benchmark, why not use it?


def get_enginemodule(self) -> EngineModuleBase:
return self.enginemodule
Expand Down Expand Up @@ -122,9 +123,9 @@ def pre_run(self):
cpu_location = ""
if p.get_pinned_cpu():
if isinstance(p.get_pinned_cpu(), (int, str)):
cpu_location = " on CPU {:3d}".format(p.get_pinned_cpu())
cpu_location = " pinned on CPU {:3d}".format(p.get_pinned_cpu())
elif isinstance(p.get_pinned_cpu(), list):
cpu_location = " on CPU [{}]".format(
cpu_location = " pinned on CPU [{}]".format(
h.cpu_list_to_range(p.get_pinned_cpu())
)
else:
Expand All @@ -139,7 +140,7 @@ def pre_run(self):
monitoring = "(M)"
print(
"[{}] {}/{}/{}{}: {:3d} stressor{} for {}s{}".format(
p.get_name(),
p.get_name_with_position(),
self.engine_module.get_engine().get_name(),
self.engine_module.get_name(),
p.get_engine_module_parameter(),
Expand Down
10 changes: 4 additions & 6 deletions hwbench/bench/benchmarks.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,11 +265,9 @@ def run(self):
)

# Save each benchmark result
results[
"{}_{}".format(
benchmark.get_parameters().get_name(), benchmark.get_job_number()
)
] = benchmark.run()
results[benchmark.get_parameters().get_name_with_position()] = (
benchmark.run()
)
return results

def dump(self):
Expand All @@ -278,7 +276,7 @@ def dump(self):
engine = bench.get_enginemodule().get_engine()
em = bench.get_enginemodule()
param = bench.get_parameters()
print(f"[{param.get_name()}_{bench.get_job_number()}]", file=f)
print(f"[{param.get_name_with_position()}]", file=f)
print(f"runtime={param.get_runtime()}", file=f)
print(f"monitoring={param.get_monitoring_config()}", file=f)
print(f"engine={engine.get_name()}", file=f)
Expand Down
11 changes: 11 additions & 0 deletions hwbench/bench/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ def __init__(
self.skip_method = skip_method
self.sync_start = sync_start

def get_benchmark(self):
return self.benchmark

def set_benchmark(self, benchmark):
self.benchmark = benchmark

def get_pinned_cpu(self):
if self.pinned_cpu == "none":
return ""
Expand All @@ -44,6 +50,11 @@ def get_pinned_cpu(self):
def get_name(self) -> str:
return self.job_name

def get_name_with_position(self) -> str:
if not self.benchmark:
return self.get_name()
return f"{self.get_name()}_{self.benchmark.get_job_number()}"

def get_engine_instances_count(self) -> int:
return self.engine_instances

Expand Down
46 changes: 46 additions & 0 deletions hwbench/bench/test_fio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from . import test_benchmarks_common as tbc


class TestFio(tbc.TestCommon):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.load_mocked_hardware(
cpucores="./hwbench/tests/parsing/cpu_cores/v2321",
cpuinfo="./hwbench/tests/parsing/cpu_info/v2321",
numa="./hwbench/tests/parsing/numa/8domainsllc",
)
self.load_benches("./hwbench/config/fio.conf")
self.parse_jobs_config()
self.QUADRANT0 = list(range(0, 16)) + list(range(64, 80))
self.QUADRANT1 = list(range(16, 32)) + list(range(80, 96))
self.ALL = list(range(0, 128))

def test_fio(self):
"""Check fio syntax."""
assert self.benches.count_benchmarks() == 2
assert self.benches.count_jobs() == 1
assert self.benches.runtime() == 80

for bench in self.benches.benchs:
self.assertIsNone(bench.validate_parameters())
bench.get_parameters().get_name() == "randread_cmdline"

bench_0 = self.get_bench_parameters(0)
assert (
bench_0.get_engine_module_parameter_base()
== "--bs=4k --direct=1 --filename=/dev/nvme0n1 --group_reporting \
--invalidate=1 --iodepth=256 --ioengine=libaio --log_avg_msec=20000 --name=randread_cmdline_0 \
--numjobs=4 --output-format=json+ --readonly --runtime=40 --rw=randread --time_based \
--write_bw_log=fio/randread_cmdline_0_bw.log --write_hist_log=fio/randread_cmdline_0_hist.log \
--write_iops_log=fio/randread_cmdline_0_iops.log --write_lat_log=fio/randread_cmdline_0_lat.log"
)

bench_1 = self.get_bench_parameters(1)
assert (
bench_1.get_engine_module_parameter_base()
== "--bs=4k --direct=1 --filename=/dev/nvme0n1 --group_reporting \
--invalidate=1 --iodepth=256 --ioengine=libaio --log_avg_msec=20000 --name=randread_cmdline_1 \
--numjobs=6 --output-format=json+ --readonly --runtime=40 --rw=randread --time_based \
--write_bw_log=fio/randread_cmdline_1_bw.log --write_hist_log=fio/randread_cmdline_1_hist.log \
--write_iops_log=fio/randread_cmdline_1_iops.log --write_lat_log=fio/randread_cmdline_1_lat.log"
)
18 changes: 18 additions & 0 deletions hwbench/config/fio.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# This configuration will :
# - test /dev/nvme0n1 in 4k randread for 40 seconds
# -- first with 4 stressors
# -- then with 6 stressors
#
# As runtime is set to 30s by the user, it should be replaced by runtime=40 defined by hardware bench
[global]
runtime=40
monitor=all

[randread_cmdline]
engine=fio
engine_module=cmdline
engine_module_parameter_base=--filename=/dev/nvme0n1 --direct=1 --rw=randread --bs=4k --ioengine=libaio --iodepth=256 --group_reporting --readonly --runtime=30 --numjobs=10 --name=plop
hosting_cpu_cores=all
hosting_cpu_cores_scaling=none
stressor_range=4,6

26 changes: 26 additions & 0 deletions hwbench/config/test_parse_fio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from unittest.mock import patch
from ..environment.mock import MockHardware
from ..bench import test_benchmarks_common as tbc


class TestParseConfig(tbc.TestCommon):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.hw = MockHardware()
self.load_benches("./hwbench/config/fio.conf")

def test_sections_name(self):
"""Check if sections names are properly detected."""
sections = self.get_jobs_config().get_sections()
assert sections == [
"randread_cmdline",
]

def test_keywords(self):
"""Check if all keywords are valid."""
try:
with patch("hwbench.utils.helpers.is_binary_available") as iba:
iba.return_value = True
self.get_jobs_config().validate_sections()
except Exception as exc:
assert False, f"'validate_sections' detected a syntax error {exc}"
Loading