-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathfio.py
More file actions
328 lines (305 loc) · 10.1 KB
/
Copy pathfio.py
File metadata and controls
328 lines (305 loc) · 10.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
import json
import typing as t
from pydantic import Field
from kube_custom_resource import schema
from ...config import settings
from ...errors import PodLogFormatError, PodResultsIncompleteError
from ...utils import format_amount
from . import base
class FioRW(str, schema.Enum):
"""
Enumeration of supported Fio rw modes.
"""
READ = "read"
WRITE = "write"
RANDREAD = "randread"
RANDWRITE = "randwrite"
RW_READWRITE = "rw,readwrite"
RANDRW = "randrw"
class FioDirect(int, schema.Enum):
"""
Enumeration of supported Fio direct Bools.
"""
TRUE = 1
FALSE = 0
class FioIOEngine(str, schema.Enum):
"""
Enumeration of supported Fio ioengines.
"""
LIBAIO = "libaio"
class FioSpec(base.BenchmarkSpec):
"""
Defines the parameters for the Fio benchmark.
"""
image: schema.constr(min_length = 1) = Field(
f"{settings.default_image_prefix}fio:{settings.default_image_tag}",
description = "The image to use for the benchmark."
)
image_pull_policy: base.ImagePullPolicy = Field(
base.ImagePullPolicy.IF_NOT_PRESENT,
description = "The pull policy for the image."
)
fio_port: schema.conint(ge = 1024) = Field(
8765,
description = "The port that the Fio sever listens on."
)
volume_claim_template: schema.Dict[str, schema.Any] = Field(
default_factory = dict,
description = "The template that describes the PVC to mount on workers."
)
num_workers: schema.conint(gt = 0) = Field(
1,
description = "The number of Fio workers."
)
# Fio config options
rw: FioRW = Field(
FioRW.READ,
description = "The value of the Fio rw config option."
)
bs: schema.constr(pattern = "\\d+(K|M|G|T|P)?") = Field(
"4M",
description = "The value of the Fio bs config option."
)
iodepth: schema.conint(gt = 0) = Field(
1,
description = "The value of the Fio iodepth config option."
)
ioengine: schema.conint(gt = 0) = Field(
1,
description = "The value of the Fio iodepth config option."
)
nrfiles: schema.conint(gt = 0) = Field(
1,
description = "The value of the Fio nrfiles config option."
)
rwmixread: schema.conint(ge = 0, le = 100) = Field(
50,
description = "The value of the Fio rwmixread config option."
)
percentage_random: schema.conint(ge = 0, le = 100) = Field(
100,
description = "The value of the Fio percentage_random config option."
)
direct: FioDirect = Field(
FioDirect.TRUE,
description = "The value of the Fio direct config option."
)
ioengine: FioIOEngine = Field(
FioIOEngine.LIBAIO,
description = "The value of the Fio ioengine config option."
)
runtime: schema.constr(pattern = "\\d+(D|H|M|s|ms|us)?") = Field(
"30s",
description = "The value of the Fio runtime config option."
)
num_jobs: schema.conint(gt = 0) = Field(
1,
description = "The value of the Fio numjobs config option."
)
size: schema.constr(pattern = "\\d+(K|M|G|T|P)?") = Field(
"10G",
description = "The value of the Fio size config option."
)
thread: bool = Field(
False,
description = "Fio use threads config option."
)
class FioResult(schema.BaseModel):
"""
Represents an individual Fio result.
"""
read_bw: schema.confloat(ge = 0) = Field(
...,
description = "The aggregate read bandwidth."
)
read_iops: schema.confloat(ge = 0) = Field(
...,
description = "The aggregate read IOPS."
)
read_lat_ns_mean: schema.confloat(ge = 0) = Field(
...,
description = "The aggregate mean read latency."
)
read_lat_ns_stddev: schema.confloat(ge = 0) = Field(
...,
description = "The aggregate read latency standard deviation."
)
write_bw: schema.confloat(ge = 0) = Field(
...,
description = "The aggregate write bandwidth."
)
write_iops: schema.confloat(ge = 0) = Field(
...,
description = "The aggregate write IOPS."
)
write_lat_ns_mean: schema.confloat(ge = 0) = Field(
...,
description = "The aggregate mean read latency."
)
write_lat_ns_stddev: schema.confloat(ge = 0) = Field(
...,
description = "The aggregate read latency standard deviation."
)
class FioStatus(base.BenchmarkStatus):
"""
Represents the status of the Fio benchmark.
"""
result: schema.Optional[FioResult] = Field(
None,
description = "The result of the benchmark."
)
read_bw_result: schema.Optional[schema.IntOrString] = Field(
None,
description = "The summary result for read bw, used for display."
)
write_bw_result: schema.Optional[schema.IntOrString] = Field(
None,
description = "The summary result for write bw, used for display."
)
read_iops_result: schema.Optional[schema.confloat(ge = 0)] = Field(
None,
description = "The summary result for read IOPs, used for display."
)
write_iops_result: schema.Optional[schema.confloat(ge = 0)] = Field(
None,
description = "The summary result for write IOPs, used for display."
)
master_pod: schema.Optional[base.PodInfo] = Field(
None,
description = "Pod information for the Fio master pod."
)
worker_pods: schema.Dict[str, base.PodInfo] = Field(
default_factory = dict,
description = "Pod information for the worker pods, indexed by pod name."
)
client_log: schema.Optional[schema.constr(min_length = 1)] = Field(
None,
description = "The raw pod log of the client pod."
)
class Fio(
base.Benchmark,
subresources = {"status": {}},
printer_columns = [
{
"name": "Num Jobs",
"type": "integer",
"jsonPath": ".spec.numJobs",
},
{
"name": "Num Workers",
"type": "integer",
"jsonPath": ".spec.numWorkers",
},
{
"name": "RW",
"type": "string",
"jsonPath": ".spec.rw",
},
{
"name": "BS",
"type": "string",
"jsonPath": ".spec.bs",
},
{
"name": "Pct Read",
"type": "string",
"jsonPath": ".spec.rwmixread",
},
{
"name": "Pct Random",
"type": "string",
"jsonPath": ".spec.percentageRandom",
},
{
"name": "Status",
"type": "string",
"jsonPath": ".status.phase",
},
{
"name": "Started",
"type": "date",
"jsonPath": ".status.startedAt",
},
{
"name": "Finished",
"type": "date",
"jsonPath": ".status.finishedAt",
},
{
"name": "Read Bandwidth",
"type": "string",
"jsonPath": ".status.readBwResult",
},
{
"name": "Read IOPS",
"type": "number",
"jsonPath": ".status.readIopsResult",
},
{
"name": "Write Bandwidth",
"type": "string",
"jsonPath": ".status.writeBwResult",
},
{
"name": "Write IOPS",
"type": "number",
"jsonPath": ".status.writeIopsResult",
}
]
):
"""
Custom resource for running a fio benchmark.
"""
spec: FioSpec = Field(
...,
description = "The parameters for the benchmark."
)
status: FioStatus = Field(
default_factory = FioStatus,
description = "The status of the benchmark."
)
async def pod_modified(
self,
pod: t.Dict[str, t.Any],
fetch_pod_log: t.Callable[[], t.Awaitable[str]]
):
pod_phase = pod.get("status", {}).get("phase", "Unknown")
pod_component = pod["metadata"]["labels"][settings.component_label]
if pod_phase == "Running":
if pod_component == "master":
self.status.master_pod = base.PodInfo.from_pod(pod)
else:
self.status.worker_pods[pod["metadata"]["name"]] = base.PodInfo.from_pod(pod)
elif pod_phase == "Succeeded":
self.status.client_log = await fetch_pod_log()
def summarise(self):
# If the client log has not yet been recorded, bail
if not self.status.client_log:
raise PodResultsIncompleteError("master pod has not recorded a result yet")
# Compute the result from the client log
try:
fio_json = json.loads(self.status.client_log)
except:
raise PodLogFormatError("pod log is not of the expected format")
if len(fio_json['client_stats']) == 1:
# Single worker, single process doesn't have an
# 'All clients' log section
aggregate_data = fio_json['client_stats'][0]
else:
aggregate_data = [i for i in fio_json['client_stats'] if i['jobname'] == 'All clients'][0]
# Format results nicely for printing
self.status.read_bw_result = "{0} {1}B/s".format(*format_amount(aggregate_data['read']['bw'], "K"))
self.status.write_bw_result = "{0} {1}B/s".format(*format_amount(aggregate_data['write']['bw'], "K"))
self.status.write_iops_result = round(aggregate_data['write']['iops'], 2)
self.status.read_iops_result = round(aggregate_data['read']['iops'], 2)
# Use unformatted data for full fioresult
self.status.result = FioResult(
read_bw = aggregate_data['read']['bw'],
read_iops = aggregate_data['read']['iops'],
read_lat_ns_mean = aggregate_data['read']['lat_ns']['mean'],
read_lat_ns_stddev = aggregate_data['read']['lat_ns']['stddev'],
write_bw = aggregate_data['write']['bw'],
write_iops = aggregate_data['write']['iops'],
write_lat_ns_mean = aggregate_data['write']['lat_ns']['mean'],
write_lat_ns_stddev = aggregate_data['write']['lat_ns']['stddev']
)