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