blob: 2d8d78a2e4d18495935fd8981b43871711a95679 [file] [log] [blame]
koder aka kdanilovf90de852017-01-20 18:12:27 +02001import abc
2import copy
3from collections import OrderedDict
4from typing import Optional, Iterator, Union, Dict, Tuple, NamedTuple, Any, cast
5
6
7from ...utils import ssize2b, b2ssize
8from ..job import JobConfig, JobParams
9
10
11Var = NamedTuple('Var', [('name', str)])
12
13
14def is_fio_opt_true(vl: Union[str, int]) -> bool:
15 return str(vl).lower() in ['1', 'true', 't', 'yes', 'y']
16
17
18class FioJobParams(JobParams):
19 """Class contains all parameters, which significantly affects fio results.
20
21 oper - operation type - read/write/randread/...
22 sync_mode - direct/sync/async/direct+sync
23 bsize - block size in KiB
24 qd - IO queue depth,
25 thcount - thread count,
26 write_perc - write perc for mixed(read+write) loads
27
28 Like block size or operation type, but not file name or file size.
29 Can be used as key in dictionary.
30 """
31
32 sync2long = {'x': "sync direct",
33 's': "sync",
34 'd': "direct",
35 'a': "buffered"}
36
37 @property
38 def sync_mode_long(self) -> str:
39 return self.sync2long[self['sync_mode']]
40
41 @property
42 def summary(self) -> str:
43 """Test short summary, used mostly for file names and short image description"""
44 res = "{0[oper]}{0[sync_mode]}{0[bsize]}".format(self)
45 if self['qd'] is not None:
46 res += "_qd" + str(self['qd'])
47 if self['thcount'] not in (1, None):
48 res += "th" + str(self['thcount'])
49 if self['write_perc'] is not None:
50 res += "wr" + str(self['write_perc'])
51 return res
52
53 @property
54 def long_summary(self) -> str:
55 """Readable long summary for management and deployment engineers"""
koder aka kdanilova732a602017-02-01 20:29:56 +020056 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 +020057 if self['qd'] is not None:
koder aka kdanilova732a602017-02-01 20:29:56 +020058 res += ", QD = " + str(self['qd'])
koder aka kdanilovf90de852017-01-20 18:12:27 +020059 if self['thcount'] not in (1, None):
koder aka kdanilova732a602017-02-01 20:29:56 +020060 res += ", threads={0[thcount]}".format(self)
koder aka kdanilovf90de852017-01-20 18:12:27 +020061 if self['write_perc'] is not None:
koder aka kdanilova732a602017-02-01 20:29:56 +020062 res += ", write_perc={0[write_perc]}%".format(self)
koder aka kdanilovf90de852017-01-20 18:12:27 +020063 return res
64
koder aka kdanilova732a602017-02-01 20:29:56 +020065 def copy(self, **kwargs: Dict[str, Any]) -> 'FioJobParams':
66 np = self.params.copy()
67 np.update(kwargs)
68 return self.__class__(**np)
69
70 @property
71 def char_tpl(self) -> Tuple[Union[str, int], ...]:
72 mint = lambda x: -10000000000 if x is None else int(x)
73 return self['oper'], mint(self['bsize']), self['sync_mode'], \
74 mint(self['thcount']), mint(self['qd']), mint(self['write_perc'])
75
koder aka kdanilovf90de852017-01-20 18:12:27 +020076
77class FioJobConfig(JobConfig):
78 """Fio job configuration"""
79 ds2mode = {(True, True): 'x',
80 (True, False): 's',
81 (False, True): 'd',
82 (False, False): 'a'}
83
84 op_type2short = {"randread": "rr",
85 "randwrite": "rw",
86 "read": "sr",
87 "write": "sw",
88 "randrw": "rx"}
89
90 def __init__(self, name: str, idx: int) -> None:
91 JobConfig.__init__(self, idx)
92 self.name = name
93 self._sync_mode = None # type: Optional[str]
94 self._params = None # type: Optional[Dict[str, Any]]
95
96 # ------------- BASIC PROPERTIES -----------------------------------------------------------------------------------
97
98 @property
99 def write_perc(self) -> Optional[int]:
100 try:
101 return int(self.vals["rwmixwrite"])
102 except (KeyError, TypeError):
103 try:
104 return 100 - int(self.vals["rwmixread"])
105 except (KeyError, TypeError):
106 return None
107
108 @property
109 def qd(self) -> int:
110 return int(self.vals['iodepth'])
111
112 @property
113 def bsize(self) -> int:
114 bsize = ssize2b(self.vals['blocksize'])
115 assert bsize % 1024 == 0
116 return bsize // 1024
117
118 @property
119 def oper(self) -> str:
120 return self.vals['rw']
121
122 @property
123 def op_type_short(self) -> str:
124 return self.op_type2short[self.vals['rw']]
125
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,
145 sync_mode=self.sync_mode,
146 bsize=self.bsize,
147 qd=self.qd,
148 thcount=self.thcount,
149 write_perc=self.write_perc)
150 return cast(JobParams, FioJobParams(**cast(Dict[str, Any], self._params)))
151
152 # ------------------------------------------------------------------------------------------------------------------
153
154 def __eq__(self, o: object) -> bool:
155 if not isinstance(o, FioJobConfig):
156 return False
157 return self.vals == cast(FioJobConfig, o).vals
158
159 def copy(self) -> 'FioJobConfig':
160 return copy.deepcopy(self)
161
162 def required_vars(self) -> Iterator[Tuple[str, Var]]:
163 for name, val in self.vals.items():
164 if isinstance(val, Var):
165 yield name, val
166
167 def is_free(self) -> bool:
168 return len(list(self.required_vars())) == 0
169
170 def __str__(self) -> str:
koder aka kdanilova732a602017-02-01 20:29:56 +0200171 res = "[{0}]\n".format(self.summary)
koder aka kdanilovf90de852017-01-20 18:12:27 +0200172
173 for name, val in self.vals.items():
174 if name.startswith('_') or name == name.upper():
175 continue
176 if isinstance(val, Var):
177 res += "{0}={{{1}}}\n".format(name, val.name)
178 else:
179 res += "{0}={1}\n".format(name, val)
180
181 return res
182
183 def __repr__(self) -> str:
184 return str(self)
185
186 def raw(self) -> Dict[str, Any]:
187 res = super().raw()
188 res['vals'] = list(map(list, self.vals.items()))
189 return res
190
191 @classmethod
192 def fromraw(cls, data: Dict[str, Any]) -> 'FioJobConfig':
193 data['vals'] = OrderedDict(data['vals'])
koder aka kdanilova732a602017-02-01 20:29:56 +0200194 data['_sync_mode'] = None
195 data['_params'] = None
koder aka kdanilovf90de852017-01-20 18:12:27 +0200196 return cast(FioJobConfig, super().fromraw(data))