Ved-vampir | 5c7b614 | 2015-04-24 19:49:59 +0300 | [diff] [blame] | 1 | """ Analize test results for finding bottlenecks """ |
| 2 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 3 | import re |
Ved-vampir | 5c7b614 | 2015-04-24 19:49:59 +0300 | [diff] [blame] | 4 | import sys |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 5 | import csv |
| 6 | import time |
| 7 | import bisect |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 8 | import os.path |
Ved-vampir | 5c7b614 | 2015-04-24 19:49:59 +0300 | [diff] [blame] | 9 | import argparse |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 10 | import collections |
Ved-vampir | 5c7b614 | 2015-04-24 19:49:59 +0300 | [diff] [blame] | 11 | |
| 12 | |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 13 | import yaml |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 14 | import texttable |
Ved-vampir | 5c7b614 | 2015-04-24 19:49:59 +0300 | [diff] [blame] | 15 | |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 16 | try: |
| 17 | import pygraphviz as pgv |
| 18 | except ImportError: |
| 19 | pgv = None |
Ved-vampir | fcea072 | 2015-04-27 14:06:13 +0300 | [diff] [blame] | 20 | |
Ved-vampir | b1e1861 | 2015-05-22 18:10:22 +0300 | [diff] [blame] | 21 | sys.path.append("/mnt/other/work/disk_perf_test_tool") |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 22 | from wally.run_test import load_data_from |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 23 | from wally.utils import b2ssize, b2ssize_10 |
Ved-vampir | fcea072 | 2015-04-27 14:06:13 +0300 | [diff] [blame] | 24 | |
| 25 | |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 26 | class SensorInfo(object): |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 27 | def __init__(self, name, print_name, native_ext, to_bytes_coef): |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 28 | self.name = name |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 29 | self.print_name = print_name |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 30 | self.native_ext = native_ext |
| 31 | self.to_bytes_coef = to_bytes_coef |
Ved-vampir | fcea072 | 2015-04-27 14:06:13 +0300 | [diff] [blame] | 32 | |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 33 | |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 34 | _SINFO = [ |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 35 | SensorInfo('recv_bytes', 'net_recv', 'B', 1), |
| 36 | SensorInfo('send_bytes', 'net_send', 'B', 1), |
| 37 | SensorInfo('sectors_written', 'hdd_write', 'Sect', 512), |
| 38 | SensorInfo('sectors_read', 'hdd_read', 'Sect', 512), |
| 39 | SensorInfo('reads_completed', 'read_op', 'OP', None), |
| 40 | SensorInfo('writes_completed', 'write_op', 'OP', None), |
Ved-vampir | b1e1861 | 2015-05-22 18:10:22 +0300 | [diff] [blame] | 41 | SensorInfo('procs_blocked', 'blocked_procs', 'P', None), |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 42 | ] |
Ved-vampir | fcea072 | 2015-04-27 14:06:13 +0300 | [diff] [blame] | 43 | |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 44 | SINFO_MAP = dict((sinfo.name, sinfo) for sinfo in _SINFO) |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 45 | to_bytes = dict((sinfo.name, sinfo.to_bytes_coef) |
| 46 | for sinfo in _SINFO |
| 47 | if sinfo.to_bytes_coef is not None) |
Ved-vampir | fcea072 | 2015-04-27 14:06:13 +0300 | [diff] [blame] | 48 | |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 49 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 50 | class NodeSensorsData(object): |
| 51 | def __init__(self, source_id, hostname, headers, values): |
| 52 | self.source_id = source_id |
| 53 | self.hostname = hostname |
| 54 | self.headers = headers |
| 55 | self.values = values |
| 56 | self.times = None |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 57 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 58 | def finalize(self): |
| 59 | self.times = [v[0] for v in self.values] |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 60 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 61 | def get_data_for_interval(self, beg, end): |
| 62 | p1 = bisect.bisect_left(self.times, beg) |
| 63 | p2 = bisect.bisect_right(self.times, end) |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 64 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 65 | obj = self.__class__(self.source_id, |
| 66 | self.hostname, |
| 67 | self.headers, |
| 68 | self.values[p1:p2]) |
| 69 | obj.times = self.times[p1:p2] |
| 70 | return obj |
| 71 | |
| 72 | def __getitem__(self, name): |
| 73 | idx = self.headers.index(name.split('.')) |
| 74 | # +1 as first is a time |
| 75 | return [val[idx] for val in self.values] |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 76 | |
| 77 | |
| 78 | def load_results_csv(fd): |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 79 | data = fd.read() |
| 80 | results = {} |
| 81 | for block in data.split("NEW_DATA"): |
| 82 | block = block.strip() |
| 83 | if len(block) == 0: |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 84 | continue |
| 85 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 86 | it = csv.reader(block.split("\n")) |
| 87 | headers = next(it) |
Ved-vampir | b1e1861 | 2015-05-22 18:10:22 +0300 | [diff] [blame] | 88 | sens_data = [map(float, vals) for vals in it] |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 89 | source_id, hostname = headers[:2] |
| 90 | headers = [(None, 'time')] + \ |
| 91 | [header.split('.') for header in headers[2:]] |
| 92 | assert set(map(len, headers)) == set([2]) |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 93 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 94 | results[source_id] = NodeSensorsData(source_id, hostname, |
| 95 | headers, sens_data) |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 96 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 97 | return results |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 98 | |
| 99 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 100 | def load_test_timings(fname, max_diff=1000): |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 101 | raw_map = collections.defaultdict(lambda: []) |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 102 | |
| 103 | class data(object): |
| 104 | pass |
| 105 | |
| 106 | load_data_from(fname)(None, data) |
Ved-vampir | b1e1861 | 2015-05-22 18:10:22 +0300 | [diff] [blame] | 107 | for test_type, test_results in data.results.items(): |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 108 | if test_type == 'io': |
| 109 | for tests_res in test_results: |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 110 | raw_map[tests_res.config.name].append(tests_res.run_interval) |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 111 | |
| 112 | result = {} |
| 113 | for name, intervals in raw_map.items(): |
| 114 | intervals.sort() |
| 115 | curr_start, curr_stop = intervals[0] |
| 116 | curr_result = [] |
| 117 | |
| 118 | for (start, stop) in intervals[1:]: |
| 119 | if abs(curr_start - start) < max_diff: |
| 120 | # if abs(curr_stop - stop) > 2: |
| 121 | # print abs(curr_stop - stop) |
| 122 | assert abs(curr_stop - stop) < max_diff |
| 123 | else: |
| 124 | assert start + max_diff >= curr_stop |
| 125 | assert stop > curr_stop |
| 126 | curr_result.append((curr_start, curr_stop)) |
| 127 | curr_start, curr_stop = start, stop |
| 128 | curr_result.append((curr_start, curr_stop)) |
| 129 | |
| 130 | merged_res = [] |
| 131 | curr_start, curr_stop = curr_result[0] |
| 132 | for start, stop in curr_result[1:]: |
| 133 | if abs(curr_stop - start) < max_diff: |
| 134 | curr_stop = stop |
| 135 | else: |
| 136 | merged_res.append((curr_start, curr_stop)) |
| 137 | curr_start, curr_stop = start, stop |
| 138 | merged_res.append((curr_start, curr_stop)) |
| 139 | result[name] = merged_res |
| 140 | |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 141 | return result |
| 142 | |
| 143 | |
| 144 | critical_values = dict( |
| 145 | io_queue=1, |
Ved-vampir | b1e1861 | 2015-05-22 18:10:22 +0300 | [diff] [blame] | 146 | usage_percent=0.8, |
| 147 | procs_blocked=1, |
| 148 | procs_queue=1) |
Ved-vampir | fcea072 | 2015-04-27 14:06:13 +0300 | [diff] [blame] | 149 | |
Ved-vampir | fcea072 | 2015-04-27 14:06:13 +0300 | [diff] [blame] | 150 | |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 151 | class AggregatedData(object): |
| 152 | def __init__(self, sensor_name): |
| 153 | self.sensor_name = sensor_name |
Ved-vampir | 5c7b614 | 2015-04-24 19:49:59 +0300 | [diff] [blame] | 154 | |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 155 | # (node, device): count |
| 156 | self.per_device = collections.defaultdict(lambda: 0) |
| 157 | |
| 158 | # node: count |
| 159 | self.per_node = collections.defaultdict(lambda: 0) |
| 160 | |
| 161 | # role: count |
| 162 | self.per_role = collections.defaultdict(lambda: 0) |
| 163 | |
| 164 | # (role_or_node, device_or_*): count |
| 165 | self.all_together = collections.defaultdict(lambda: 0) |
| 166 | |
| 167 | def __str__(self): |
| 168 | res = "<AggregatedData({0})>\n".format(self.sensor_name) |
| 169 | for (role_or_node, device), val in self.all_together.items(): |
| 170 | res += " {0}:{1} = {2}\n".format(role_or_node, device, val) |
| 171 | return res |
| 172 | |
| 173 | |
| 174 | def total_consumption(sensors_data, roles_map): |
| 175 | result = {} |
| 176 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 177 | for name, sensor_data in sensors_data.items(): |
| 178 | for pos, (dev, sensor) in enumerate(sensor_data.headers): |
Ved-vampir | b1e1861 | 2015-05-22 18:10:22 +0300 | [diff] [blame] | 179 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 180 | if 'time' == sensor: |
| 181 | continue |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 182 | |
| 183 | try: |
| 184 | ad = result[sensor] |
| 185 | except KeyError: |
| 186 | ad = result[sensor] = AggregatedData(sensor) |
| 187 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 188 | val = sum(vals[pos] for vals in sensor_data.values) |
| 189 | |
| 190 | ad.per_device[(sensor_data.hostname, dev)] += val |
| 191 | |
| 192 | # vals1 = sensors_data['localhost:22']['sdc.sectors_read'] |
| 193 | # vals2 = sensors_data['localhost:22']['sdb.sectors_written'] |
| 194 | |
| 195 | # from matplotlib import pyplot as plt |
| 196 | # plt.plot(range(len(vals1)), vals1) |
| 197 | # plt.plot(range(len(vals2)), vals2) |
| 198 | # plt.show() |
| 199 | # exit(1) |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 200 | |
| 201 | for ad in result.values(): |
| 202 | for (hostname, dev), val in ad.per_device.items(): |
| 203 | ad.per_node[hostname] += val |
| 204 | |
| 205 | for role in roles_map[hostname]: |
| 206 | ad.per_role[role] += val |
| 207 | |
| 208 | ad.all_together[(hostname, dev)] = val |
| 209 | |
| 210 | for role, val in ad.per_role.items(): |
| 211 | ad.all_together[(role, '*')] = val |
| 212 | |
| 213 | for node, val in ad.per_node.items(): |
| 214 | ad.all_together[(node, '*')] = val |
| 215 | |
| 216 | return result |
| 217 | |
| 218 | |
Ved-vampir | b1e1861 | 2015-05-22 18:10:22 +0300 | [diff] [blame] | 219 | def avg_load(sensors_data): |
| 220 | load = collections.defaultdict(lambda: 0) |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 221 | |
| 222 | min_time = 0xFFFFFFFFFFF |
| 223 | max_time = 0 |
| 224 | |
Ved-vampir | b1e1861 | 2015-05-22 18:10:22 +0300 | [diff] [blame] | 225 | for sensor_data in sensors_data.values(): |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 226 | |
Ved-vampir | b1e1861 | 2015-05-22 18:10:22 +0300 | [diff] [blame] | 227 | min_time = min(min_time, min(sensor_data.times)) |
| 228 | max_time = max(max_time, max(sensor_data.times)) |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 229 | |
| 230 | for name, max_val in critical_values.items(): |
Ved-vampir | b1e1861 | 2015-05-22 18:10:22 +0300 | [diff] [blame] | 231 | for pos, (dev, sensor) in enumerate(sensor_data.headers): |
| 232 | if sensor == name: |
| 233 | for vals in sensor_data.values: |
| 234 | if vals[pos] > max_val: |
| 235 | load[(sensor_data.hostname, dev, sensor)] += 1 |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 236 | return load, max_time - min_time |
| 237 | |
| 238 | |
Ved-vampir | b1e1861 | 2015-05-22 18:10:22 +0300 | [diff] [blame] | 239 | def print_bottlenecks(sensors_data, max_bottlenecks=15): |
| 240 | load, duration = avg_load(sensors_data) |
| 241 | |
| 242 | if not load: |
| 243 | return "\n*** No bottlenecks found *** \n" |
| 244 | |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 245 | rev_items = ((v, k) for (k, v) in load.items()) |
| 246 | |
| 247 | res = sorted(rev_items, reverse=True)[:max_bottlenecks] |
| 248 | |
| 249 | max_name_sz = max(len(name) for _, name in res) |
| 250 | frmt = "{{0:>{0}}} | {{1:>4}}".format(max_name_sz) |
| 251 | table = [frmt.format("Component", "% times load > 100%")] |
| 252 | |
| 253 | for (v, k) in res: |
| 254 | table.append(frmt.format(k, int(v * 100.0 / duration + 0.5))) |
| 255 | |
| 256 | return "\n".join(table) |
| 257 | |
| 258 | |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 259 | def print_consumption(agg, min_transfer=None): |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 260 | rev_items = [] |
| 261 | for (node_or_role, dev), v in agg.all_together.items(): |
| 262 | rev_items.append((int(v), node_or_role + ':' + dev)) |
| 263 | |
| 264 | res = sorted(rev_items, reverse=True) |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 265 | |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 266 | if min_transfer is not None: |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 267 | res = [(v, k) |
| 268 | for (v, k) in res |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 269 | if v >= min_transfer] |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 270 | |
| 271 | if len(res) == 0: |
| 272 | return None |
| 273 | |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 274 | res = [(b2ssize(v) + "B", k) for (v, k) in res] |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 275 | |
| 276 | max_name_sz = max(len(name) for _, name in res) |
| 277 | max_val_sz = max(len(val) for val, _ in res) |
| 278 | |
| 279 | frmt = " {{0:>{0}}} | {{1:>{1}}} ".format(max_name_sz, max_val_sz) |
| 280 | table = [frmt.format("Component", "Usage")] |
| 281 | |
| 282 | for (v, k) in res: |
| 283 | table.append(frmt.format(k, v)) |
| 284 | |
| 285 | return "\n".join(table) |
Ved-vampir | 5c7b614 | 2015-04-24 19:49:59 +0300 | [diff] [blame] | 286 | |
| 287 | |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 288 | def make_roles_mapping(source_id_mapping, source_id2hostname): |
| 289 | result = {} |
| 290 | for ssh_url, roles in source_id_mapping.items(): |
| 291 | if '@' in ssh_url: |
| 292 | source_id = ssh_url.split('@')[1] |
| 293 | else: |
| 294 | source_id = ssh_url.split('://')[1] |
| 295 | |
| 296 | if source_id.count(':') == 2: |
| 297 | source_id = source_id.rsplit(":", 1)[0] |
| 298 | |
| 299 | if source_id.endswith(':'): |
| 300 | source_id += "22" |
| 301 | |
| 302 | if source_id in source_id2hostname: |
| 303 | result[source_id] = roles |
| 304 | result[source_id2hostname[source_id]] = roles |
| 305 | |
| 306 | for testnode_src in (set(source_id2hostname) - set(result)): |
| 307 | result[testnode_src] = ['testnode'] |
| 308 | result[source_id2hostname[testnode_src]] = ['testnode'] |
| 309 | |
| 310 | return result |
| 311 | |
| 312 | |
| 313 | def get_testdata_size(consumption): |
| 314 | max_data = 0 |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 315 | for name, sens in SINFO_MAP.items(): |
| 316 | if sens.to_bytes_coef is not None: |
| 317 | agg = consumption.get(name) |
| 318 | if agg is not None: |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 319 | cdt = agg.per_role.get('testnode', 0) * sens.to_bytes_coef |
| 320 | max_data = max(max_data, cdt) |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 321 | return max_data |
| 322 | |
| 323 | |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 324 | def get_testop_cout(consumption): |
| 325 | max_op = 0 |
| 326 | for name, sens in SINFO_MAP.items(): |
| 327 | if sens.to_bytes_coef is None: |
| 328 | agg = consumption.get(name) |
| 329 | if agg is not None: |
| 330 | max_op = max(max_op, agg.per_role.get('testnode', 0)) |
| 331 | return max_op |
| 332 | |
| 333 | |
| 334 | def get_data_for_intervals(data, intervals): |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 335 | res = {} |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 336 | for begin, end in intervals: |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 337 | for name, node_data in data.items(): |
| 338 | ndata = node_data.get_data_for_interval(begin, end) |
| 339 | res[name] = ndata |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 340 | return res |
| 341 | |
| 342 | |
| 343 | class Host(object): |
| 344 | def __init__(self, name=None): |
| 345 | self.name = name |
| 346 | self.hdd_devs = {} |
| 347 | self.net_devs = None |
| 348 | |
| 349 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 350 | def plot_consumption(per_consumer_table, fields, refload): |
| 351 | if pgv is None: |
| 352 | return |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 353 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 354 | hosts = {} |
| 355 | storage_sensors = ('sectors_written', 'sectors_read') |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 356 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 357 | for (hostname, dev), consumption in per_consumer_table.items(): |
| 358 | if hostname not in hosts: |
| 359 | hosts[hostname] = Host(hostname) |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 360 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 361 | host = hosts[hostname] |
| 362 | cons_map = dict(zip(fields, consumption)) |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 363 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 364 | for sn in storage_sensors: |
| 365 | vl = cons_map.get(sn, 0) |
| 366 | if vl > 0: |
| 367 | host.hdd_devs.setdefault(dev, {})[sn] = vl |
| 368 | |
| 369 | p = pgv.AGraph(name='system', directed=True) |
| 370 | |
| 371 | net = "Network" |
| 372 | p.add_node(net) |
| 373 | |
| 374 | in_color = 'red' |
| 375 | out_color = 'green' |
| 376 | |
| 377 | for host in hosts.values(): |
| 378 | g = p.subgraph(name="cluster_" + host.name, label=host.name, |
| 379 | color="blue") |
| 380 | g.add_node(host.name, shape="diamond") |
| 381 | p.add_edge(host.name, net) |
| 382 | p.add_edge(net, host.name) |
| 383 | |
| 384 | for dev_name, values in host.hdd_devs.items(): |
| 385 | if dev_name == '*': |
| 386 | continue |
| 387 | |
| 388 | to = values.get('sectors_written', 0) |
| 389 | frm = values.get('sectors_read', 0) |
| 390 | to_pw = 7 * to / refload |
| 391 | frm_pw = 7 * frm / refload |
| 392 | min_with = 0.1 |
| 393 | |
| 394 | if to_pw > min_with or frm_pw > min_with: |
| 395 | dev_fqn = host.name + "." + dev_name |
| 396 | g.add_node(dev_fqn) |
| 397 | |
| 398 | if to_pw > min_with: |
| 399 | g.add_edge(host.name, dev_fqn, |
| 400 | label=b2ssize(to) + "B", |
| 401 | penwidth=to_pw, |
| 402 | fontcolor=out_color, |
| 403 | color=out_color) |
| 404 | |
| 405 | if frm_pw > min_with: |
| 406 | g.add_edge(dev_fqn, host.name, |
| 407 | label=b2ssize(frm) + "B", |
| 408 | penwidth=frm_pw, |
| 409 | color=in_color, |
| 410 | fontcolor=in_color) |
| 411 | |
| 412 | return p.string() |
| 413 | |
| 414 | |
| 415 | def parse_args(args): |
| 416 | parser = argparse.ArgumentParser() |
| 417 | parser.add_argument('-t', '--time_period', nargs=2, |
| 418 | type=int, default=None, |
| 419 | help="Begin and end time for tests") |
| 420 | parser.add_argument('-m', '--max-bottlenek', type=int, |
Ved-vampir | b1e1861 | 2015-05-22 18:10:22 +0300 | [diff] [blame] | 421 | default=15, help="Max bottleneck to show") |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 422 | parser.add_argument('-x', '--max-diff', type=int, |
Ved-vampir | b1e1861 | 2015-05-22 18:10:22 +0300 | [diff] [blame] | 423 | default=10, help="Max bottleneck to show in" + |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 424 | "0.1% from test nodes summ load") |
| 425 | parser.add_argument('-d', '--debug-ver', action='store_true', |
| 426 | help="Full report with original data") |
| 427 | parser.add_argument('-u', '--user-ver', action='store_true', |
| 428 | default=True, help="Avg load report") |
| 429 | parser.add_argument('-s', '--select-loads', nargs='*', default=[]) |
| 430 | parser.add_argument('-f', '--fields', nargs='*', default=[]) |
| 431 | parser.add_argument('results_folder') |
| 432 | return parser.parse_args(args[1:]) |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 433 | |
| 434 | |
Ved-vampir | 5c7b614 | 2015-04-24 19:49:59 +0300 | [diff] [blame] | 435 | def main(argv): |
| 436 | opts = parse_args(argv) |
| 437 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 438 | stor_dir = os.path.join(opts.results_folder, 'sensor_storage') |
| 439 | data = {} |
| 440 | source_id2hostname = {} |
| 441 | |
| 442 | csv_files = os.listdir(stor_dir) |
| 443 | for fname in csv_files: |
| 444 | assert re.match(r"\d+_\d+.csv$", fname) |
| 445 | |
| 446 | csv_files.sort(key=lambda x: int(x.split('_')[0])) |
| 447 | |
| 448 | for fname in csv_files: |
| 449 | with open(os.path.join(stor_dir, fname)) as fd: |
| 450 | for name, node_sens_data in load_results_csv(fd).items(): |
| 451 | if name in data: |
| 452 | assert data[name].hostname == node_sens_data.hostname |
| 453 | assert data[name].source_id == node_sens_data.source_id |
| 454 | assert data[name].headers == node_sens_data.headers |
| 455 | data[name].values.extend(node_sens_data.values) |
| 456 | else: |
| 457 | data[name] = node_sens_data |
| 458 | |
| 459 | for nd in data.values(): |
| 460 | assert nd.source_id not in source_id2hostname |
| 461 | source_id2hostname[nd.source_id] = nd.hostname |
| 462 | nd.finalize() |
Ved-vampir | 5c7b614 | 2015-04-24 19:49:59 +0300 | [diff] [blame] | 463 | |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 464 | roles_file = os.path.join(opts.results_folder, |
| 465 | 'nodes.yaml') |
Ved-vampir | fcea072 | 2015-04-27 14:06:13 +0300 | [diff] [blame] | 466 | |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 467 | src2roles = yaml.load(open(roles_file)) |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 468 | |
| 469 | timings = load_test_timings(opts.results_folder) |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 470 | |
| 471 | roles_map = make_roles_mapping(src2roles, source_id2hostname) |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 472 | max_diff = float(opts.max_diff) / 1000 |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 473 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 474 | fields = ('recv_bytes', 'send_bytes', |
| 475 | 'sectors_read', 'sectors_written', |
| 476 | 'reads_completed', 'writes_completed') |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 477 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 478 | if opts.fields != []: |
| 479 | fields = [field for field in fields if field in opts.fields] |
| 480 | |
| 481 | for test_name, intervals in sorted(timings.items()): |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 482 | if opts.select_loads != []: |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 483 | if test_name not in opts.select_loads: |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 484 | continue |
| 485 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 486 | data_chunks = get_data_for_intervals(data, intervals) |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 487 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 488 | consumption = total_consumption(data_chunks, roles_map) |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 489 | |
Ved-vampir | b1e1861 | 2015-05-22 18:10:22 +0300 | [diff] [blame] | 490 | bottlenecks = print_bottlenecks(data_chunks) |
| 491 | |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 492 | testdata_sz = get_testdata_size(consumption) * max_diff |
| 493 | testop_count = get_testop_cout(consumption) * max_diff |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 494 | |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 495 | per_consumer_table = {} |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 496 | per_consumer_table_str = {} |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 497 | |
Ved-vampir | b1e1861 | 2015-05-22 18:10:22 +0300 | [diff] [blame] | 498 | all_consumers = set()#consumption.values()[0].all_together) |
| 499 | for value in consumption.values(): |
| 500 | all_consumers = all_consumers | set(value.all_together) |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 501 | fields = [field for field in fields if field in consumption] |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 502 | all_consumers_sum = [] |
| 503 | |
| 504 | for consumer in all_consumers: |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 505 | tb_str = per_consumer_table_str[consumer] = [] |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 506 | tb = per_consumer_table[consumer] = [] |
| 507 | vl = 0 |
| 508 | for name in fields: |
| 509 | val = consumption[name].all_together[consumer] |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 510 | if SINFO_MAP[name].to_bytes_coef is None: |
| 511 | if val < testop_count: |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 512 | tb_str.append('0') |
| 513 | else: |
| 514 | tb_str.append(b2ssize_10(int(val))) |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 515 | else: |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 516 | val = int(val) * SINFO_MAP[name].to_bytes_coef |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 517 | if val < testdata_sz: |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 518 | tb_str.append('-') |
| 519 | else: |
| 520 | tb_str.append(b2ssize(val) + "B") |
| 521 | tb.append(int(val)) |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 522 | vl += int(val) |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 523 | all_consumers_sum.append((vl, consumer)) |
| 524 | |
| 525 | all_consumers_sum.sort(reverse=True) |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 526 | |
| 527 | plot_consumption(per_consumer_table, fields, |
| 528 | testdata_sz / max_diff) |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 529 | |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 530 | tt = texttable.Texttable(max_width=130) |
| 531 | tt.set_cols_align(["l"] + ["r"] * len(fields)) |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 532 | |
| 533 | header = ["Name"] |
| 534 | for fld in fields: |
| 535 | if fld in SINFO_MAP: |
| 536 | header.append(SINFO_MAP[fld].print_name) |
| 537 | else: |
| 538 | header.append(fld) |
| 539 | tt.header(header) |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 540 | |
| 541 | for summ, consumer in all_consumers_sum: |
| 542 | if summ > 0: |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 543 | tt.add_row([":".join(consumer)] + |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 544 | per_consumer_table_str[consumer]) |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 545 | |
| 546 | tt.set_deco(texttable.Texttable.VLINES | texttable.Texttable.HEADER) |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 547 | res = tt.draw() |
| 548 | max_len = max(map(len, res.split("\n"))) |
| 549 | print test_name.center(max_len) |
| 550 | print res |
Ved-vampir | b1e1861 | 2015-05-22 18:10:22 +0300 | [diff] [blame] | 551 | print bottlenecks |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 552 | |
Ved-vampir | 5c7b614 | 2015-04-24 19:49:59 +0300 | [diff] [blame] | 553 | |
| 554 | if __name__ == "__main__": |
| 555 | exit(main(sys.argv)) |