blob: d37ac60f2e090dbba21fc306aede712f8e5a6d8c [file] [log] [blame]
koder aka kdanilov108ac362017-01-19 20:17:16 +02001import os
koder aka kdanilov7f59d562016-12-26 01:34:23 +02002import abc
koder aka kdanilova047e1b2015-04-21 23:16:59 +03003import logging
koder aka kdanilov108ac362017-01-19 20:17:16 +02004from collections import defaultdict
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +03005from typing import Dict, Any, Iterator, Tuple, cast, List, Set, Optional, Union
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +03006
koder aka kdanilovffaf48d2016-12-27 02:25:29 +02007import numpy
kdanylov aka koder736e5c12017-05-07 17:27:14 +03008from statsmodels.tsa.stattools import adfuller
kdanylov aka kodercdfcdaf2017-04-29 10:03:39 +03009
kdanylov aka koder736e5c12017-05-07 17:27:14 +030010import xmlbuilder3
koder aka kdanilovbe8f89f2015-04-28 14:51:51 +030011
koder aka kdanilov108ac362017-01-19 20:17:16 +020012import wally
koder aka kdanilovffaf48d2016-12-27 02:25:29 +020013
kdanylov aka koderb0833332017-05-13 20:39:17 +030014from cephlib import html
15from cephlib.units import b2ssize, b2ssize_10, unit_conversion_coef, unit_conversion_coef_f
16from cephlib.statistic import calc_norm_stat_props
17from cephlib.storage_selectors import summ_sensors, find_sensors_to_2d
18from cephlib.wally_storage import find_nodes_by_roles
19
20from .utils import STORAGE_ROLES
koder aka kdanilov39e449e2016-12-17 15:15:26 +020021from .stage import Stage, StepOrder
22from .test_run_class import TestRun
kdanylov aka koderb0833332017-05-13 20:39:17 +030023from .result_classes import IResultStorage
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +030024from .result_classes import DataSource, TimeSeries, SuiteConfig
koder aka kdanilov108ac362017-01-19 20:17:16 +020025from .suits.io.fio import FioTest, FioJobConfig
koder aka kdanilova732a602017-02-01 20:29:56 +020026from .suits.io.fio_job import FioJobParams
27from .suits.job import JobConfig
kdanylov aka koderb0833332017-05-13 20:39:17 +030028from .data_selectors import get_aggregated, AGG_TAG
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +030029from .report_profiles import (DefStyleProfile, DefColorProfile, StyleProfile, ColorProfile,
30 default_format, io_chart_format)
31from .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)
33from .resources import ResourceNames, get_resources_usage, make_iosum, IOSummary, get_cluster_cpu_load
koder aka kdanilov962ee5f2016-12-19 02:40:08 +020034logger = logging.getLogger("wally")
koder aka kdanilova047e1b2015-04-21 23:16:59 +030035
36
koder aka kdanilov108ac362017-01-19 20:17:16 +020037# ---------------- CONSTS ---------------------------------------------------------------------------------------------
koder aka kdanilov39e449e2016-12-17 15:15:26 +020038
koder aka kdanilov7f59d562016-12-26 01:34:23 +020039
koder aka kdanilov108ac362017-01-19 20:17:16 +020040DEBUG = False
koder aka kdanilov39e449e2016-12-17 15:15:26 +020041
koder aka kdanilov39e449e2016-12-17 15:15:26 +020042
koder aka kdanilov108ac362017-01-19 20:17:16 +020043# ---------------- STRUCTS -------------------------------------------------------------------------------------------
koder aka kdanilov39e449e2016-12-17 15:15:26 +020044
koder aka kdanilov7f59d562016-12-26 01:34:23 +020045
46# TODO: need to be revised, have to user StatProps fields instead
47class StoragePerfSummary:
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +030048 def __init__(self) -> None:
koder aka kdanilov7f59d562016-12-26 01:34:23 +020049 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 kdanilov108ac362017-01-19 20:17:16 +020068# -------------- AGGREGATION AND STAT FUNCTIONS ----------------------------------------------------------------------
koder aka kdanilov108ac362017-01-19 20:17:16 +020069
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +030070LEVEL_SENSORS = {("block-io", "io_queue"), ("system-cpu", "procs_blocked"), ("system-cpu", "procs_queue")}
koder aka kdanilova732a602017-02-01 20:29:56 +020071
72
73def 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
78def 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 koder736e5c12017-05-07 17:27:14 +030082
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +030083# 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 koder736e5c12017-05-07 17:27:14 +030089
koder aka kdanilov108ac362017-01-19 20:17:16 +020090
91# -------------------- REPORT HELPERS --------------------------------------------------------------------------------
92
93
koder aka kdanilov7f59d562016-12-26 01:34:23 +020094class HTMLBlock:
95 data = None # type: str
96 js_links = [] # type: List[str]
97 css_links = [] # type: List[str]
koder aka kdanilova732a602017-02-01 20:29:56 +020098 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 koder45183182017-04-30 23:55:40 +0300104 def __eq__(self, o: Any) -> bool:
koder aka kdanilova732a602017-02-01 20:29:56 +0200105 return o.order_attr == self.order_attr # type: ignore
106
kdanylov aka koder45183182017-04-30 23:55:40 +0300107 def __lt__(self, o: Any) -> bool:
koder aka kdanilova732a602017-02-01 20:29:56 +0200108 return o.order_attr > self.order_attr # type: ignore
109
110
111class 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 kdanilov7f59d562016-12-26 01:34:23 +0200121
122
koder aka kdanilov108ac362017-01-19 20:17:16 +0200123class Menu1st:
124 engineering = "Engineering"
125 summary = "Summary"
koder aka kdanilova732a602017-02-01 20:29:56 +0200126 per_job = "Per Job"
koder aka kdanilov108ac362017-01-19 20:17:16 +0200127
128
129class Menu2ndEng:
130 iops_time = "IOPS(time)"
131 hist = "IOPS/lat overall histogram"
132 lat_time = "Lat(time)"
133
134
135class Menu2ndSumm:
136 io_lat_qd = "IO & Lat vs QD"
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300137 cpu_usage_qd = "CPU usage"
koder aka kdanilov108ac362017-01-19 20:17:16 +0200138
139
koder aka kdanilova732a602017-02-01 20:29:56 +0200140menu_1st_order = [Menu1st.summary, Menu1st.engineering, Menu1st.per_job]
koder aka kdanilov108ac362017-01-19 20:17:16 +0200141
142
143# -------------------- REPORTS --------------------------------------------------------------------------------------
144
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300145class ReporterBase:
kdanylov aka koderb0833332017-05-13 20:39:17 +0300146 def __init__(self, rstorage: IResultStorage, style: StyleProfile, colors: ColorProfile) -> None:
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300147 self.style = style
148 self.colors = colors
149 self.rstorage = rstorage
koder aka kdanilov108ac362017-01-19 20:17:16 +0200150
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300151 def plt(self, func, ds: DataSource, *args, **kwargs) -> str:
152 return func(self.rstorage, self.style, self.colors, ds, *args, **kwargs)
153
154
155class SuiteReporter(ReporterBase, metaclass=abc.ABCMeta):
156 suite_types = set() # type: Set[str]
koder aka kdanilova732a602017-02-01 20:29:56 +0200157
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200158 @abc.abstractmethod
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300159 def get_divs(self, suite: SuiteConfig) -> Iterator[Tuple[str, str, HTMLBlock]]:
koder aka kdanilova732a602017-02-01 20:29:56 +0200160 pass
161
162
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300163class JobReporter(ReporterBase, metaclass=abc.ABCMeta):
koder aka kdanilova732a602017-02-01 20:29:56 +0200164 suite_type = set() # type: Set[str]
165
166 @abc.abstractmethod
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300167 def get_divs(self, suite: SuiteConfig, job: JobConfig) -> Iterator[Tuple[str, str, HTMLBlock]]:
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200168 pass
169
170
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300171# # 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 kdanilov7f59d562016-12-26 01:34:23 +0200187
188
189# Main performance report
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300190class IOQD(SuiteReporter):
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200191 """Creates graph, which show how IOPS and Latency depend on QD"""
koder aka kdanilova732a602017-02-01 20:29:56 +0200192 suite_types = {'fio'}
193
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300194 def get_divs(self, suite: SuiteConfig) -> Iterator[Tuple[str, str, HTMLBlock]]:
koder aka kdanilova732a602017-02-01 20:29:56 +0200195 ts_map = defaultdict(list) # type: Dict[FioJobParams, List[Tuple[SuiteConfig, FioJobConfig]]]
196 str_summary = {} # type: Dict[FioJobParams, List[IOSummary]]
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300197
198 for job in self.rstorage.iter_job(suite):
koder aka kdanilov108ac362017-01-19 20:17:16 +0200199 fjob = cast(FioJobConfig, job)
koder aka kdanilova732a602017-02-01 20:29:56 +0200200 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 kdanilov108ac362017-01-19 20:17:16 +0200203
koder aka kdanilova732a602017-02-01 20:29:56 +0200204 for tpl, suites_jobs in ts_map.items():
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300205 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 kdanilova732a602017-02-01 20:29:56 +0200207 iosums.sort(key=lambda x: x.qd)
208 summary, summary_long = str_summary[tpl]
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300209
210 yield Menu1st.summary, Menu2ndSumm.io_lat_qd, \
211 HTMLBlock(html.H2(html.center("IOPS, BW, Lat = func(QD). " + summary_long)))
212
koder aka kdanilova732a602017-02-01 20:29:56 +0200213 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 koder736e5c12017-05-07 17:27:14 +0300219 tag=io_chart_format)
koder aka kdanilov108ac362017-01-19 20:17:16 +0200220
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300221 fpath = self.plt(io_chart, ds, title="", legend="IOPS/BW", iosums=iosums)
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300222 yield Menu1st.summary, Menu2ndSumm.io_lat_qd, HTMLBlock(html.center(html.img(fpath)))
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200223
224
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300225class 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 koderb0833332017-05-13 20:39:17 +0300230 test_nc = len(list(find_nodes_by_roles(self.rstorage.storage, ['testnode'])))
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300231 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 koderb0833332017-05-13 20:39:17 +0300267 xlabel="CPU core time per IOP", ylabel="QD * Test nodes" if test_nc != 1 else "QD",
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300268 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 kdanilov7f59d562016-12-26 01:34:23 +0200272
273
koder aka kdanilova732a602017-02-01 20:29:56 +0200274class StatInfo(JobReporter):
275 """Statistic info for job results"""
276 suite_types = {'fio'}
277
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300278 def get_divs(self, suite: SuiteConfig, job: JobConfig) -> Iterator[Tuple[str, str, HTMLBlock]]:
koder aka kdanilova732a602017-02-01 20:29:56 +0200279
280 fjob = cast(FioJobConfig, job)
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300281 io_sum = make_iosum(self.rstorage, suite, fjob, self.style.hist_boxes)
koder aka kdanilova732a602017-02-01 20:29:56 +0200282
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300283 caption = "Test summary - " + job.params.long_summary
kdanylov aka koderb0833332017-05-13 20:39:17 +0300284 test_nc = len(list(find_nodes_by_roles(self.rstorage.storage, ['testnode'])))
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300285 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 koder736e5c12017-05-07 17:27:14 +0300298 "ADF test"]
koder aka kdanilova732a602017-02-01 20:29:56 +0200299
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300300 align = ['left'] + ['right'] * (len(stat_data_headers) - 1)
301
302 bw_units = "B"
303 bw_target_units = bw_units + 'ps'
kdanylov aka koderb0833332017-05-13 20:39:17 +0300304 bw_coef = unit_conversion_coef_f(io_sum.bw.units, bw_target_units)
kdanylov aka koder45183182017-04-30 23:55:40 +0300305
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300306 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 kdanilova732a602017-02-01 20:29:56 +0200315 bw_data = ["Bandwidth",
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300316 b2ssize(io_sum.bw.data.sum() * bw_coef) + bw_units,
kdanylov aka koder45183182017-04-30 23:55:40 +0300317 "{}{} ~ {}{}".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 kdanilova732a602017-02-01 20:29:56 +0200321 "-",
322 "{:.2f} / {:.2f}".format(io_sum.bw.kurt, io_sum.bw.skew),
kdanylov aka koder45183182017-04-30 23:55:40 +0300323 b2ssize(io_sum.bw.perc_5 * bw_coef) + bw_target_units,
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300324 b2ssize(io_sum.bw.perc_1 * bw_coef) + bw_target_units,
325 ad_test]
koder aka kdanilova732a602017-02-01 20:29:56 +0200326
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300327 stat_data = [bw_data]
koder aka kdanilova732a602017-02-01 20:29:56 +0200328
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300329 if fjob.bsize < StyleProfile.large_blocks:
kdanylov aka koderb0833332017-05-13 20:39:17 +0300330 iops_coef = unit_conversion_coef_f(io_sum.bw.units, 'KiBps') / fjob.bsize
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300331 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 kdanilova732a602017-02-01 20:29:56 +0200342
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300343 lat_target_unit = 's'
kdanylov aka koderb0833332017-05-13 20:39:17 +0300344 lat_coef = unit_conversion_coef_f(io_sum.lat.units, lat_target_unit)
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300345 # 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 koder736e5c12017-05-07 17:27:14 +0300361 yield Menu1st.per_job, job.summary, HTMLBlock(res)
koder aka kdanilova732a602017-02-01 20:29:56 +0200362
koder aka kdanilova732a602017-02-01 20:29:56 +0200363
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300364class Resources(JobReporter):
365 """Statistic info for job results"""
366 suite_types = {'fio'}
367
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300368 def get_divs(self, suite: SuiteConfig, job: JobConfig) -> Iterator[Tuple[str, str, HTMLBlock]]:
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300369
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300370 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 kdanilova732a602017-02-01 20:29:56 +0200373
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300374 table_structure = [
375 "Service provided",
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300376 (ResourceNames.io_made, ResourceNames.data_tr),
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300377 "Test nodes total load",
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300378 (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 koder736e5c12017-05-07 17:27:14 +0300384 "Storage nodes resource consumed",
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300385 (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 kdanilova732a602017-02-01 20:29:56 +0200403
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300404 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 koder3a9e5db2017-05-09 20:00:44 +0300412 [doc.th(header) for header in ["Resource", "Usage count", "To service"] * (2 if iops_ok else 1)]
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300413
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300414 cols = 6 if iops_ok else 3
415 col_per_tp = 3
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300416
417 short_name = {
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300418 name: (name if name in {ResourceNames.io_made, ResourceNames.data_tr}
419 else " ".join(name.split()[2:]).capitalize())
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300420 for name in records.keys()
421 }
422
kdanylov aka koderb0833332017-05-13 20:39:17 +0300423 short_name[ResourceNames.storage_cpu_s] = "CPU core (s/IOP)"
424 short_name[ResourceNames.storage_cpu_s_b] = "CPU core (s/B)"
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300425
426 with doc.tbody:
427 with doc.tr:
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300428 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 koder736e5c12017-05-07 17:27:14 +0300431
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 koder3a9e5db2017-05-09 20:00:44 +0300440 doc.td("-", colspan=str(col_per_tp))
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300441 continue
442
443 amount_s, avg, dev = records[name]
444
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300445 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 koder736e5c12017-05-07 17:27:14 +0300451 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 koder3a9e5db2017-05-09 20:00:44 +0300456 if dev is None:
457 rel_val_s = avg_s
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300458 else:
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300459 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 koder736e5c12017-05-07 17:27:14 +0300464
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 koder3a9e5db2017-05-09 20:00:44 +0300480 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 koder736e5c12017-05-07 17:27:14 +0300482
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300483 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 koder736e5c12017-05-07 17:27:14 +0300487
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300488 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 koder736e5c12017-05-07 17:27:14 +0300490
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300491 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 koder736e5c12017-05-07 17:27:14 +0300500 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 koder3a9e5db2017-05-09 20:00:44 +0300507
508 if dev is None:
509 dev = 0
510
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300511 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 koder3a9e5db2017-05-09 20:00:44 +0300525 fname = self.plt(plot_simple_bars, ds, tp.capitalize(),
526 [name.replace(" nodes", "") for name in names],
527 vals, devs)
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300528
529 yield Menu1st.per_job, job.summary, HTMLBlock(html.img(fname))
530
531
532class BottleNeck(JobReporter):
533 """Statistic info for job results"""
534 suite_types = {'fio'}
535
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300536 def get_divs(self, suite: SuiteConfig, job: JobConfig) -> Iterator[Tuple[str, str, HTMLBlock]]:
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300537
kdanylov aka koderb0833332017-05-13 20:39:17 +0300538 nodes = list(find_nodes_by_roles(self.rstorage.storage, STORAGE_ROLES))
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300539
540 sensor = 'block-io'
541 metric = 'io_queue'
542 bn_val = 16
543
kdanylov aka koderb0833332017-05-13 20:39:17 +0300544 for node_id in nodes:
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300545 bn = 0
546 tot = 0
kdanylov aka koderb0833332017-05-13 20:39:17 +0300547 for _, ds in self.rstorage.iter_sensors(node_id=node_id, sensor=sensor, metric=metric):
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300548 if ds.dev in ('sdb', 'sdc', 'sdd', 'sde'):
kdanylov aka koderb0833332017-05-13 20:39:17 +0300549 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 koder736e5c12017-05-07 17:27:14 +0300553
554 yield Menu1st.per_job, job.summary, HTMLBlock("")
koder aka kdanilova732a602017-02-01 20:29:56 +0200555
556
kdanylov aka kodercdfcdaf2017-04-29 10:03:39 +0300557# CPU load
558class CPULoadPlot(JobReporter):
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300559 def get_divs(self, suite: SuiteConfig, job: JobConfig) -> Iterator[Tuple[str, str, HTMLBlock]]:
kdanylov aka kodercdfcdaf2017-04-29 10:03:39 +0300560
kdanylov aka kodercdfcdaf2017-04-29 10:03:39 +0300561 # plot CPU time
562 for rt, roles in [('storage', STORAGE_ROLES), ('test', ['testnode'])]:
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300563 cpu_ts = get_cluster_cpu_load(self.rstorage, roles, job.reliable_info_range_s)
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300564 tss = [(name, ts.data * 100 / cpu_ts['total'].data)
565 for name, ts in cpu_ts.items()
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300566 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 kodercdfcdaf2017-04-29 10:03:39 +0300575
576 yield Menu1st.per_job, job.summary, HTMLBlock(html.img(fname))
577
578
579# IO time and QD
580class QDIOTimeHeatmap(JobReporter):
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300581 def get_divs(self, suite: SuiteConfig, job: JobConfig) -> Iterator[Tuple[str, str, HTMLBlock]]:
kdanylov aka kodercdfcdaf2017-04-29 10:03:39 +0300582
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 koderb0833332017-05-13 20:39:17 +0300590 for node in self.rstorage.find_nodes(STORAGE_ROLES):
kdanylov aka kodercdfcdaf2017-04-29 10:03:39 +0300591 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 kodercdfcdaf2017-04-29 10:03:39 +0300603 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 koder736e5c12017-05-07 17:27:14 +0300608
609 yield Menu1st.per_job, job.summary, \
610 HTMLBlock(html.H2(html.center("{} IO heatmaps".format(name.capitalize()))))
611
kdanylov aka kodercdfcdaf2017-04-29 10:03:39 +0300612 # QD heatmap
kdanylov aka koderb0833332017-05-13 20:39:17 +0300613 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 koder736e5c12017-05-07 17:27:14 +0300616
617 ds = DataSource(suite.storage_id, job.storage_id, AGG_TAG, 'block-io', name, tag="hmap." + default_format)
618
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300619 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 kodercdfcdaf2017-04-29 10:03:39 +0300621 yield Menu1st.per_job, job.summary, HTMLBlock(html.img(fname))
622
623 # Block size heatmap
kdanylov aka koderb0833332017-05-13 20:39:17 +0300624 wc2d = find_sensors_to_2d(self.rstorage, trange, node_id=nodes, sensor='block-io', devs=devs,
625 metric='writes_completed')
kdanylov aka kodercdfcdaf2017-04-29 10:03:39 +0300626 wc2d[wc2d < 1E-3] = 1
kdanylov aka koderb0833332017-05-13 20:39:17 +0300627 sw2d = find_sensors_to_2d(self.rstorage, trange, node_id=nodes, sensor='block-io', devs=devs,
628 metric='sectors_written')
kdanylov aka kodercdfcdaf2017-04-29 10:03:39 +0300629 data2d = sw2d / wc2d / 1024
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300630 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 kodercdfcdaf2017-04-29 10:03:39 +0300633 yield Menu1st.per_job, job.summary, HTMLBlock(html.img(fname))
634
635 # iotime heatmap
kdanylov aka koderb0833332017-05-13 20:39:17 +0300636 wtime2d = find_sensors_to_2d(self.rstorage, trange, node_id=nodes, sensor='block-io', devs=devs,
637 metric='io_time')
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300638 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 kodercdfcdaf2017-04-29 10:03:39 +0300641 yield Menu1st.per_job, job.summary, HTMLBlock(html.img(fname))
642
643
koder aka kdanilov108ac362017-01-19 20:17:16 +0200644# IOPS/latency over test time for each job
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300645class LoadToolResults(JobReporter):
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200646 """IOPS/latency during test"""
koder aka kdanilova732a602017-02-01 20:29:56 +0200647 suite_types = {'fio'}
koder aka kdanilov108ac362017-01-19 20:17:16 +0200648
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300649 def get_divs(self, suite: SuiteConfig, job: JobConfig) -> Iterator[Tuple[str, str, HTMLBlock]]:
koder aka kdanilov108ac362017-01-19 20:17:16 +0200650
koder aka kdanilova732a602017-02-01 20:29:56 +0200651 fjob = cast(FioJobConfig, job)
koder aka kdanilov108ac362017-01-19 20:17:16 +0200652
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300653 yield Menu1st.per_job, job.summary, HTMLBlock(html.H2(html.center("Load tool results")))
654
kdanylov aka koderb0833332017-05-13 20:39:17 +0300655 agg_io = get_aggregated(self.rstorage, suite.storage_id, fjob.storage_id, "bw", job.reliable_info_range_s)
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300656 if fjob.bsize >= DefStyleProfile.large_blocks:
kdanylov aka kodercdfcdaf2017-04-29 10:03:39 +0300657 title = "Fio measured Bandwidth over time"
koder aka kdanilova732a602017-02-01 20:29:56 +0200658 units = "MiBps"
kdanylov aka koderb0833332017-05-13 20:39:17 +0300659 agg_io.data //= int(unit_conversion_coef_f(units, agg_io.units))
koder aka kdanilova732a602017-02-01 20:29:56 +0200660 else:
kdanylov aka kodercdfcdaf2017-04-29 10:03:39 +0300661 title = "Fio measured IOPS over time"
kdanylov aka koderb0833332017-05-13 20:39:17 +0300662 agg_io.data //= (int(unit_conversion_coef_f("KiBps", agg_io.units)) * fjob.bsize)
koder aka kdanilova732a602017-02-01 20:29:56 +0200663 units = "IOPS"
koder aka kdanilov108ac362017-01-19 20:17:16 +0200664
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300665 fpath = self.plt(plot_v_over_time, agg_io.source(tag='ts.' + default_format), title, units, agg_io)
koder aka kdanilova732a602017-02-01 20:29:56 +0200666 yield Menu1st.per_job, fjob.summary, HTMLBlock(html.img(fpath))
koder aka kdanilov108ac362017-01-19 20:17:16 +0200667
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300668 if fjob.bsize < DefStyleProfile.large_blocks:
kdanylov aka koderb0833332017-05-13 20:39:17 +0300669 agg_lat = get_aggregated(self.rstorage, suite.storage_id, fjob.storage_id, "lat",
670 job.reliable_info_range_s)
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300671 TARGET_UNITS = 'ms'
kdanylov aka koderb0833332017-05-13 20:39:17 +0300672 coef = unit_conversion_coef_f(agg_lat.units, TARGET_UNITS)
673 agg_lat.histo_bins = agg_lat.histo_bins.copy() * coef
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300674 agg_lat.units = TARGET_UNITS
koder aka kdanilov108ac362017-01-19 20:17:16 +0200675
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300676 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 kdanilov108ac362017-01-19 20:17:16 +0200679
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300680 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 kdanilov108ac362017-01-19 20:17:16 +0200682
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300683 yield Menu1st.per_job, fjob.summary, HTMLBlock(html.img(fpath))
koder aka kdanilov108ac362017-01-19 20:17:16 +0200684
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300685 fjob = cast(FioJobConfig, job)
koder aka kdanilov108ac362017-01-19 20:17:16 +0200686
kdanylov aka koderb0833332017-05-13 20:39:17 +0300687 agg_io = get_aggregated(self.rstorage, suite.storage_id, fjob.storage_id, "bw", job.reliable_info_range_s)
koder aka kdanilov108ac362017-01-19 20:17:16 +0200688
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300689 if fjob.bsize >= DefStyleProfile.large_blocks:
690 title = "BW distribution"
691 units = "MiBps"
kdanylov aka koderb0833332017-05-13 20:39:17 +0300692 agg_io.data //= int(unit_conversion_coef_f(units, agg_io.units))
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300693 else:
694 title = "IOPS distribution"
kdanylov aka koderb0833332017-05-13 20:39:17 +0300695 agg_io.data //= (int(unit_conversion_coef_f("KiBps", agg_io.units)) * fjob.bsize)
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300696 units = "IOPS"
697
698 io_stat_prop = calc_norm_stat_props(agg_io, bins_count=StyleProfile.hist_boxes)
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300699 fpath = self.plt(plot_hist, agg_io.source(tag='hist.' + default_format), title, units, io_stat_prop)
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300700 yield Menu1st.per_job, fjob.summary, HTMLBlock(html.img(fpath))
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200701
702
703# Cluster load over test time
koder aka kdanilova732a602017-02-01 20:29:56 +0200704class ClusterLoad(JobReporter):
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200705 """IOPS/latency during test"""
706
koder aka kdanilova732a602017-02-01 20:29:56 +0200707 # TODO: units should came from sensor
koder aka kdanilov108ac362017-01-19 20:17:16 +0200708 storage_sensors = [
kdanylov aka koder45183182017-04-30 23:55:40 +0300709 ('block-io', 'reads_completed', "Read", 'iop'),
710 ('block-io', 'writes_completed', "Write", 'iop'),
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300711 ('block-io', 'sectors_read', "Read", 'MiB'),
712 ('block-io', 'sectors_written', "Write", 'MiB'),
koder aka kdanilov108ac362017-01-19 20:17:16 +0200713 ]
714
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300715 def get_divs(self, suite: SuiteConfig, job: JobConfig) -> Iterator[Tuple[str, str, HTMLBlock]]:
716
koder aka kdanilova732a602017-02-01 20:29:56 +0200717 yield Menu1st.per_job, job.summary, HTMLBlock(html.H2(html.center("Cluster load")))
koder aka kdanilov108ac362017-01-19 20:17:16 +0200718
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300719 sensors = []
720 max_iop = 0
721 max_bytes = 0
kdanylov aka koderb0833332017-05-13 20:39:17 +0300722 stor_nodes = find_nodes_by_roles(self.rstorage.storage, STORAGE_ROLES)
kdanylov aka koder45183182017-04-30 23:55:40 +0300723 for sensor, metric, op, units in self.storage_sensors:
kdanylov aka koderb0833332017-05-13 20:39:17 +0300724 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 kdanilov108ac362017-01-19 20:17:16 +0200734
kdanylov aka koderb0833332017-05-13 20:39:17 +0300735 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 koder0e0cfcb2017-03-27 22:19:09 +0300742
kdanylov aka koderb0833332017-05-13 20:39:17 +0300743 sensors.append(("{} {}".format(op, units), ds, ts, units))
koder aka kdanilov108ac362017-01-19 20:17:16 +0200744
kdanylov aka koderb0833332017-05-13 20:39:17 +0300745 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 kdanilov108ac362017-01-19 20:17:16 +0200750
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300751 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 kdanilov7f59d562016-12-26 01:34:23 +0200758
759
koder aka kdanilov108ac362017-01-19 20:17:16 +0200760# ------------------------------------------ REPORT STAGES -----------------------------------------------------------
761
762
763class HtmlReportStage(Stage):
764 priority = StepOrder.REPORT
765
766 def run(self, ctx: TestRun) -> None:
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300767 job_reporters_cls = [StatInfo, Resources, LoadToolResults, ClusterLoad, CPULoadPlot, QDIOTimeHeatmap]
kdanylov aka koderb0833332017-05-13 20:39:17 +0300768 job_reporters = [rcls(ctx.rstorage, DefStyleProfile, DefColorProfile) for rcls in job_reporters_cls]
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300769
770 suite_reporters_cls = [IOQD, ResourceQD]
kdanylov aka koderb0833332017-05-13 20:39:17 +0300771 suite_reporters = [rcls(ctx.rstorage, DefStyleProfile, DefColorProfile) for rcls in suite_reporters_cls]
koder aka kdanilov108ac362017-01-19 20:17:16 +0200772
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 kdanilova732a602017-02-01 20:29:56 +0200783 # 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 kdanilov108ac362017-01-19 20:17:16 +0200786
koder aka kdanilova732a602017-02-01 20:29:56 +0200787 items = defaultdict(lambda: defaultdict(list)) # type: Dict[str, Dict[str, List[HTMLBlock]]]
kdanylov aka kodercdfcdaf2017-04-29 10:03:39 +0300788 DEBUG = False
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300789 job_summ_sort_order = []
790
koder aka kdanilova732a602017-02-01 20:29:56 +0200791 # TODO: filter reporters
kdanylov aka koderb0833332017-05-13 20:39:17 +0300792 for suite in ctx.rstorage.iter_suite(FioTest.name):
793 all_jobs = list(ctx.rstorage.iter_job(suite))
koder aka kdanilova732a602017-02-01 20:29:56 +0200794 all_jobs.sort(key=lambda job: job.params)
koder aka kdanilova732a602017-02-01 20:29:56 +0200795
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300796 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 koderb0833332017-05-13 20:39:17 +0300801 for job in all_jobs:
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300802 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 koder736e5c12017-05-07 17:27:14 +0300814 try:
815 logger.debug("Start reporter %s on suite %s", reporter.__class__.__name__, suite.test_type)
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300816 for block, item, html in reporter.get_divs(suite):
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300817 items[block][item].append(html)
818 except Exception as exc:
819 logger.exception("Failed to generate report")
koder aka kdanilov108ac362017-01-19 20:17:16 +0200820
koder aka kdanilova732a602017-02-01 20:29:56 +0200821 if DEBUG:
822 break
823
kdanylov aka kodercdfcdaf2017-04-29 10:03:39 +0300824 logger.debug("Generating result html")
825
koder aka kdanilov108ac362017-01-19 20:17:16 +0200826 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 koder3a9e5db2017-05-09 20:00:44 +0300832
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 kdanilov108ac362017-01-19 20:17:16 +0200839 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 kdanilova732a602017-02-01 20:29:56 +0200842 content_block.extend(" " + x.data for x in items[menu_1st][menu_2nd])
koder aka kdanilov108ac362017-01-19 20:17:16 +0200843 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 koderb0833332017-05-13 20:39:17 +0300849 report_path = ctx.rstorage.put_report(report, "index.html")
850 ctx.rstorage.put_report(css_file, "main.css")
koder aka kdanilov108ac362017-01-19 20:17:16 +0200851 logger.info("Report is stored into %r", report_path)