koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 1 | import os |
koder aka kdanilov | 7f59d56 | 2016-12-26 01:34:23 +0200 | [diff] [blame] | 2 | import abc |
koder aka kdanilov | a047e1b | 2015-04-21 23:16:59 +0300 | [diff] [blame] | 3 | import logging |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 4 | from collections import defaultdict |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 5 | from typing import Dict, Any, Iterator, Tuple, cast, List, Set, Optional, Union |
koder aka kdanilov | cff7b2e | 2015-04-18 20:48:15 +0300 | [diff] [blame] | 6 | |
koder aka kdanilov | ffaf48d | 2016-12-27 02:25:29 +0200 | [diff] [blame] | 7 | import numpy |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 8 | from statsmodels.tsa.stattools import adfuller |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 9 | |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 10 | import xmlbuilder3 |
koder aka kdanilov | be8f89f | 2015-04-28 14:51:51 +0300 | [diff] [blame] | 11 | |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 12 | import wally |
koder aka kdanilov | ffaf48d | 2016-12-27 02:25:29 +0200 | [diff] [blame] | 13 | |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 14 | from . import html |
koder aka kdanilov | 39e449e | 2016-12-17 15:15:26 +0200 | [diff] [blame] | 15 | from .stage import Stage, StepOrder |
| 16 | from .test_run_class import TestRun |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 17 | from .hlstorage import ResultStorage |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 18 | from .utils import b2ssize, b2ssize_10, STORAGE_ROLES, unit_conversion_coef |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 19 | from .statistic import calc_norm_stat_props |
| 20 | from .result_classes import DataSource, TimeSeries, SuiteConfig |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 21 | from .suits.io.fio import FioTest, FioJobConfig |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 22 | from .suits.io.fio_job import FioJobParams |
| 23 | from .suits.job import JobConfig |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 24 | from .data_selectors import get_aggregated, AGG_TAG, summ_sensors, find_sensors_to_2d, find_nodes_by_roles |
| 25 | from .report_profiles import (DefStyleProfile, DefColorProfile, StyleProfile, ColorProfile, |
| 26 | default_format, io_chart_format) |
| 27 | from .plot import (io_chart, plot_simple_bars, plot_hmap_from_2d, plot_lat_over_time, plot_simple_over_time, |
| 28 | plot_histo_heatmap, plot_v_over_time, plot_hist) |
| 29 | from .resources import ResourceNames, get_resources_usage, make_iosum, IOSummary, get_cluster_cpu_load |
koder aka kdanilov | 962ee5f | 2016-12-19 02:40:08 +0200 | [diff] [blame] | 30 | logger = logging.getLogger("wally") |
koder aka kdanilov | a047e1b | 2015-04-21 23:16:59 +0300 | [diff] [blame] | 31 | |
| 32 | |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 33 | # ---------------- CONSTS --------------------------------------------------------------------------------------------- |
koder aka kdanilov | 39e449e | 2016-12-17 15:15:26 +0200 | [diff] [blame] | 34 | |
koder aka kdanilov | 7f59d56 | 2016-12-26 01:34:23 +0200 | [diff] [blame] | 35 | |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 36 | DEBUG = False |
koder aka kdanilov | 39e449e | 2016-12-17 15:15:26 +0200 | [diff] [blame] | 37 | |
koder aka kdanilov | 39e449e | 2016-12-17 15:15:26 +0200 | [diff] [blame] | 38 | |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 39 | # ---------------- STRUCTS ------------------------------------------------------------------------------------------- |
koder aka kdanilov | 39e449e | 2016-12-17 15:15:26 +0200 | [diff] [blame] | 40 | |
koder aka kdanilov | 7f59d56 | 2016-12-26 01:34:23 +0200 | [diff] [blame] | 41 | |
| 42 | # TODO: need to be revised, have to user StatProps fields instead |
| 43 | class StoragePerfSummary: |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 44 | def __init__(self) -> None: |
koder aka kdanilov | 7f59d56 | 2016-12-26 01:34:23 +0200 | [diff] [blame] | 45 | self.direct_iops_r_max = 0 # type: int |
| 46 | self.direct_iops_w_max = 0 # type: int |
| 47 | |
| 48 | # 64 used instead of 4k to faster feed caches |
| 49 | self.direct_iops_w64_max = 0 # type: int |
| 50 | |
| 51 | self.rws4k_10ms = 0 # type: int |
| 52 | self.rws4k_30ms = 0 # type: int |
| 53 | self.rws4k_100ms = 0 # type: int |
| 54 | self.bw_write_max = 0 # type: int |
| 55 | self.bw_read_max = 0 # type: int |
| 56 | |
| 57 | self.bw = None # type: float |
| 58 | self.iops = None # type: float |
| 59 | self.lat = None # type: float |
| 60 | self.lat_50 = None # type: float |
| 61 | self.lat_95 = None # type: float |
| 62 | |
| 63 | |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 64 | # -------------- AGGREGATION AND STAT FUNCTIONS ---------------------------------------------------------------------- |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 65 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 66 | LEVEL_SENSORS = {("block-io", "io_queue"), ("system-cpu", "procs_blocked"), ("system-cpu", "procs_queue")} |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 67 | |
| 68 | |
| 69 | def is_level_sensor(sensor: str, metric: str) -> bool: |
| 70 | """Returns True if sensor measure level of any kind, E.g. queue depth.""" |
| 71 | return (sensor, metric) in LEVEL_SENSORS |
| 72 | |
| 73 | |
| 74 | def is_delta_sensor(sensor: str, metric: str) -> bool: |
| 75 | """Returns True if sensor provides deltas for cumulative value. E.g. io completed in given period""" |
| 76 | return not is_level_sensor(sensor, metric) |
| 77 | |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 78 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 79 | # def get_idle_load(rstorage: ResultStorage, *args, **kwargs) -> float: |
| 80 | # if 'idle' not in rstorage.storage: |
| 81 | # return 0.0 |
| 82 | # idle_time = rstorage.storage.get('idle') |
| 83 | # ssum = summ_sensors(rstorage, time_range=idle_time, *args, **kwargs) |
| 84 | # return numpy.average(ssum) |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 85 | |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 86 | |
| 87 | # -------------------- REPORT HELPERS -------------------------------------------------------------------------------- |
| 88 | |
| 89 | |
koder aka kdanilov | 7f59d56 | 2016-12-26 01:34:23 +0200 | [diff] [blame] | 90 | class HTMLBlock: |
| 91 | data = None # type: str |
| 92 | js_links = [] # type: List[str] |
| 93 | css_links = [] # type: List[str] |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 94 | order_attr = None # type: Any |
| 95 | |
| 96 | def __init__(self, data: str, order_attr: Any = None) -> None: |
| 97 | self.data = data |
| 98 | self.order_attr = order_attr |
| 99 | |
kdanylov aka koder | 4518318 | 2017-04-30 23:55:40 +0300 | [diff] [blame] | 100 | def __eq__(self, o: Any) -> bool: |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 101 | return o.order_attr == self.order_attr # type: ignore |
| 102 | |
kdanylov aka koder | 4518318 | 2017-04-30 23:55:40 +0300 | [diff] [blame] | 103 | def __lt__(self, o: Any) -> bool: |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 104 | return o.order_attr > self.order_attr # type: ignore |
| 105 | |
| 106 | |
| 107 | class Table: |
| 108 | def __init__(self, header: List[str]) -> None: |
| 109 | self.header = header |
| 110 | self.data = [] |
| 111 | |
| 112 | def add_line(self, values: List[str]) -> None: |
| 113 | self.data.append(values) |
| 114 | |
| 115 | def html(self): |
| 116 | return html.table("", self.header, self.data) |
koder aka kdanilov | 7f59d56 | 2016-12-26 01:34:23 +0200 | [diff] [blame] | 117 | |
| 118 | |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 119 | class Menu1st: |
| 120 | engineering = "Engineering" |
| 121 | summary = "Summary" |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 122 | per_job = "Per Job" |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 123 | |
| 124 | |
| 125 | class Menu2ndEng: |
| 126 | iops_time = "IOPS(time)" |
| 127 | hist = "IOPS/lat overall histogram" |
| 128 | lat_time = "Lat(time)" |
| 129 | |
| 130 | |
| 131 | class Menu2ndSumm: |
| 132 | io_lat_qd = "IO & Lat vs QD" |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 133 | cpu_usage_qd = "CPU usage" |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 134 | |
| 135 | |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 136 | menu_1st_order = [Menu1st.summary, Menu1st.engineering, Menu1st.per_job] |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 137 | |
| 138 | |
| 139 | # -------------------- REPORTS -------------------------------------------------------------------------------------- |
| 140 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 141 | class ReporterBase: |
| 142 | def __init__(self, rstorage: ResultStorage, style: StyleProfile, colors: ColorProfile) -> None: |
| 143 | self.style = style |
| 144 | self.colors = colors |
| 145 | self.rstorage = rstorage |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 146 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 147 | def plt(self, func, ds: DataSource, *args, **kwargs) -> str: |
| 148 | return func(self.rstorage, self.style, self.colors, ds, *args, **kwargs) |
| 149 | |
| 150 | |
| 151 | class SuiteReporter(ReporterBase, metaclass=abc.ABCMeta): |
| 152 | suite_types = set() # type: Set[str] |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 153 | |
koder aka kdanilov | 7f59d56 | 2016-12-26 01:34:23 +0200 | [diff] [blame] | 154 | @abc.abstractmethod |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 155 | def get_divs(self, suite: SuiteConfig) -> Iterator[Tuple[str, str, HTMLBlock]]: |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 156 | pass |
| 157 | |
| 158 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 159 | class JobReporter(ReporterBase, metaclass=abc.ABCMeta): |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 160 | suite_type = set() # type: Set[str] |
| 161 | |
| 162 | @abc.abstractmethod |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 163 | def get_divs(self, suite: SuiteConfig, job: JobConfig) -> Iterator[Tuple[str, str, HTMLBlock]]: |
koder aka kdanilov | 7f59d56 | 2016-12-26 01:34:23 +0200 | [diff] [blame] | 164 | pass |
| 165 | |
| 166 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 167 | # # Linearization report |
| 168 | # class IOPSBsize(SuiteReporter): |
| 169 | # """Creates graphs, which show how IOPS and Latency depend on block size""" |
| 170 | # |
| 171 | # |
| 172 | # # Main performance report |
| 173 | # class PerformanceSummary(SuiteReporter): |
| 174 | # """Aggregated summary fro storage""" |
| 175 | |
| 176 | # # Node load over test time |
| 177 | # class NodeLoad(SuiteReporter): |
| 178 | # """IOPS/latency during test""" |
| 179 | |
| 180 | # # Ceph operation breakout report |
| 181 | # class CephClusterSummary(SuiteReporter): |
| 182 | # """IOPS/latency during test""" |
koder aka kdanilov | 7f59d56 | 2016-12-26 01:34:23 +0200 | [diff] [blame] | 183 | |
| 184 | |
| 185 | # Main performance report |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 186 | class IOQD(SuiteReporter): |
koder aka kdanilov | 7f59d56 | 2016-12-26 01:34:23 +0200 | [diff] [blame] | 187 | """Creates graph, which show how IOPS and Latency depend on QD""" |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 188 | suite_types = {'fio'} |
| 189 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 190 | def get_divs(self, suite: SuiteConfig) -> Iterator[Tuple[str, str, HTMLBlock]]: |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 191 | ts_map = defaultdict(list) # type: Dict[FioJobParams, List[Tuple[SuiteConfig, FioJobConfig]]] |
| 192 | str_summary = {} # type: Dict[FioJobParams, List[IOSummary]] |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 193 | |
| 194 | for job in self.rstorage.iter_job(suite): |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 195 | fjob = cast(FioJobConfig, job) |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 196 | fjob_no_qd = cast(FioJobParams, fjob.params.copy(qd=None)) |
| 197 | str_summary[fjob_no_qd] = (fjob_no_qd.summary, fjob_no_qd.long_summary) |
| 198 | ts_map[fjob_no_qd].append((suite, fjob)) |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 199 | |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 200 | for tpl, suites_jobs in ts_map.items(): |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 201 | if len(suites_jobs) >= self.style.min_iops_vs_qd_jobs: |
| 202 | iosums = [make_iosum(self.rstorage, suite, job, self.style.hist_boxes) for suite, job in suites_jobs] |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 203 | iosums.sort(key=lambda x: x.qd) |
| 204 | summary, summary_long = str_summary[tpl] |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 205 | |
| 206 | yield Menu1st.summary, Menu2ndSumm.io_lat_qd, \ |
| 207 | HTMLBlock(html.H2(html.center("IOPS, BW, Lat = func(QD). " + summary_long))) |
| 208 | |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 209 | ds = DataSource(suite_id=suite.storage_id, |
| 210 | job_id=summary, |
| 211 | node_id=AGG_TAG, |
| 212 | sensor="fio", |
| 213 | dev=AGG_TAG, |
| 214 | metric="io_over_qd", |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 215 | tag=io_chart_format) |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 216 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 217 | fpath = self.plt(io_chart, ds, title="", legend="IOPS/BW", iosums=iosums) |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 218 | yield Menu1st.summary, Menu2ndSumm.io_lat_qd, HTMLBlock(html.center(html.img(fpath))) |
koder aka kdanilov | 7f59d56 | 2016-12-26 01:34:23 +0200 | [diff] [blame] | 219 | |
| 220 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 221 | class ResourceQD(SuiteReporter): |
| 222 | suite_types = {'fio'} |
| 223 | |
| 224 | def get_divs(self, suite: SuiteConfig) -> Iterator[Tuple[str, str, HTMLBlock]]: |
| 225 | qd_grouped_jobs = {} # type: Dict[FioJobParams, List[FioJobConfig]] |
| 226 | test_nc = len(list(find_nodes_by_roles(self.rstorage, ['testnode']))) |
| 227 | for job in self.rstorage.iter_job(suite): |
| 228 | fjob = cast(FioJobConfig, job) |
| 229 | if fjob.bsize != 4: |
| 230 | continue |
| 231 | |
| 232 | fjob_no_qd = cast(FioJobParams, fjob.params.copy(qd=None)) |
| 233 | qd_grouped_jobs.setdefault(fjob_no_qd, []).append(fjob) |
| 234 | |
| 235 | for jc_no_qd, jobs in sorted(qd_grouped_jobs.items()): |
| 236 | cpu_usage2qd = {} |
| 237 | for job in jobs: |
| 238 | usage, iops_ok = get_resources_usage(suite, job, self.rstorage, hist_boxes=self.style.hist_boxes, |
| 239 | large_block=self.style.large_blocks) |
| 240 | |
| 241 | if iops_ok: |
| 242 | cpu_usage2qd[job.qd] = usage[ResourceNames.storage_cpu_s] |
| 243 | |
| 244 | if len(cpu_usage2qd) < StyleProfile.min_iops_vs_qd_jobs: |
| 245 | continue |
| 246 | |
| 247 | labels, vals, errs = zip(*((l, avg, dev) for l, (_, avg, dev) in sorted(cpu_usage2qd.items()))) |
| 248 | |
| 249 | if test_nc == 1: |
| 250 | labels = list(map(str, labels)) |
| 251 | else: |
| 252 | labels = ["{} * {}".format(label, test_nc) for label in labels] |
| 253 | |
| 254 | ds = DataSource(suite_id=suite.storage_id, |
| 255 | job_id=jc_no_qd.summary, |
| 256 | node_id="cluster", |
| 257 | sensor=AGG_TAG, |
| 258 | dev='cpu', |
| 259 | metric="cpu_for_iop", |
| 260 | tag=io_chart_format) |
| 261 | |
| 262 | fpath = self.plt(plot_simple_bars, ds, jc_no_qd.long_summary, labels, vals, errs, |
| 263 | xlabel="CPU time per IOP", ylabel="QD * Test nodes" if test_nc != 1 else "QD", |
| 264 | x_formatter=(lambda x, pos: b2ssize_10(x) + 's'), |
| 265 | one_point_zero_line=False) |
| 266 | |
| 267 | yield Menu1st.summary, Menu2ndSumm.cpu_usage_qd, HTMLBlock(html.center(html.img(fpath))) |
koder aka kdanilov | 7f59d56 | 2016-12-26 01:34:23 +0200 | [diff] [blame] | 268 | |
| 269 | |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 270 | class StatInfo(JobReporter): |
| 271 | """Statistic info for job results""" |
| 272 | suite_types = {'fio'} |
| 273 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 274 | def get_divs(self, suite: SuiteConfig, job: JobConfig) -> Iterator[Tuple[str, str, HTMLBlock]]: |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 275 | |
| 276 | fjob = cast(FioJobConfig, job) |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 277 | io_sum = make_iosum(self.rstorage, suite, fjob, self.style.hist_boxes) |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 278 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 279 | caption = "Test summary - " + job.params.long_summary |
| 280 | test_nc = len(list(find_nodes_by_roles(self.rstorage, ['testnode']))) |
| 281 | if test_nc > 1: |
| 282 | caption += " * {} nodes".format(test_nc) |
| 283 | |
| 284 | res = html.H2(html.center(caption)) |
| 285 | stat_data_headers = ["Name", |
| 286 | "Total done", |
| 287 | "Average ~ Dev", |
| 288 | "Conf interval", |
| 289 | "Mediana", |
| 290 | "Mode", |
| 291 | "Kurt / Skew", |
| 292 | "95%", |
| 293 | "99%", |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 294 | "ADF test"] |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 295 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 296 | align = ['left'] + ['right'] * (len(stat_data_headers) - 1) |
| 297 | |
| 298 | bw_units = "B" |
| 299 | bw_target_units = bw_units + 'ps' |
kdanylov aka koder | 4518318 | 2017-04-30 23:55:40 +0300 | [diff] [blame] | 300 | bw_coef = float(unit_conversion_coef(io_sum.bw.units, bw_target_units)) |
| 301 | |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 302 | adf_v, *_1, stats, _2 = adfuller(io_sum.bw.data) |
| 303 | |
| 304 | for v in ("1%", "5%", "10%"): |
| 305 | if adf_v <= stats[v]: |
| 306 | ad_test = v |
| 307 | break |
| 308 | else: |
| 309 | ad_test = "Failed" |
| 310 | |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 311 | bw_data = ["Bandwidth", |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 312 | b2ssize(io_sum.bw.data.sum() * bw_coef) + bw_units, |
kdanylov aka koder | 4518318 | 2017-04-30 23:55:40 +0300 | [diff] [blame] | 313 | "{}{} ~ {}{}".format(b2ssize(io_sum.bw.average * bw_coef), bw_target_units, |
| 314 | b2ssize(io_sum.bw.deviation * bw_coef), bw_target_units), |
| 315 | b2ssize(io_sum.bw.confidence * bw_coef) + bw_target_units, |
| 316 | b2ssize(io_sum.bw.perc_50 * bw_coef) + bw_target_units, |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 317 | "-", |
| 318 | "{:.2f} / {:.2f}".format(io_sum.bw.kurt, io_sum.bw.skew), |
kdanylov aka koder | 4518318 | 2017-04-30 23:55:40 +0300 | [diff] [blame] | 319 | b2ssize(io_sum.bw.perc_5 * bw_coef) + bw_target_units, |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 320 | b2ssize(io_sum.bw.perc_1 * bw_coef) + bw_target_units, |
| 321 | ad_test] |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 322 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 323 | stat_data = [bw_data] |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 324 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 325 | if fjob.bsize < StyleProfile.large_blocks: |
| 326 | iops_coef = float(unit_conversion_coef(io_sum.bw.units, 'KiBps')) / fjob.bsize |
| 327 | iops_data = ["IOPS", |
| 328 | b2ssize_10(io_sum.bw.data.sum() * iops_coef), |
| 329 | "{}IOPS ~ {}IOPS".format(b2ssize_10(io_sum.bw.average * iops_coef), |
| 330 | b2ssize_10(io_sum.bw.deviation * iops_coef)), |
| 331 | b2ssize_10(io_sum.bw.confidence * iops_coef) + "IOPS", |
| 332 | b2ssize_10(io_sum.bw.perc_50 * iops_coef) + "IOPS", |
| 333 | "-", |
| 334 | "{:.2f} / {:.2f}".format(io_sum.bw.kurt, io_sum.bw.skew), |
| 335 | b2ssize_10(io_sum.bw.perc_5 * iops_coef) + "IOPS", |
| 336 | b2ssize_10(io_sum.bw.perc_1 * iops_coef) + "IOPS", |
| 337 | ad_test] |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 338 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 339 | lat_target_unit = 's' |
| 340 | lat_coef = unit_conversion_coef(io_sum.lat.units, lat_target_unit) |
| 341 | # latency |
| 342 | lat_data = ["Latency", |
| 343 | "-", |
| 344 | "-", |
| 345 | "-", |
| 346 | b2ssize_10(io_sum.lat.perc_50 * lat_coef) + lat_target_unit, |
| 347 | "-", |
| 348 | "-", |
| 349 | b2ssize_10(io_sum.lat.perc_95 * lat_coef) + lat_target_unit, |
| 350 | b2ssize_10(io_sum.lat.perc_99 * lat_coef) + lat_target_unit, |
| 351 | '-'] |
| 352 | |
| 353 | # sensor usage |
| 354 | stat_data.extend([iops_data, lat_data]) |
| 355 | |
| 356 | res += html.center(html.table("Load stats info", stat_data_headers, stat_data, align=align)) |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 357 | yield Menu1st.per_job, job.summary, HTMLBlock(res) |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 358 | |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 359 | |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 360 | class Resources(JobReporter): |
| 361 | """Statistic info for job results""" |
| 362 | suite_types = {'fio'} |
| 363 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 364 | def get_divs(self, suite: SuiteConfig, job: JobConfig) -> Iterator[Tuple[str, str, HTMLBlock]]: |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 365 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 366 | records, iops_ok = get_resources_usage(suite, job, self.rstorage, |
| 367 | large_block=self.style.large_blocks, |
| 368 | hist_boxes=self.style.hist_boxes) |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 369 | |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 370 | table_structure = [ |
| 371 | "Service provided", |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 372 | (ResourceNames.io_made, ResourceNames.data_tr), |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 373 | "Test nodes total load", |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 374 | (ResourceNames.test_send_pkt, ResourceNames.test_send), |
| 375 | (ResourceNames.test_recv_pkt, ResourceNames.test_recv), |
| 376 | (ResourceNames.test_net_pkt, ResourceNames.test_net), |
| 377 | (ResourceNames.test_write_iop, ResourceNames.test_write), |
| 378 | (ResourceNames.test_read_iop, ResourceNames.test_read), |
| 379 | (ResourceNames.test_iop, ResourceNames.test_rw), |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 380 | "Storage nodes resource consumed", |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 381 | (ResourceNames.storage_send_pkt, ResourceNames.storage_send), |
| 382 | (ResourceNames.storage_recv_pkt, ResourceNames.storage_recv), |
| 383 | (ResourceNames.storage_net_pkt, ResourceNames.storage_net), |
| 384 | (ResourceNames.storage_write_iop, ResourceNames.storage_write), |
| 385 | (ResourceNames.storage_read_iop, ResourceNames.storage_read), |
| 386 | (ResourceNames.storage_iop, ResourceNames.storage_rw), |
| 387 | (ResourceNames.storage_cpu_s, ResourceNames.storage_cpu_s_b), |
| 388 | ] # type: List[Union[str, Tuple[Optional[str], ...]] |
| 389 | |
| 390 | if not iops_ok: |
| 391 | table_structure2 = [] |
| 392 | for line in table_structure: |
| 393 | if isinstance(line, str): |
| 394 | table_structure2.append(line) |
| 395 | else: |
| 396 | assert len(line) == 2 |
| 397 | table_structure2.append((line[1],)) |
| 398 | table_structure = table_structure2 |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 399 | |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 400 | yield Menu1st.per_job, job.summary, HTMLBlock(html.H2(html.center("Resources usage"))) |
| 401 | |
| 402 | doc = xmlbuilder3.XMLBuilder("table", |
| 403 | **{"class": "table table-bordered table-striped table-condensed table-hover", |
| 404 | "style": "width: auto;"}) |
| 405 | |
| 406 | with doc.thead: |
| 407 | with doc.tr: |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 408 | [doc.th(header) for header in ["Resource", "Usage count", "To service"] * (2 if iops_ok else 1)] |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 409 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 410 | cols = 6 if iops_ok else 3 |
| 411 | col_per_tp = 3 |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 412 | |
| 413 | short_name = { |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 414 | name: (name if name in {ResourceNames.io_made, ResourceNames.data_tr} |
| 415 | else " ".join(name.split()[2:]).capitalize()) |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 416 | for name in records.keys() |
| 417 | } |
| 418 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 419 | short_name[ResourceNames.storage_cpu_s] = "CPU (s/IOP)" |
| 420 | short_name[ResourceNames.storage_cpu_s_b] = "CPU (s/B)" |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 421 | |
| 422 | with doc.tbody: |
| 423 | with doc.tr: |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 424 | if iops_ok: |
| 425 | doc.td(colspan=str(col_per_tp)).center.b("Operations") |
| 426 | doc.td(colspan=str(col_per_tp)).center.b("Bytes") |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 427 | |
| 428 | for line in table_structure: |
| 429 | with doc.tr: |
| 430 | if isinstance(line, str): |
| 431 | with doc.td(colspan=str(cols)): |
| 432 | doc.center.b(line) |
| 433 | else: |
| 434 | for name in line: |
| 435 | if name is None: |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 436 | doc.td("-", colspan=str(col_per_tp)) |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 437 | continue |
| 438 | |
| 439 | amount_s, avg, dev = records[name] |
| 440 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 441 | if name in (ResourceNames.storage_cpu_s, ResourceNames.storage_cpu_s_b) and avg is not None: |
| 442 | if dev is None: |
| 443 | rel_val_s = b2ssize_10(avg) + 's' |
| 444 | else: |
| 445 | dev_s = str(int(dev * 100 / avg)) + "%" if avg > 1E-9 else b2ssize_10(dev) + 's' |
| 446 | rel_val_s = "{}s ~ {}".format(b2ssize_10(avg), dev_s) |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 447 | else: |
| 448 | if avg is None: |
| 449 | rel_val_s = '-' |
| 450 | else: |
| 451 | avg_s = int(avg) if avg > 10 else '{:.1f}'.format(avg) |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 452 | if dev is None: |
| 453 | rel_val_s = avg_s |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 454 | else: |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 455 | if avg > 1E-5: |
| 456 | dev_s = str(int(dev * 100 / avg)) + "%" |
| 457 | else: |
| 458 | dev_s = int(dev) if dev > 10 else '{:.1f}'.format(dev) |
| 459 | rel_val_s = "{} ~ {}".format(avg_s, dev_s) |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 460 | |
| 461 | doc.td(short_name[name], align="left") |
| 462 | doc.td(amount_s, align="right") |
| 463 | |
| 464 | if avg is None or avg < 0.9: |
| 465 | doc.td(rel_val_s, align="right") |
| 466 | elif avg < 2.0: |
| 467 | doc.td(align="right").font(rel_val_s, color='green') |
| 468 | elif avg < 5.0: |
| 469 | doc.td(align="right").font(rel_val_s, color='orange') |
| 470 | else: |
| 471 | doc.td(align="right").font(rel_val_s, color='red') |
| 472 | |
| 473 | res = xmlbuilder3.tostr(doc).split("\n", 1)[1] |
| 474 | yield Menu1st.per_job, job.summary, HTMLBlock(html.center(res)) |
| 475 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 476 | iop_names = [ResourceNames.test_write_iop, ResourceNames.test_read_iop, ResourceNames.test_iop, |
| 477 | ResourceNames.storage_write_iop, ResourceNames.storage_read_iop, ResourceNames.storage_iop] |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 478 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 479 | bytes_names = [ResourceNames.test_write, ResourceNames.test_read, ResourceNames.test_rw, |
| 480 | ResourceNames.test_send, ResourceNames.test_recv, ResourceNames.test_net, |
| 481 | ResourceNames.storage_write, ResourceNames.storage_read, ResourceNames.storage_rw, |
| 482 | ResourceNames.storage_send, ResourceNames.storage_recv, ResourceNames.storage_net] |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 483 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 484 | net_pkt_names = [ResourceNames.test_send_pkt, ResourceNames.test_recv_pkt, ResourceNames.test_net_pkt, |
| 485 | ResourceNames.storage_send_pkt, ResourceNames.storage_recv_pkt, ResourceNames.storage_net_pkt] |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 486 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 487 | pairs = [("bytes", bytes_names)] |
| 488 | if iops_ok: |
| 489 | pairs.insert(0, ('iop', iop_names)) |
| 490 | pairs.append(('Net packets per IOP', net_pkt_names)) |
| 491 | |
| 492 | yield Menu1st.per_job, job.summary, \ |
| 493 | HTMLBlock(html.H2(html.center("Resource consumption per service provided"))) |
| 494 | |
| 495 | for tp, names in pairs: |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 496 | vals = [] |
| 497 | devs = [] |
| 498 | avail_names = [] |
| 499 | for name in names: |
| 500 | if name in records: |
| 501 | avail_names.append(name) |
| 502 | _, avg, dev = records[name] |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 503 | |
| 504 | if dev is None: |
| 505 | dev = 0 |
| 506 | |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 507 | vals.append(avg) |
| 508 | devs.append(dev) |
| 509 | |
| 510 | # synchronously sort values and names, values is a key |
| 511 | vals, names, devs = map(list, zip(*sorted(zip(vals, names, devs)))) |
| 512 | |
| 513 | ds = DataSource(suite_id=suite.storage_id, |
| 514 | job_id=job.storage_id, |
| 515 | node_id=AGG_TAG, |
| 516 | sensor='resources', |
| 517 | dev=AGG_TAG, |
| 518 | metric=tp.replace(' ', "_") + '2service_bar', |
| 519 | tag=default_format) |
| 520 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 521 | fname = self.plt(plot_simple_bars, ds, tp.capitalize(), |
| 522 | [name.replace(" nodes", "") for name in names], |
| 523 | vals, devs) |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 524 | |
| 525 | yield Menu1st.per_job, job.summary, HTMLBlock(html.img(fname)) |
| 526 | |
| 527 | |
| 528 | class BottleNeck(JobReporter): |
| 529 | """Statistic info for job results""" |
| 530 | suite_types = {'fio'} |
| 531 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 532 | def get_divs(self, suite: SuiteConfig, job: JobConfig) -> Iterator[Tuple[str, str, HTMLBlock]]: |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 533 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 534 | nodes = list(find_nodes_by_roles(self.rstorage, STORAGE_ROLES)) |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 535 | |
| 536 | sensor = 'block-io' |
| 537 | metric = 'io_queue' |
| 538 | bn_val = 16 |
| 539 | |
| 540 | for node in nodes: |
| 541 | bn = 0 |
| 542 | tot = 0 |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 543 | for _, ds in self.rstorage.iter_sensors(node_id=node.node_id, sensor=sensor, metric=metric): |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 544 | if ds.dev in ('sdb', 'sdc', 'sdd', 'sde'): |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 545 | data = self.rstorage.load_sensor(ds) |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 546 | p1 = job.reliable_info_range_s[0] * unit_conversion_coef('s', data.time_units) |
| 547 | p2 = job.reliable_info_range_s[1] * unit_conversion_coef('s', data.time_units) |
| 548 | idx1, idx2 = numpy.searchsorted(data.times, (p1, p2)) |
| 549 | bn += (data.data[idx1: idx2] > bn_val).sum() |
| 550 | tot += idx2 - idx1 |
| 551 | print(node, bn, tot) |
| 552 | |
| 553 | yield Menu1st.per_job, job.summary, HTMLBlock("") |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 554 | |
| 555 | |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 556 | # CPU load |
| 557 | class CPULoadPlot(JobReporter): |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 558 | def get_divs(self, suite: SuiteConfig, job: JobConfig) -> Iterator[Tuple[str, str, HTMLBlock]]: |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 559 | |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 560 | # plot CPU time |
| 561 | for rt, roles in [('storage', STORAGE_ROLES), ('test', ['testnode'])]: |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 562 | cpu_ts = get_cluster_cpu_load(self.rstorage, roles, job.reliable_info_range_s) |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 563 | tss = [(name, ts.data * 100 / cpu_ts['total'].data) |
| 564 | for name, ts in cpu_ts.items() |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 565 | if name in {'user', 'sys', 'idle', 'iowait'}] |
| 566 | |
| 567 | |
| 568 | ds = cpu_ts['idle'].source(job_id=job.storage_id, suite_id=suite.storage_id, |
| 569 | node_id=AGG_TAG, metric='allcpu', tag=rt + '.plt.' + default_format) |
| 570 | |
| 571 | fname = self.plt(plot_simple_over_time, ds, tss=tss, average=True, ylabel="CPU time %", |
| 572 | title="{} nodes CPU usage".format(rt.capitalize()), |
| 573 | xlabel="Time from test begin") |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 574 | |
| 575 | yield Menu1st.per_job, job.summary, HTMLBlock(html.img(fname)) |
| 576 | |
| 577 | |
| 578 | # IO time and QD |
| 579 | class QDIOTimeHeatmap(JobReporter): |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 580 | def get_divs(self, suite: SuiteConfig, job: JobConfig) -> Iterator[Tuple[str, str, HTMLBlock]]: |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 581 | |
| 582 | # TODO: fix this hardcode, need to track what devices are actually used on test and storage nodes |
| 583 | # use saved storage info in nodes |
| 584 | |
| 585 | journal_devs = None |
| 586 | storage_devs = None |
| 587 | test_nodes_devs = ['rbd0'] |
| 588 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 589 | for node in find_nodes_by_roles(self.rstorage, STORAGE_ROLES): |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 590 | cjd = set(node.params['ceph_journal_devs']) |
| 591 | if journal_devs is None: |
| 592 | journal_devs = cjd |
| 593 | else: |
| 594 | assert journal_devs == cjd, "{!r} != {!r}".format(journal_devs, cjd) |
| 595 | |
| 596 | csd = set(node.params['ceph_storage_devs']) |
| 597 | if storage_devs is None: |
| 598 | storage_devs = csd |
| 599 | else: |
| 600 | assert storage_devs == csd, "{!r} != {!r}".format(storage_devs, csd) |
| 601 | |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 602 | trange = (job.reliable_info_range[0] // 1000, job.reliable_info_range[1] // 1000) |
| 603 | |
| 604 | for name, devs, roles in [('storage', storage_devs, STORAGE_ROLES), |
| 605 | ('journal', journal_devs, STORAGE_ROLES), |
| 606 | ('test', test_nodes_devs, ['testnode'])]: |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 607 | |
| 608 | yield Menu1st.per_job, job.summary, \ |
| 609 | HTMLBlock(html.H2(html.center("{} IO heatmaps".format(name.capitalize())))) |
| 610 | |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 611 | # QD heatmap |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 612 | ioq2d = find_sensors_to_2d(self.rstorage, roles, sensor='block-io', devs=devs, |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 613 | metric='io_queue', time_range=trange) |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 614 | |
| 615 | ds = DataSource(suite.storage_id, job.storage_id, AGG_TAG, 'block-io', name, tag="hmap." + default_format) |
| 616 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 617 | fname = self.plt(plot_hmap_from_2d, ds(metric='io_queue'), data2d=ioq2d, xlabel='Time', ylabel="IO QD", |
| 618 | title=name.capitalize() + " devs QD", bins=StyleProfile.qd_bins) |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 619 | yield Menu1st.per_job, job.summary, HTMLBlock(html.img(fname)) |
| 620 | |
| 621 | # Block size heatmap |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 622 | wc2d = find_sensors_to_2d(self.rstorage, roles, sensor='block-io', devs=devs, |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 623 | metric='writes_completed', time_range=trange) |
| 624 | wc2d[wc2d < 1E-3] = 1 |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 625 | sw2d = find_sensors_to_2d(self.rstorage, roles, sensor='block-io', devs=devs, |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 626 | metric='sectors_written', time_range=trange) |
| 627 | data2d = sw2d / wc2d / 1024 |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 628 | fname = self.plt(plot_hmap_from_2d, ds(metric='wr_block_size'), |
| 629 | data2d=data2d, title=name.capitalize() + " write block size", |
| 630 | ylabel="IO bsize, KiB", xlabel='Time', bins=StyleProfile.block_size_bins) |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 631 | yield Menu1st.per_job, job.summary, HTMLBlock(html.img(fname)) |
| 632 | |
| 633 | # iotime heatmap |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 634 | wtime2d = find_sensors_to_2d(self.rstorage, roles, sensor='block-io', devs=devs, |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 635 | metric='io_time', time_range=trange) |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 636 | fname = self.plt(plot_hmap_from_2d, ds(metric='io_time'), data2d=wtime2d, |
| 637 | xlabel='Time', ylabel="IO time (ms) per second", |
| 638 | title=name.capitalize() + " iotime", bins=StyleProfile.iotime_bins) |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 639 | yield Menu1st.per_job, job.summary, HTMLBlock(html.img(fname)) |
| 640 | |
| 641 | |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 642 | # IOPS/latency over test time for each job |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 643 | class LoadToolResults(JobReporter): |
koder aka kdanilov | 7f59d56 | 2016-12-26 01:34:23 +0200 | [diff] [blame] | 644 | """IOPS/latency during test""" |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 645 | suite_types = {'fio'} |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 646 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 647 | def get_divs(self, suite: SuiteConfig, job: JobConfig) -> Iterator[Tuple[str, str, HTMLBlock]]: |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 648 | |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 649 | fjob = cast(FioJobConfig, job) |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 650 | |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 651 | yield Menu1st.per_job, job.summary, HTMLBlock(html.H2(html.center("Load tool results"))) |
| 652 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 653 | agg_io = get_aggregated(self.rstorage, suite, fjob, "bw") |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 654 | if fjob.bsize >= DefStyleProfile.large_blocks: |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 655 | title = "Fio measured Bandwidth over time" |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 656 | units = "MiBps" |
kdanylov aka koder | 4518318 | 2017-04-30 23:55:40 +0300 | [diff] [blame] | 657 | agg_io.data //= int(unit_conversion_coef(units, agg_io.units)) |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 658 | else: |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 659 | title = "Fio measured IOPS over time" |
kdanylov aka koder | 4518318 | 2017-04-30 23:55:40 +0300 | [diff] [blame] | 660 | agg_io.data //= (int(unit_conversion_coef("KiBps", agg_io.units)) * fjob.bsize) |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 661 | units = "IOPS" |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 662 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 663 | fpath = self.plt(plot_v_over_time, agg_io.source(tag='ts.' + default_format), title, units, agg_io) |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 664 | yield Menu1st.per_job, fjob.summary, HTMLBlock(html.img(fpath)) |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 665 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 666 | if fjob.bsize < DefStyleProfile.large_blocks: |
| 667 | agg_lat = get_aggregated(self.rstorage, suite, fjob, "lat").copy() |
| 668 | TARGET_UNITS = 'ms' |
| 669 | coef = unit_conversion_coef(agg_lat.units, TARGET_UNITS) |
| 670 | agg_lat.histo_bins = agg_lat.histo_bins.copy() * float(coef) |
| 671 | agg_lat.units = TARGET_UNITS |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 672 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 673 | fpath = self.plt(plot_lat_over_time, agg_lat.source(tag='ts.' + default_format), "Latency", agg_lat, |
| 674 | ylabel="Latency, " + agg_lat.units) |
| 675 | yield Menu1st.per_job, fjob.summary, HTMLBlock(html.img(fpath)) |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 676 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 677 | fpath = self.plt(plot_histo_heatmap, agg_lat.source(tag='hmap.' + default_format), |
| 678 | "Latency heatmap", agg_lat, ylabel="Latency, " + agg_lat.units, xlabel='Test time') |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 679 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 680 | yield Menu1st.per_job, fjob.summary, HTMLBlock(html.img(fpath)) |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 681 | |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 682 | fjob = cast(FioJobConfig, job) |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 683 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 684 | agg_io = get_aggregated(self.rstorage, suite, fjob, "bw") |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 685 | |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 686 | if fjob.bsize >= DefStyleProfile.large_blocks: |
| 687 | title = "BW distribution" |
| 688 | units = "MiBps" |
| 689 | agg_io.data //= int(unit_conversion_coef(units, agg_io.units)) |
| 690 | else: |
| 691 | title = "IOPS distribution" |
| 692 | agg_io.data //= (int(unit_conversion_coef("KiBps", agg_io.units)) * fjob.bsize) |
| 693 | units = "IOPS" |
| 694 | |
| 695 | io_stat_prop = calc_norm_stat_props(agg_io, bins_count=StyleProfile.hist_boxes) |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 696 | fpath = self.plt(plot_hist, agg_io.source(tag='hist.' + default_format), title, units, io_stat_prop) |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 697 | yield Menu1st.per_job, fjob.summary, HTMLBlock(html.img(fpath)) |
koder aka kdanilov | 7f59d56 | 2016-12-26 01:34:23 +0200 | [diff] [blame] | 698 | |
| 699 | |
| 700 | # Cluster load over test time |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 701 | class ClusterLoad(JobReporter): |
koder aka kdanilov | 7f59d56 | 2016-12-26 01:34:23 +0200 | [diff] [blame] | 702 | """IOPS/latency during test""" |
| 703 | |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 704 | # TODO: units should came from sensor |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 705 | storage_sensors = [ |
kdanylov aka koder | 4518318 | 2017-04-30 23:55:40 +0300 | [diff] [blame] | 706 | ('block-io', 'reads_completed', "Read", 'iop'), |
| 707 | ('block-io', 'writes_completed', "Write", 'iop'), |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 708 | ('block-io', 'sectors_read', "Read", 'MiB'), |
| 709 | ('block-io', 'sectors_written', "Write", 'MiB'), |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 710 | ] |
| 711 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 712 | def get_divs(self, suite: SuiteConfig, job: JobConfig) -> Iterator[Tuple[str, str, HTMLBlock]]: |
| 713 | |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 714 | yield Menu1st.per_job, job.summary, HTMLBlock(html.H2(html.center("Cluster load"))) |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 715 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 716 | sensors = [] |
| 717 | max_iop = 0 |
| 718 | max_bytes = 0 |
| 719 | |
kdanylov aka koder | 4518318 | 2017-04-30 23:55:40 +0300 | [diff] [blame] | 720 | for sensor, metric, op, units in self.storage_sensors: |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 721 | ts = summ_sensors(self.rstorage, STORAGE_ROLES, sensor, metric, job.reliable_info_range_s) |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 722 | ds = DataSource(suite_id=suite.storage_id, |
| 723 | job_id=job.storage_id, |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 724 | node_id="storage", |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 725 | sensor=sensor, |
| 726 | dev=AGG_TAG, |
| 727 | metric=metric, |
kdanylov aka koder | 4e4af68 | 2017-05-01 01:52:14 +0300 | [diff] [blame] | 728 | tag="ts." + default_format) |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 729 | |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 730 | data = ts.data if units != 'MiB' else ts.data * float(unit_conversion_coef(ts.units, 'MiB')) |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 731 | ts = TimeSeries(name="", |
kdanylov aka koder | 4518318 | 2017-04-30 23:55:40 +0300 | [diff] [blame] | 732 | times=numpy.arange(*job.reliable_info_range_s), |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 733 | data=data, |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 734 | raw=None, |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 735 | units=units if ts.units is None else ts.units, |
| 736 | time_units=ts.time_units, |
| 737 | source=ds, |
| 738 | histo_bins=ts.histo_bins) |
kdanylov aka koder | 0e0cfcb | 2017-03-27 22:19:09 +0300 | [diff] [blame] | 739 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 740 | sensors.append(("{} {}".format(op, units), ds, ts, units)) |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 741 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 742 | if units == 'iop': |
| 743 | max_iop = max(max_iop, data.sum()) |
| 744 | else: |
| 745 | assert units == 'MiB' |
| 746 | max_bytes = max(max_bytes, data.sum()) |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 747 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 748 | for title, ds, ts, units in sensors: |
| 749 | if ts.data.sum() >= (max_iop if units == 'iop' else max_bytes) * DefStyleProfile.min_load_diff: |
| 750 | fpath = self.plt(plot_v_over_time, ds, title, units, ts=ts) |
| 751 | yield Menu1st.per_job, job.summary, HTMLBlock(html.img(fpath)) |
| 752 | else: |
| 753 | logger.info("Hide '%s' plot for %s, as it's cum load is less then %s%%", |
| 754 | title, job.summary, int(DefStyleProfile.min_load_diff * 100)) |
koder aka kdanilov | 7f59d56 | 2016-12-26 01:34:23 +0200 | [diff] [blame] | 755 | |
| 756 | |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 757 | # ------------------------------------------ REPORT STAGES ----------------------------------------------------------- |
| 758 | |
| 759 | |
| 760 | class HtmlReportStage(Stage): |
| 761 | priority = StepOrder.REPORT |
| 762 | |
| 763 | def run(self, ctx: TestRun) -> None: |
| 764 | rstorage = ResultStorage(ctx.storage) |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 765 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 766 | job_reporters_cls = [StatInfo, Resources, LoadToolResults, ClusterLoad, CPULoadPlot, QDIOTimeHeatmap] |
| 767 | job_reporters = [rcls(rstorage, DefStyleProfile, DefColorProfile) for rcls in job_reporters_cls] |
| 768 | |
| 769 | suite_reporters_cls = [IOQD, ResourceQD] |
| 770 | suite_reporters = [rcls(rstorage, DefStyleProfile, DefColorProfile) for rcls in suite_reporters_cls] |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 771 | |
| 772 | root_dir = os.path.dirname(os.path.dirname(wally.__file__)) |
| 773 | doc_templ_path = os.path.join(root_dir, "report_templates/index.html") |
| 774 | report_template = open(doc_templ_path, "rt").read() |
| 775 | css_file_src = os.path.join(root_dir, "report_templates/main.css") |
| 776 | css_file = open(css_file_src, "rt").read() |
| 777 | |
| 778 | menu_block = [] |
| 779 | content_block = [] |
| 780 | link_idx = 0 |
| 781 | |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 782 | # matplotlib.rcParams.update(ctx.config.reporting.matplotlib_params.raw()) |
| 783 | # ColorProfile.__dict__.update(ctx.config.reporting.colors.raw()) |
| 784 | # StyleProfile.__dict__.update(ctx.config.reporting.style.raw()) |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 785 | |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 786 | items = defaultdict(lambda: defaultdict(list)) # type: Dict[str, Dict[str, List[HTMLBlock]]] |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 787 | DEBUG = False |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 788 | job_summ_sort_order = [] |
| 789 | |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 790 | # TODO: filter reporters |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 791 | for suite in rstorage.iter_suite(FioTest.name): |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 792 | all_jobs = list(rstorage.iter_job(suite)) |
| 793 | all_jobs.sort(key=lambda job: job.params) |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 794 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 795 | new_jobs_in_order = [job.summary for job in all_jobs] |
| 796 | same = set(new_jobs_in_order).intersection(set(job_summ_sort_order)) |
| 797 | assert not same, "Job with same names in different suits found: " + ",".join(same) |
| 798 | job_summ_sort_order.extend(new_jobs_in_order) |
| 799 | |
| 800 | for job in all_jobs[:1]: |
| 801 | try: |
| 802 | for reporter in job_reporters: |
| 803 | logger.debug("Start reporter %s on job %s suite %s", |
| 804 | reporter.__class__.__name__, job.summary, suite.test_type) |
| 805 | for block, item, html in reporter.get_divs(suite, job): |
| 806 | items[block][item].append(html) |
| 807 | if DEBUG: |
| 808 | break |
| 809 | except Exception: |
| 810 | logger.exception("Failed to generate report for %s", job.summary) |
| 811 | |
| 812 | for reporter in suite_reporters: |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 813 | try: |
| 814 | logger.debug("Start reporter %s on suite %s", reporter.__class__.__name__, suite.test_type) |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 815 | for block, item, html in reporter.get_divs(suite): |
kdanylov aka koder | 736e5c1 | 2017-05-07 17:27:14 +0300 | [diff] [blame] | 816 | items[block][item].append(html) |
| 817 | except Exception as exc: |
| 818 | logger.exception("Failed to generate report") |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 819 | |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 820 | if DEBUG: |
| 821 | break |
| 822 | |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 823 | logger.debug("Generating result html") |
| 824 | |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 825 | for idx_1st, menu_1st in enumerate(sorted(items, key=lambda x: menu_1st_order.index(x))): |
| 826 | menu_block.append( |
| 827 | '<a href="#item{}" class="nav-group" data-toggle="collapse" data-parent="#MainMenu">{}</a>' |
| 828 | .format(idx_1st, menu_1st) |
| 829 | ) |
| 830 | menu_block.append('<div class="collapse" id="item{}">'.format(idx_1st)) |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 831 | |
| 832 | if menu_1st == Menu1st.per_job: |
| 833 | in_order = sorted(items[menu_1st], key=job_summ_sort_order.index) |
| 834 | else: |
| 835 | in_order = sorted(items[menu_1st]) |
| 836 | |
| 837 | for menu_2nd in in_order: |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 838 | menu_block.append(' <a href="#content{}" class="nav-group-item">{}</a>' |
| 839 | .format(link_idx, menu_2nd)) |
| 840 | content_block.append('<div id="content{}">'.format(link_idx)) |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 841 | content_block.extend(" " + x.data for x in items[menu_1st][menu_2nd]) |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 842 | content_block.append('</div>') |
| 843 | link_idx += 1 |
| 844 | menu_block.append('</div>') |
| 845 | |
| 846 | report = report_template.replace("{{{menu}}}", ("\n" + " " * 16).join(menu_block)) |
| 847 | report = report.replace("{{{content}}}", ("\n" + " " * 16).join(content_block)) |
| 848 | report_path = rstorage.put_report(report, "index.html") |
| 849 | rstorage.put_report(css_file, "main.css") |
| 850 | logger.info("Report is stored into %r", report_path) |