blob: a257d13c723fab04c164a325b938cf272dbe3291 [file] [log] [blame]
Ved-vampir5c7b6142015-04-24 19:49:59 +03001""" Analize test results for finding bottlenecks """
2
3import sys
koder aka kdanilovf86d7af2015-05-06 04:01:54 +03004import os.path
Ved-vampir5c7b6142015-04-24 19:49:59 +03005import argparse
koder aka kdanilovf86d7af2015-05-06 04:01:54 +03006import collections
Ved-vampir5c7b6142015-04-24 19:49:59 +03007
8
koder aka kdanilovf86d7af2015-05-06 04:01:54 +03009import yaml
Ved-vampir5c7b6142015-04-24 19:49:59 +030010
Ved-vampirfcea0722015-04-27 14:06:13 +030011
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030012from wally.utils import b2ssize
Ved-vampirfcea0722015-04-27 14:06:13 +030013
14
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030015class SensorsData(object):
16 def __init__(self, source_id, hostname, ctime, values):
17 self.source_id = source_id
18 self.hostname = hostname
19 self.ctime = ctime
20 self.values = values # [((dev, sensor), value)]
Ved-vampirfcea0722015-04-27 14:06:13 +030021
22
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030023def load_results(fd):
24 res = []
25 source_id2nostname = {}
Ved-vampir5c7b6142015-04-24 19:49:59 +030026
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030027 for line in fd:
28 line = line.strip()
29 if line != "":
30 _, data = eval(line)
31 ctime = data.pop('time')
32 source_id = data.pop('source_id')
33 hostname = data.pop('hostname')
Ved-vampirfcea0722015-04-27 14:06:13 +030034
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030035 data = [(k.split('.'), v) for k, v in data.items()]
Ved-vampirfcea0722015-04-27 14:06:13 +030036
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030037 sd = SensorsData(source_id, hostname, ctime, data)
38 res.append((ctime, sd))
39 source_id2nostname[source_id] = hostname
Ved-vampirfcea0722015-04-27 14:06:13 +030040
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030041 res.sort(key=lambda x: x[0])
42 return res, source_id2nostname
Ved-vampir5c7b6142015-04-24 19:49:59 +030043
44
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030045critical_values = dict(
46 io_queue=1,
47 mem_usage_percent=0.8)
Ved-vampirfcea0722015-04-27 14:06:13 +030048
49
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030050class SensorInfo(object):
51 def __init__(self, name, native_ext, to_bytes_coef):
52 self.name = name
53 self.native_ext = native_ext
54 self.to_bytes_coef = to_bytes_coef
Ved-vampirfcea0722015-04-27 14:06:13 +030055
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030056SINFO = [
57 SensorInfo('recv_bytes', 'B', 1),
58 SensorInfo('send_bytes', 'B', 1),
59 SensorInfo('sectors_written', 'Sect', 512),
60 SensorInfo('sectors_read', 'Sect', 512),
61]
Ved-vampirfcea0722015-04-27 14:06:13 +030062
63
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030064SINFO_MAP = dict((sinfo.name, sinfo) for sinfo in SINFO)
Ved-vampirfcea0722015-04-27 14:06:13 +030065
Ved-vampirfcea0722015-04-27 14:06:13 +030066
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030067class AggregatedData(object):
68 def __init__(self, sensor_name):
69 self.sensor_name = sensor_name
Ved-vampir5c7b6142015-04-24 19:49:59 +030070
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030071 # (node, device): count
72 self.per_device = collections.defaultdict(lambda: 0)
73
74 # node: count
75 self.per_node = collections.defaultdict(lambda: 0)
76
77 # role: count
78 self.per_role = collections.defaultdict(lambda: 0)
79
80 # (role_or_node, device_or_*): count
81 self.all_together = collections.defaultdict(lambda: 0)
82
83 def __str__(self):
84 res = "<AggregatedData({0})>\n".format(self.sensor_name)
85 for (role_or_node, device), val in self.all_together.items():
86 res += " {0}:{1} = {2}\n".format(role_or_node, device, val)
87 return res
88
89
90def total_consumption(sensors_data, roles_map):
91 result = {}
92
93 for _, item in sensors_data:
94 for (dev, sensor), val in item.values:
95
96 try:
97 ad = result[sensor]
98 except KeyError:
99 ad = result[sensor] = AggregatedData(sensor)
100
101 ad.per_device[(item.hostname, dev)] += val
102
103 for ad in result.values():
104 for (hostname, dev), val in ad.per_device.items():
105 ad.per_node[hostname] += val
106
107 for role in roles_map[hostname]:
108 ad.per_role[role] += val
109
110 ad.all_together[(hostname, dev)] = val
111
112 for role, val in ad.per_role.items():
113 ad.all_together[(role, '*')] = val
114
115 for node, val in ad.per_node.items():
116 ad.all_together[(node, '*')] = val
117
118 return result
119
120
121def avg_load(data):
122 load = {}
123
124 min_time = 0xFFFFFFFFFFF
125 max_time = 0
126
127 for tm, item in data:
128
129 min_time = min(min_time, item.ctime)
130 max_time = max(max_time, item.ctime)
131
132 for name, max_val in critical_values.items():
133 for (dev, sensor), val in item.values:
134 if sensor == name and val > max_val:
135 load[(item.hostname, dev, sensor)] += 1
136 return load, max_time - min_time
137
138
139def print_bottlenecks(data_iter, max_bottlenecks=15):
140 load, duration = avg_load(data_iter)
141 rev_items = ((v, k) for (k, v) in load.items())
142
143 res = sorted(rev_items, reverse=True)[:max_bottlenecks]
144
145 max_name_sz = max(len(name) for _, name in res)
146 frmt = "{{0:>{0}}} | {{1:>4}}".format(max_name_sz)
147 table = [frmt.format("Component", "% times load > 100%")]
148
149 for (v, k) in res:
150 table.append(frmt.format(k, int(v * 100.0 / duration + 0.5)))
151
152 return "\n".join(table)
153
154
155def print_consumption(agg, roles, min_transfer=0):
156 rev_items = []
157 for (node_or_role, dev), v in agg.all_together.items():
158 rev_items.append((int(v), node_or_role + ':' + dev))
159
160 res = sorted(rev_items, reverse=True)
161 sinfo = SINFO_MAP[agg.sensor_name]
162
163 if sinfo.to_bytes_coef is not None:
164 res = [(v, k)
165 for (v, k) in res
166 if v * sinfo.to_bytes_coef >= min_transfer]
167
168 if len(res) == 0:
169 return None
170
171 res = [(b2ssize(v) + sinfo.native_ext, k) for (v, k) in res]
172
173 max_name_sz = max(len(name) for _, name in res)
174 max_val_sz = max(len(val) for val, _ in res)
175
176 frmt = " {{0:>{0}}} | {{1:>{1}}} ".format(max_name_sz, max_val_sz)
177 table = [frmt.format("Component", "Usage")]
178
179 for (v, k) in res:
180 table.append(frmt.format(k, v))
181
182 return "\n".join(table)
Ved-vampir5c7b6142015-04-24 19:49:59 +0300183
184
185def parse_args(args):
186 parser = argparse.ArgumentParser()
187 parser.add_argument('-t', '--time_period', nargs=2,
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300188 type=int, default=None,
Ved-vampir5c7b6142015-04-24 19:49:59 +0300189 help="Begin and end time for tests")
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300190 parser.add_argument('-m', '--max-bottlenek', type=int,
191 default=15, help="Max bottlenek to show")
Ved-vampirfcea0722015-04-27 14:06:13 +0300192 parser.add_argument('-d', '--debug-ver', action='store_true',
193 help="Full report with original data")
194 parser.add_argument('-u', '--user-ver', action='store_true',
195 default=True,
196 help="Avg load report")
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300197 parser.add_argument('results_folder')
Ved-vampir5c7b6142015-04-24 19:49:59 +0300198 return parser.parse_args(args[1:])
199
200
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300201def make_roles_mapping(source_id_mapping, source_id2hostname):
202 result = {}
203 for ssh_url, roles in source_id_mapping.items():
204 if '@' in ssh_url:
205 source_id = ssh_url.split('@')[1]
206 else:
207 source_id = ssh_url.split('://')[1]
208
209 if source_id.count(':') == 2:
210 source_id = source_id.rsplit(":", 1)[0]
211
212 if source_id.endswith(':'):
213 source_id += "22"
214
215 if source_id in source_id2hostname:
216 result[source_id] = roles
217 result[source_id2hostname[source_id]] = roles
218
219 for testnode_src in (set(source_id2hostname) - set(result)):
220 result[testnode_src] = ['testnode']
221 result[source_id2hostname[testnode_src]] = ['testnode']
222
223 return result
224
225
226def get_testdata_size(consumption):
227 max_data = 0
228 for sensor_name, agg in consumption.items():
229 if sensor_name in SINFO_MAP:
230 tb = SINFO_MAP[sensor_name].to_bytes_coef
231 if tb is not None:
232 max_data = max(max_data, agg.per_role.get('testnode', 0) * tb)
233 return max_data
234
235
Ved-vampir5c7b6142015-04-24 19:49:59 +0300236def main(argv):
237 opts = parse_args(argv)
238
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300239 sensors_data_fname = os.path.join(opts.results_folder,
240 'sensor_storage.txt')
Ved-vampir5c7b6142015-04-24 19:49:59 +0300241
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300242 roles_file = os.path.join(opts.results_folder,
243 'nodes.yaml')
Ved-vampirfcea0722015-04-27 14:06:13 +0300244
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300245 src2roles = yaml.load(open(roles_file))
246
247 with open(sensors_data_fname) as fd:
248 data, source_id2hostname = load_results(fd)
249
250 roles_map = make_roles_mapping(src2roles, source_id2hostname)
251
252 # print print_bottlenecks(data, opts.max_bottlenek)
253 # print print_bottlenecks(data, opts.max_bottlenek)
254
255 consumption = total_consumption(data, roles_map)
256
257 testdata_sz = get_testdata_size(consumption) // 1024
258 for name in ('recv_bytes', 'send_bytes',
259 'sectors_read', 'sectors_written'):
260 table = print_consumption(consumption[name], roles_map, testdata_sz)
261 if table is None:
262 print "Consumption of", name, "is negligible"
263 else:
264 ln = max(map(len, table.split('\n')))
265 print '-' * ln
266 print name.center(ln)
267 print '-' * ln
268 print table
269 print '-' * ln
270 print
Ved-vampirfcea0722015-04-27 14:06:13 +0300271
Ved-vampir5c7b6142015-04-24 19:49:59 +0300272
273if __name__ == "__main__":
274 exit(main(sys.argv))