blob: 0f55e91214acfc702f4901e21f028e957dc2ef7c [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"""
56 res = "{0[sync_mode_long]} {0[oper]} {1}".format(self, b2ssize(self['bsize'] * 1024))
57 if self['qd'] is not None:
58 res += " QD = " + str(self['qd'])
59 if self['thcount'] not in (1, None):
60 res += " threads={0[thcount]}".format(self)
61 if self['write_perc'] is not None:
62 res += " write_perc={0[write_perc]}%".format(self)
63 return res
64
65
66class FioJobConfig(JobConfig):
67 """Fio job configuration"""
68 ds2mode = {(True, True): 'x',
69 (True, False): 's',
70 (False, True): 'd',
71 (False, False): 'a'}
72
73 op_type2short = {"randread": "rr",
74 "randwrite": "rw",
75 "read": "sr",
76 "write": "sw",
77 "randrw": "rx"}
78
79 def __init__(self, name: str, idx: int) -> None:
80 JobConfig.__init__(self, idx)
81 self.name = name
82 self._sync_mode = None # type: Optional[str]
83 self._params = None # type: Optional[Dict[str, Any]]
84
85 # ------------- BASIC PROPERTIES -----------------------------------------------------------------------------------
86
87 @property
88 def write_perc(self) -> Optional[int]:
89 try:
90 return int(self.vals["rwmixwrite"])
91 except (KeyError, TypeError):
92 try:
93 return 100 - int(self.vals["rwmixread"])
94 except (KeyError, TypeError):
95 return None
96
97 @property
98 def qd(self) -> int:
99 return int(self.vals['iodepth'])
100
101 @property
102 def bsize(self) -> int:
103 bsize = ssize2b(self.vals['blocksize'])
104 assert bsize % 1024 == 0
105 return bsize // 1024
106
107 @property
108 def oper(self) -> str:
109 return self.vals['rw']
110
111 @property
112 def op_type_short(self) -> str:
113 return self.op_type2short[self.vals['rw']]
114
115 @property
116 def thcount(self) -> int:
117 return int(self.vals.get('numjobs', 1))
118
119 @property
120 def sync_mode(self) -> str:
121 if self._sync_mode is None:
122 direct = is_fio_opt_true(self.vals.get('direct', '0')) or \
123 not is_fio_opt_true(self.vals.get('buffered', '0'))
124 sync = is_fio_opt_true(self.vals.get('sync', '0'))
125 self._sync_mode = self.ds2mode[(sync, direct)]
126 return cast(str, self._sync_mode)
127
128 # ----------- COMPLEX PROPERTIES -----------------------------------------------------------------------------------
129
130 @property
131 def params(self) -> JobParams:
132 if self._params is None:
133 self._params = dict(oper=self.oper,
134 sync_mode=self.sync_mode,
135 bsize=self.bsize,
136 qd=self.qd,
137 thcount=self.thcount,
138 write_perc=self.write_perc)
139 return cast(JobParams, FioJobParams(**cast(Dict[str, Any], self._params)))
140
141 # ------------------------------------------------------------------------------------------------------------------
142
143 def __eq__(self, o: object) -> bool:
144 if not isinstance(o, FioJobConfig):
145 return False
146 return self.vals == cast(FioJobConfig, o).vals
147
148 def copy(self) -> 'FioJobConfig':
149 return copy.deepcopy(self)
150
151 def required_vars(self) -> Iterator[Tuple[str, Var]]:
152 for name, val in self.vals.items():
153 if isinstance(val, Var):
154 yield name, val
155
156 def is_free(self) -> bool:
157 return len(list(self.required_vars())) == 0
158
159 def __str__(self) -> str:
160 res = "[{0}]\n".format(self.params.summary)
161
162 for name, val in self.vals.items():
163 if name.startswith('_') or name == name.upper():
164 continue
165 if isinstance(val, Var):
166 res += "{0}={{{1}}}\n".format(name, val.name)
167 else:
168 res += "{0}={1}\n".format(name, val)
169
170 return res
171
172 def __repr__(self) -> str:
173 return str(self)
174
175 def raw(self) -> Dict[str, Any]:
176 res = super().raw()
177 res['vals'] = list(map(list, self.vals.items()))
178 return res
179
180 @classmethod
181 def fromraw(cls, data: Dict[str, Any]) -> 'FioJobConfig':
182 data['vals'] = OrderedDict(data['vals'])
183 return cast(FioJobConfig, super().fromraw(data))