Skip to content

Commit 17c88ba

Browse files
committed
Update fio benchmark to output as JSON and include latency and IOPs in report
1 parent af06507 commit 17c88ba

File tree

3 files changed

+127
-25
lines changed

3 files changed

+127
-25
lines changed

internal/report/table_defs.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2363,13 +2363,15 @@ func numaBenchmarkTableValues(outputs map[string]script.ScriptOutput) []Field {
23632363
}
23642364

23652365
func storageBenchmarkTableValues(outputs map[string]script.ScriptOutput) []Field {
2366-
readBW, writeBW := storagePerfFromOutput(outputs)
2367-
if readBW == "" && writeBW == "" {
2366+
readLat, readBw, writeLat, writeBw := storagePerfFromOutput(outputs)
2367+
if readLat == "" && readBw == "" && writeLat == "" && writeBw == "" {
23682368
return []Field{}
23692369
}
23702370
return []Field{
2371-
{Name: "Single-Thread Read Bandwidth", Values: []string{readBW}},
2372-
{Name: "Single-Thread Write Bandwidth", Values: []string{writeBW}},
2371+
{Name: "Single-Thread Read Latency (ns)", Values: []string{readLat}},
2372+
{Name: "Single-Thread Read Bandwidth (MiB/s)", Values: []string{readBw}},
2373+
{Name: "Single-Thread Write Latency (ns)", Values: []string{writeLat}},
2374+
{Name: "Single-Thread Write Bandwidth (MiB/s)", Values: []string{writeBw}},
23732375
}
23742376
}
23752377

internal/report/table_helpers_benchmarking.go

Lines changed: 119 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,100 @@ package report
44
// SPDX-License-Identifier: BSD-3-Clause
55

66
import (
7+
"encoding/json"
78
"fmt"
89
"log/slog"
910
"perfspect/internal/script"
1011
"perfspect/internal/util"
11-
"regexp"
1212
"strconv"
1313
"strings"
1414
)
1515

16+
// fioOutput is the top-level struct for the FIO JSON report.
17+
// ref: https://fio.readthedocs.io/en/latest/fio_doc.html#json-output
18+
type fioOutput struct {
19+
FioVersion string `json:"fio version"`
20+
Timestamp int64 `json:"timestamp"`
21+
TimestampMs int64 `json:"timestamp_ms"`
22+
Time string `json:"time"`
23+
Jobs []fioJob `json:"jobs"`
24+
}
25+
26+
// Job represents a single job's results within the FIO report.
27+
type fioJob struct {
28+
Jobname string `json:"jobname"`
29+
Groupid int `json:"groupid"`
30+
JobStart int64 `json:"job_start"`
31+
Error int `json:"error"`
32+
Eta int `json:"eta"`
33+
Elapsed int `json:"elapsed"`
34+
Read fioIOStats `json:"read"`
35+
Write fioIOStats `json:"write"`
36+
Trim fioIOStats `json:"trim"`
37+
JobRuntime int `json:"job_runtime"`
38+
UsrCPU float64 `json:"usr_cpu"`
39+
SysCPU float64 `json:"sys_cpu"`
40+
Ctx int `json:"ctx"`
41+
Majf int `json:"majf"`
42+
Minf int `json:"minf"`
43+
IodepthLevel map[string]float64 `json:"iodepth_level"`
44+
IodepthSubmit map[string]float64 `json:"iodepth_submit"`
45+
IodepthComplete map[string]float64 `json:"iodepth_complete"`
46+
LatencyNs map[string]float64 `json:"latency_ns"`
47+
LatencyUs map[string]float64 `json:"latency_us"`
48+
LatencyMs map[string]float64 `json:"latency_ms"`
49+
LatencyDepth int `json:"latency_depth"`
50+
LatencyTarget int `json:"latency_target"`
51+
LatencyPercentile float64 `json:"latency_percentile"`
52+
LatencyWindow int `json:"latency_window"`
53+
}
54+
55+
// IOStats holds the detailed I/O statistics for read, write, or trim operations.
56+
type fioIOStats struct {
57+
IoBytes int64 `json:"io_bytes"`
58+
IoKbytes int64 `json:"io_kbytes"`
59+
BwBytes int64 `json:"bw_bytes"`
60+
Bw int64 `json:"bw"`
61+
Iops float64 `json:"iops"`
62+
Runtime int `json:"runtime"`
63+
TotalIos int `json:"total_ios"`
64+
ShortIos int `json:"short_ios"`
65+
DropIos int `json:"drop_ios"`
66+
SlatNs fioLatencyStats `json:"slat_ns"`
67+
ClatNs fioLatencyStatsPercentiles `json:"clat_ns"`
68+
LatNs fioLatencyStats `json:"lat_ns"`
69+
BwMin int `json:"bw_min"`
70+
BwMax int `json:"bw_max"`
71+
BwAgg float64 `json:"bw_agg"`
72+
BwMean float64 `json:"bw_mean"`
73+
BwDev float64 `json:"bw_dev"`
74+
BwSamples int `json:"bw_samples"`
75+
IopsMin int `json:"iops_min"`
76+
IopsMax int `json:"iops_max"`
77+
IopsMean float64 `json:"iops_mean"`
78+
IopsStddev float64 `json:"iops_stddev"`
79+
IopsSamples int `json:"iops_samples"`
80+
}
81+
82+
// fioLatencyStats holds basic latency metrics.
83+
type fioLatencyStats struct {
84+
Min int64 `json:"min"`
85+
Max int64 `json:"max"`
86+
Mean float64 `json:"mean"`
87+
Stddev float64 `json:"stddev"`
88+
N int `json:"N"`
89+
}
90+
91+
// LatencyStatsPercentiles holds latency metrics including percentiles.
92+
type fioLatencyStatsPercentiles struct {
93+
Min int64 `json:"min"`
94+
Max int64 `json:"max"`
95+
Mean float64 `json:"mean"`
96+
Stddev float64 `json:"stddev"`
97+
N int `json:"N"`
98+
Percentile map[string]int64 `json:"percentile"`
99+
}
100+
16101
func cpuSpeedFromOutput(outputs map[string]script.ScriptOutput) string {
17102
var vals []float64
18103
for line := range strings.SplitSeq(strings.TrimSpace(outputs[script.SpeedBenchmarkScriptName].Stdout), "\n") {
@@ -35,26 +120,40 @@ func cpuSpeedFromOutput(outputs map[string]script.ScriptOutput) string {
35120
return fmt.Sprintf("%.0f", util.GeoMean(vals))
36121
}
37122

38-
func storagePerfFromOutput(outputs map[string]script.ScriptOutput) (readBW, writeBW string) {
39-
// fio output format:
40-
// READ: bw=140MiB/s (146MB/s), 140MiB/s-140MiB/s (146MB/s-146MB/s), io=16.4GiB (17.6GB), run=120004-120004msec
41-
// WRITE: bw=139MiB/s (146MB/s), 139MiB/s-139MiB/s (146MB/s-146MB/s), io=16.3GiB (17.5GB), run=120004-120004msec
42-
re := regexp.MustCompile(` bw=(\d+[.]?[\d]*\w+\/s)`)
43-
for line := range strings.SplitSeq(strings.TrimSpace(outputs[script.StorageBenchmarkScriptName].Stdout), "\n") {
44-
if strings.Contains(line, "READ: bw=") {
45-
matches := re.FindStringSubmatch(line)
46-
if len(matches) != 0 {
47-
readBW = matches[1]
48-
}
49-
} else if strings.Contains(line, "WRITE: bw=") {
50-
matches := re.FindStringSubmatch(line)
51-
if len(matches) != 0 {
52-
writeBW = matches[1]
53-
}
54-
} else if strings.Contains(line, "ERROR: ") {
55-
slog.Error("failed to run storage benchmark", slog.String("line", line))
56-
}
123+
func storagePerfFromOutput(outputs map[string]script.ScriptOutput) (readLat, readBw, writeLat, writeBw string) {
124+
output := outputs[script.StorageBenchmarkScriptName].Stdout
125+
slog.Debug("storage benchmark output", slog.String("output", output))
126+
127+
i := strings.Index(output, "{\n \"fio version\"")
128+
if i >= 0 {
129+
output = output[i:]
130+
} else {
131+
slog.Error("Unable to find fio output", slog.String("output", output))
132+
return
133+
}
134+
if strings.Contains(output, "ERROR:") {
135+
slog.Error("failed to run storage benchmark", slog.String("output", output))
136+
return
57137
}
138+
139+
slog.Debug("parsing storage benchmark output")
140+
var fioData fioOutput
141+
if err := json.Unmarshal([]byte(output), &fioData); err != nil {
142+
slog.Error("Error unmarshalling JSON", slog.String("error", err.Error()))
143+
return
144+
}
145+
if len(fioData.Jobs) > 0 {
146+
slog.Debug("jobs found in storage benchmark output")
147+
job := fioData.Jobs[0]
148+
readBw = fmt.Sprintf("%d", job.Read.Bw/1024)
149+
readLat = fmt.Sprintf("%.0f", job.Read.LatNs.Mean)
150+
writeBw = fmt.Sprintf("%d", job.Write.Bw/1024)
151+
writeLat = fmt.Sprintf("%.0f", job.Write.LatNs.Mean)
152+
} else {
153+
slog.Error("No jobs found in storage benchmark output", slog.String("output", output))
154+
}
155+
156+
slog.Debug("storage benchmark output", slog.String("readLat", readLat), slog.String("readBw", readBw), slog.String("writeLat", writeLat), slog.String("writeBw", writeBw))
58157
return
59158
}
60159

internal/script/script_defs.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1176,7 +1176,8 @@ fio --name=bandwidth --directory=$test_dir --numjobs=$numjobs \
11761176
--size="$file_size_g"G --time_based --runtime=$runtime --ramp_time=$ramp_time --ioengine=$ioengine \
11771177
--direct=1 --verify=0 --bs=1M --iodepth=1 --rw=rw \
11781178
--group_reporting=1 --iodepth_batch_submit=64 \
1179-
--iodepth_batch_complete_max=64
1179+
--iodepth_batch_complete_max=64 \
1180+
--output-format=json
11801181
rm -rf $test_dir
11811182
`,
11821183
Superuser: true,

0 commit comments

Comments
 (0)