blob: 9dffb4961dcb5666887336650f30a9c37be1a3be [file] [log] [blame]
koder aka kdanilovf90de852017-01-20 18:12:27 +02001import copy
2from collections import OrderedDict
kdanylov aka koder13e58452018-07-15 02:51:51 +03003from typing import Optional, Iterator, Union, Dict, Tuple, Any, cast
koder aka kdanilovf90de852017-01-20 18:12:27 +02004
kdanylov aka koder026e5f22017-05-15 01:04:39 +03005from cephlib.units import ssize2b, b2ssize
koder aka kdanilovf90de852017-01-20 18:12:27 +02006
kdanylov aka koder13e58452018-07-15 02:51:51 +03007from ..job import JobConfig, JobParams, Var
koder aka kdanilovf90de852017-01-20 18:12:27 +02008
9
10def is_fio_opt_true(vl: Union[str, int]) -> bool:
11 return str(vl).lower() in ['1', 'true', 't', 'yes', 'y']
12
13
14class FioJobParams(JobParams):
15 """Class contains all parameters, which significantly affects fio results.
16
17 oper - operation type - read/write/randread/...
18 sync_mode - direct/sync/async/direct+sync
19 bsize - block size in KiB
20 qd - IO queue depth,
21 thcount - thread count,
22 write_perc - write perc for mixed(read+write) loads
23
24 Like block size or operation type, but not file name or file size.
25 Can be used as key in dictionary.
26 """
27
28 sync2long = {'x': "sync direct",
29 's': "sync",
30 'd': "direct",
31 'a': "buffered"}
32
33 @property
34 def sync_mode_long(self) -> str:
35 return self.sync2long[self['sync_mode']]
36
37 @property
38 def summary(self) -> str:
39 """Test short summary, used mostly for file names and short image description"""
kdanylov aka koder13e58452018-07-15 02:51:51 +030040 res = f"{self['oper_short']}{self['sync_mode']}{self['bsize']}"
koder aka kdanilovf90de852017-01-20 18:12:27 +020041 if self['qd'] is not None:
42 res += "_qd" + str(self['qd'])
43 if self['thcount'] not in (1, None):
44 res += "th" + str(self['thcount'])
45 if self['write_perc'] is not None:
46 res += "wr" + str(self['write_perc'])
47 return res
48
49 @property
50 def long_summary(self) -> str:
51 """Readable long summary for management and deployment engineers"""
kdanylov aka koder13e58452018-07-15 02:51:51 +030052 res = f"{self['oper']}, {self.sync_mode_long}, block size {b2ssize(self['bsize'] * 1024)}B"
koder aka kdanilovf90de852017-01-20 18:12:27 +020053 if self['qd'] is not None:
koder aka kdanilova732a602017-02-01 20:29:56 +020054 res += ", QD = " + str(self['qd'])
koder aka kdanilovf90de852017-01-20 18:12:27 +020055 if self['thcount'] not in (1, None):
kdanylov aka koder13e58452018-07-15 02:51:51 +030056 res += f", threads={self['thcount']}"
koder aka kdanilovf90de852017-01-20 18:12:27 +020057 if self['write_perc'] is not None:
kdanylov aka koder13e58452018-07-15 02:51:51 +030058 res += f", fwrite_perc={self['write_perc']}%"
koder aka kdanilovf90de852017-01-20 18:12:27 +020059 return res
60
koder aka kdanilova732a602017-02-01 20:29:56 +020061 def copy(self, **kwargs: Dict[str, Any]) -> 'FioJobParams':
62 np = self.params.copy()
63 np.update(kwargs)
64 return self.__class__(**np)
65
66 @property
67 def char_tpl(self) -> Tuple[Union[str, int], ...]:
68 mint = lambda x: -10000000000 if x is None else int(x)
69 return self['oper'], mint(self['bsize']), self['sync_mode'], \
70 mint(self['thcount']), mint(self['qd']), mint(self['write_perc'])
71
koder aka kdanilovf90de852017-01-20 18:12:27 +020072
73class FioJobConfig(JobConfig):
74 """Fio job configuration"""
75 ds2mode = {(True, True): 'x',
76 (True, False): 's',
77 (False, True): 'd',
78 (False, False): 'a'}
79
80 op_type2short = {"randread": "rr",
81 "randwrite": "rw",
82 "read": "sr",
83 "write": "sw",
84 "randrw": "rx"}
85
86 def __init__(self, name: str, idx: int) -> None:
87 JobConfig.__init__(self, idx)
88 self.name = name
kdanylov aka koder13e58452018-07-15 02:51:51 +030089 self._sync_mode: Optional[str] = None
90 self._params: Optional[Dict[str, Any]] = None
koder aka kdanilovf90de852017-01-20 18:12:27 +020091
92 # ------------- BASIC PROPERTIES -----------------------------------------------------------------------------------
93
94 @property
95 def write_perc(self) -> Optional[int]:
96 try:
kdanylov aka koder13e58452018-07-15 02:51:51 +030097 return int(self.vals["rwmixwrite"]) # type: ignore
koder aka kdanilovf90de852017-01-20 18:12:27 +020098 except (KeyError, TypeError):
99 try:
kdanylov aka koder13e58452018-07-15 02:51:51 +0300100 return 100 - int(self.vals["rwmixread"]) # type: ignore
koder aka kdanilovf90de852017-01-20 18:12:27 +0200101 except (KeyError, TypeError):
102 return None
103
104 @property
105 def qd(self) -> int:
kdanylov aka koder13e58452018-07-15 02:51:51 +0300106 return int(self.vals.get('iodepth', '1')) # type: ignore
koder aka kdanilovf90de852017-01-20 18:12:27 +0200107
108 @property
109 def bsize(self) -> int:
110 bsize = ssize2b(self.vals['blocksize'])
111 assert bsize % 1024 == 0
112 return bsize // 1024
113
114 @property
115 def oper(self) -> str:
kdanylov aka koder84de1e42017-05-22 14:00:07 +0300116 vl = self.vals['rw']
kdanylov aka koder13e58452018-07-15 02:51:51 +0300117 return vl if ':' not in vl else vl.split(":")[0] # type: ignore
koder aka kdanilovf90de852017-01-20 18:12:27 +0200118
119 @property
120 def op_type_short(self) -> str:
kdanylov aka koder84de1e42017-05-22 14:00:07 +0300121 return self.op_type2short[self.oper]
koder aka kdanilovf90de852017-01-20 18:12:27 +0200122
123 @property
124 def thcount(self) -> int:
kdanylov aka koder13e58452018-07-15 02:51:51 +0300125 return int(self.vals.get('numjobs', 1)) # type: ignore
koder aka kdanilovf90de852017-01-20 18:12:27 +0200126
127 @property
128 def sync_mode(self) -> str:
129 if self._sync_mode is None:
kdanylov aka koder13e58452018-07-15 02:51:51 +0300130 direct = is_fio_opt_true(self.vals.get('direct', '0')) # type: ignore
131 direct = direct or not is_fio_opt_true(self.vals.get('buffered', '0')) # type: ignore
132 sync = is_fio_opt_true(self.vals.get('sync', '0')) # type: ignore
koder aka kdanilovf90de852017-01-20 18:12:27 +0200133 self._sync_mode = self.ds2mode[(sync, direct)]
134 return cast(str, self._sync_mode)
135
136 # ----------- COMPLEX PROPERTIES -----------------------------------------------------------------------------------
137
138 @property
139 def params(self) -> JobParams:
140 if self._params is None:
141 self._params = dict(oper=self.oper,
kdanylov aka koder150b2192017-04-01 16:53:01 +0300142 oper_short=self.op_type_short,
koder aka kdanilovf90de852017-01-20 18:12:27 +0200143 sync_mode=self.sync_mode,
144 bsize=self.bsize,
145 qd=self.qd,
146 thcount=self.thcount,
147 write_perc=self.write_perc)
148 return cast(JobParams, FioJobParams(**cast(Dict[str, Any], self._params)))
149
150 # ------------------------------------------------------------------------------------------------------------------
151
152 def __eq__(self, o: object) -> bool:
153 if not isinstance(o, FioJobConfig):
154 return False
kdanylov aka koder150b2192017-04-01 16:53:01 +0300155 return dict(self.vals) == dict(cast(FioJobConfig, o).vals)
koder aka kdanilovf90de852017-01-20 18:12:27 +0200156
157 def copy(self) -> 'FioJobConfig':
158 return copy.deepcopy(self)
159
160 def required_vars(self) -> Iterator[Tuple[str, Var]]:
161 for name, val in self.vals.items():
162 if isinstance(val, Var):
163 yield name, val
164
165 def is_free(self) -> bool:
166 return len(list(self.required_vars())) == 0
167
168 def __str__(self) -> str:
koder aka kdanilova732a602017-02-01 20:29:56 +0200169 res = "[{0}]\n".format(self.summary)
koder aka kdanilovf90de852017-01-20 18:12:27 +0200170
171 for name, val in self.vals.items():
172 if name.startswith('_') or name == name.upper():
173 continue
174 if isinstance(val, Var):
175 res += "{0}={{{1}}}\n".format(name, val.name)
176 else:
177 res += "{0}={1}\n".format(name, val)
178
179 return res
180
181 def __repr__(self) -> str:
182 return str(self)
183
184 def raw(self) -> Dict[str, Any]:
185 res = super().raw()
186 res['vals'] = list(map(list, self.vals.items()))
187 return res
188
189 @classmethod
190 def fromraw(cls, data: Dict[str, Any]) -> 'FioJobConfig':
191 data['vals'] = OrderedDict(data['vals'])
koder aka kdanilova732a602017-02-01 20:29:56 +0200192 data['_sync_mode'] = None
193 data['_params'] = None
koder aka kdanilovf90de852017-01-20 18:12:27 +0200194 return cast(FioJobConfig, super().fromraw(data))