blob: 6676895372152b1d1529c9386c274d4ef6fe5cca [file] [log] [blame]
koder aka kdanilovf90de852017-01-20 18:12:27 +02001import copy
2from collections import OrderedDict
3from typing import Optional, Iterator, Union, Dict, Tuple, NamedTuple, Any, cast
4
kdanylov aka koder026e5f22017-05-15 01:04:39 +03005from cephlib.units import ssize2b, b2ssize
koder aka kdanilovf90de852017-01-20 18:12:27 +02006
koder aka kdanilovf90de852017-01-20 18:12:27 +02007from ..job import JobConfig, JobParams
8
9
10Var = NamedTuple('Var', [('name', str)])
11
12
13def is_fio_opt_true(vl: Union[str, int]) -> bool:
14 return str(vl).lower() in ['1', 'true', 't', 'yes', 'y']
15
16
17class FioJobParams(JobParams):
18 """Class contains all parameters, which significantly affects fio results.
19
20 oper - operation type - read/write/randread/...
21 sync_mode - direct/sync/async/direct+sync
22 bsize - block size in KiB
23 qd - IO queue depth,
24 thcount - thread count,
25 write_perc - write perc for mixed(read+write) loads
26
27 Like block size or operation type, but not file name or file size.
28 Can be used as key in dictionary.
29 """
30
31 sync2long = {'x': "sync direct",
32 's': "sync",
33 'd': "direct",
34 'a': "buffered"}
35
36 @property
37 def sync_mode_long(self) -> str:
38 return self.sync2long[self['sync_mode']]
39
40 @property
41 def summary(self) -> str:
42 """Test short summary, used mostly for file names and short image description"""
kdanylov aka koder150b2192017-04-01 16:53:01 +030043 res = "{0[oper_short]}{0[sync_mode]}{0[bsize]}".format(self)
koder aka kdanilovf90de852017-01-20 18:12:27 +020044 if self['qd'] is not None:
45 res += "_qd" + str(self['qd'])
46 if self['thcount'] not in (1, None):
47 res += "th" + str(self['thcount'])
48 if self['write_perc'] is not None:
49 res += "wr" + str(self['write_perc'])
50 return res
51
52 @property
53 def long_summary(self) -> str:
54 """Readable long summary for management and deployment engineers"""
koder aka kdanilova732a602017-02-01 20:29:56 +020055 res = "{0[oper]}, {0.sync_mode_long}, block size {1}B".format(self, b2ssize(self['bsize'] * 1024))
koder aka kdanilovf90de852017-01-20 18:12:27 +020056 if self['qd'] is not None:
koder aka kdanilova732a602017-02-01 20:29:56 +020057 res += ", QD = " + str(self['qd'])
koder aka kdanilovf90de852017-01-20 18:12:27 +020058 if self['thcount'] not in (1, None):
koder aka kdanilova732a602017-02-01 20:29:56 +020059 res += ", threads={0[thcount]}".format(self)
koder aka kdanilovf90de852017-01-20 18:12:27 +020060 if self['write_perc'] is not None:
koder aka kdanilova732a602017-02-01 20:29:56 +020061 res += ", write_perc={0[write_perc]}%".format(self)
koder aka kdanilovf90de852017-01-20 18:12:27 +020062 return res
63
koder aka kdanilova732a602017-02-01 20:29:56 +020064 def copy(self, **kwargs: Dict[str, Any]) -> 'FioJobParams':
65 np = self.params.copy()
66 np.update(kwargs)
67 return self.__class__(**np)
68
69 @property
70 def char_tpl(self) -> Tuple[Union[str, int], ...]:
71 mint = lambda x: -10000000000 if x is None else int(x)
72 return self['oper'], mint(self['bsize']), self['sync_mode'], \
73 mint(self['thcount']), mint(self['qd']), mint(self['write_perc'])
74
koder aka kdanilovf90de852017-01-20 18:12:27 +020075
76class FioJobConfig(JobConfig):
77 """Fio job configuration"""
78 ds2mode = {(True, True): 'x',
79 (True, False): 's',
80 (False, True): 'd',
81 (False, False): 'a'}
82
83 op_type2short = {"randread": "rr",
84 "randwrite": "rw",
85 "read": "sr",
86 "write": "sw",
87 "randrw": "rx"}
88
89 def __init__(self, name: str, idx: int) -> None:
90 JobConfig.__init__(self, idx)
91 self.name = name
92 self._sync_mode = None # type: Optional[str]
93 self._params = None # type: Optional[Dict[str, Any]]
94
95 # ------------- BASIC PROPERTIES -----------------------------------------------------------------------------------
96
97 @property
98 def write_perc(self) -> Optional[int]:
99 try:
100 return int(self.vals["rwmixwrite"])
101 except (KeyError, TypeError):
102 try:
103 return 100 - int(self.vals["rwmixread"])
104 except (KeyError, TypeError):
105 return None
106
107 @property
108 def qd(self) -> int:
kdanylov aka koder150b2192017-04-01 16:53:01 +0300109 return int(self.vals.get('iodepth', '1'))
koder aka kdanilovf90de852017-01-20 18:12:27 +0200110
111 @property
112 def bsize(self) -> int:
113 bsize = ssize2b(self.vals['blocksize'])
114 assert bsize % 1024 == 0
115 return bsize // 1024
116
117 @property
118 def oper(self) -> str:
kdanylov aka koder84de1e42017-05-22 14:00:07 +0300119 vl = self.vals['rw']
120 return vl if ':' not in vl else vl.split(":")[0]
koder aka kdanilovf90de852017-01-20 18:12:27 +0200121
122 @property
123 def op_type_short(self) -> str:
kdanylov aka koder84de1e42017-05-22 14:00:07 +0300124 return self.op_type2short[self.oper]
koder aka kdanilovf90de852017-01-20 18:12:27 +0200125
126 @property
127 def thcount(self) -> int:
128 return int(self.vals.get('numjobs', 1))
129
130 @property
131 def sync_mode(self) -> str:
132 if self._sync_mode is None:
133 direct = is_fio_opt_true(self.vals.get('direct', '0')) or \
134 not is_fio_opt_true(self.vals.get('buffered', '0'))
135 sync = is_fio_opt_true(self.vals.get('sync', '0'))
136 self._sync_mode = self.ds2mode[(sync, direct)]
137 return cast(str, self._sync_mode)
138
139 # ----------- COMPLEX PROPERTIES -----------------------------------------------------------------------------------
140
141 @property
142 def params(self) -> JobParams:
143 if self._params is None:
144 self._params = dict(oper=self.oper,
kdanylov aka koder150b2192017-04-01 16:53:01 +0300145 oper_short=self.op_type_short,
koder aka kdanilovf90de852017-01-20 18:12:27 +0200146 sync_mode=self.sync_mode,
147 bsize=self.bsize,
148 qd=self.qd,
149 thcount=self.thcount,
150 write_perc=self.write_perc)
151 return cast(JobParams, FioJobParams(**cast(Dict[str, Any], self._params)))
152
153 # ------------------------------------------------------------------------------------------------------------------
154
155 def __eq__(self, o: object) -> bool:
156 if not isinstance(o, FioJobConfig):
157 return False
kdanylov aka koder150b2192017-04-01 16:53:01 +0300158 return dict(self.vals) == dict(cast(FioJobConfig, o).vals)
koder aka kdanilovf90de852017-01-20 18:12:27 +0200159
160 def copy(self) -> 'FioJobConfig':
161 return copy.deepcopy(self)
162
163 def required_vars(self) -> Iterator[Tuple[str, Var]]:
164 for name, val in self.vals.items():
165 if isinstance(val, Var):
166 yield name, val
167
168 def is_free(self) -> bool:
169 return len(list(self.required_vars())) == 0
170
171 def __str__(self) -> str:
koder aka kdanilova732a602017-02-01 20:29:56 +0200172 res = "[{0}]\n".format(self.summary)
koder aka kdanilovf90de852017-01-20 18:12:27 +0200173
174 for name, val in self.vals.items():
175 if name.startswith('_') or name == name.upper():
176 continue
177 if isinstance(val, Var):
178 res += "{0}={{{1}}}\n".format(name, val.name)
179 else:
180 res += "{0}={1}\n".format(name, val)
181
182 return res
183
184 def __repr__(self) -> str:
185 return str(self)
186
187 def raw(self) -> Dict[str, Any]:
188 res = super().raw()
189 res['vals'] = list(map(list, self.vals.items()))
190 return res
191
192 @classmethod
193 def fromraw(cls, data: Dict[str, Any]) -> 'FioJobConfig':
194 data['vals'] = OrderedDict(data['vals'])
koder aka kdanilova732a602017-02-01 20:29:56 +0200195 data['_sync_mode'] = None
196 data['_params'] = None
koder aka kdanilovf90de852017-01-20 18:12:27 +0200197 return cast(FioJobConfig, super().fromraw(data))