Ved-vampir | 5c7b614 | 2015-04-24 19:49:59 +0300 | [diff] [blame] | 1 | """ Analize test results for finding bottlenecks """ |
| 2 | |
| 3 | import sys |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame^] | 4 | import os.path |
Ved-vampir | 5c7b614 | 2015-04-24 19:49:59 +0300 | [diff] [blame] | 5 | import argparse |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame^] | 6 | import collections |
Ved-vampir | 5c7b614 | 2015-04-24 19:49:59 +0300 | [diff] [blame] | 7 | |
| 8 | |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame^] | 9 | import yaml |
Ved-vampir | 5c7b614 | 2015-04-24 19:49:59 +0300 | [diff] [blame] | 10 | |
Ved-vampir | fcea072 | 2015-04-27 14:06:13 +0300 | [diff] [blame] | 11 | |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame^] | 12 | from wally.utils import b2ssize |
Ved-vampir | fcea072 | 2015-04-27 14:06:13 +0300 | [diff] [blame] | 13 | |
| 14 | |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame^] | 15 | class 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-vampir | fcea072 | 2015-04-27 14:06:13 +0300 | [diff] [blame] | 21 | |
| 22 | |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame^] | 23 | def load_results(fd): |
| 24 | res = [] |
| 25 | source_id2nostname = {} |
Ved-vampir | 5c7b614 | 2015-04-24 19:49:59 +0300 | [diff] [blame] | 26 | |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame^] | 27 | 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-vampir | fcea072 | 2015-04-27 14:06:13 +0300 | [diff] [blame] | 34 | |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame^] | 35 | data = [(k.split('.'), v) for k, v in data.items()] |
Ved-vampir | fcea072 | 2015-04-27 14:06:13 +0300 | [diff] [blame] | 36 | |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame^] | 37 | sd = SensorsData(source_id, hostname, ctime, data) |
| 38 | res.append((ctime, sd)) |
| 39 | source_id2nostname[source_id] = hostname |
Ved-vampir | fcea072 | 2015-04-27 14:06:13 +0300 | [diff] [blame] | 40 | |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame^] | 41 | res.sort(key=lambda x: x[0]) |
| 42 | return res, source_id2nostname |
Ved-vampir | 5c7b614 | 2015-04-24 19:49:59 +0300 | [diff] [blame] | 43 | |
| 44 | |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame^] | 45 | critical_values = dict( |
| 46 | io_queue=1, |
| 47 | mem_usage_percent=0.8) |
Ved-vampir | fcea072 | 2015-04-27 14:06:13 +0300 | [diff] [blame] | 48 | |
| 49 | |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame^] | 50 | class 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-vampir | fcea072 | 2015-04-27 14:06:13 +0300 | [diff] [blame] | 55 | |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame^] | 56 | SINFO = [ |
| 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-vampir | fcea072 | 2015-04-27 14:06:13 +0300 | [diff] [blame] | 62 | |
| 63 | |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame^] | 64 | SINFO_MAP = dict((sinfo.name, sinfo) for sinfo in SINFO) |
Ved-vampir | fcea072 | 2015-04-27 14:06:13 +0300 | [diff] [blame] | 65 | |
Ved-vampir | fcea072 | 2015-04-27 14:06:13 +0300 | [diff] [blame] | 66 | |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame^] | 67 | class AggregatedData(object): |
| 68 | def __init__(self, sensor_name): |
| 69 | self.sensor_name = sensor_name |
Ved-vampir | 5c7b614 | 2015-04-24 19:49:59 +0300 | [diff] [blame] | 70 | |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame^] | 71 | # (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 | |
| 90 | def 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 | |
| 121 | def 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 | |
| 139 | def 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 | |
| 155 | def 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-vampir | 5c7b614 | 2015-04-24 19:49:59 +0300 | [diff] [blame] | 183 | |
| 184 | |
| 185 | def parse_args(args): |
| 186 | parser = argparse.ArgumentParser() |
| 187 | parser.add_argument('-t', '--time_period', nargs=2, |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame^] | 188 | type=int, default=None, |
Ved-vampir | 5c7b614 | 2015-04-24 19:49:59 +0300 | [diff] [blame] | 189 | help="Begin and end time for tests") |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame^] | 190 | parser.add_argument('-m', '--max-bottlenek', type=int, |
| 191 | default=15, help="Max bottlenek to show") |
Ved-vampir | fcea072 | 2015-04-27 14:06:13 +0300 | [diff] [blame] | 192 | 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 kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame^] | 197 | parser.add_argument('results_folder') |
Ved-vampir | 5c7b614 | 2015-04-24 19:49:59 +0300 | [diff] [blame] | 198 | return parser.parse_args(args[1:]) |
| 199 | |
| 200 | |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame^] | 201 | def 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 | |
| 226 | def 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-vampir | 5c7b614 | 2015-04-24 19:49:59 +0300 | [diff] [blame] | 236 | def main(argv): |
| 237 | opts = parse_args(argv) |
| 238 | |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame^] | 239 | sensors_data_fname = os.path.join(opts.results_folder, |
| 240 | 'sensor_storage.txt') |
Ved-vampir | 5c7b614 | 2015-04-24 19:49:59 +0300 | [diff] [blame] | 241 | |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame^] | 242 | roles_file = os.path.join(opts.results_folder, |
| 243 | 'nodes.yaml') |
Ved-vampir | fcea072 | 2015-04-27 14:06:13 +0300 | [diff] [blame] | 244 | |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame^] | 245 | 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-vampir | fcea072 | 2015-04-27 14:06:13 +0300 | [diff] [blame] | 271 | |
Ved-vampir | 5c7b614 | 2015-04-24 19:49:59 +0300 | [diff] [blame] | 272 | |
| 273 | if __name__ == "__main__": |
| 274 | exit(main(sys.argv)) |