blob: 3ac1292f6211080f4b55e505feba92c34c0ae56b [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
koder aka kdanilov108ac362017-01-19 20:17:16 +020014from . import html
koder aka kdanilov39e449e2016-12-17 15:15:26 +020015from .stage import Stage, StepOrder
16from .test_run_class import TestRun
koder aka kdanilov108ac362017-01-19 20:17:16 +020017from .hlstorage import ResultStorage
kdanylov aka kodercdfcdaf2017-04-29 10:03:39 +030018from .utils import b2ssize, b2ssize_10, STORAGE_ROLES, unit_conversion_coef
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +030019from .statistic import calc_norm_stat_props
20from .result_classes import DataSource, TimeSeries, SuiteConfig
koder aka kdanilov108ac362017-01-19 20:17:16 +020021from .suits.io.fio import FioTest, FioJobConfig
koder aka kdanilova732a602017-02-01 20:29:56 +020022from .suits.io.fio_job import FioJobParams
23from .suits.job import JobConfig
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +030024from .data_selectors import get_aggregated, AGG_TAG, summ_sensors, find_sensors_to_2d, find_nodes_by_roles
25from .report_profiles import (DefStyleProfile, DefColorProfile, StyleProfile, ColorProfile,
26 default_format, io_chart_format)
27from .plot import (io_chart, plot_simple_bars, plot_hmap_from_2d, plot_lat_over_time, plot_simple_over_time,
28 plot_histo_heatmap, plot_v_over_time, plot_hist)
29from .resources import ResourceNames, get_resources_usage, make_iosum, IOSummary, get_cluster_cpu_load
koder aka kdanilov962ee5f2016-12-19 02:40:08 +020030logger = logging.getLogger("wally")
koder aka kdanilova047e1b2015-04-21 23:16:59 +030031
32
koder aka kdanilov108ac362017-01-19 20:17:16 +020033# ---------------- CONSTS ---------------------------------------------------------------------------------------------
koder aka kdanilov39e449e2016-12-17 15:15:26 +020034
koder aka kdanilov7f59d562016-12-26 01:34:23 +020035
koder aka kdanilov108ac362017-01-19 20:17:16 +020036DEBUG = False
koder aka kdanilov39e449e2016-12-17 15:15:26 +020037
koder aka kdanilov39e449e2016-12-17 15:15:26 +020038
koder aka kdanilov108ac362017-01-19 20:17:16 +020039# ---------------- STRUCTS -------------------------------------------------------------------------------------------
koder aka kdanilov39e449e2016-12-17 15:15:26 +020040
koder aka kdanilov7f59d562016-12-26 01:34:23 +020041
42# TODO: need to be revised, have to user StatProps fields instead
43class StoragePerfSummary:
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +030044 def __init__(self) -> None:
koder aka kdanilov7f59d562016-12-26 01:34:23 +020045 self.direct_iops_r_max = 0 # type: int
46 self.direct_iops_w_max = 0 # type: int
47
48 # 64 used instead of 4k to faster feed caches
49 self.direct_iops_w64_max = 0 # type: int
50
51 self.rws4k_10ms = 0 # type: int
52 self.rws4k_30ms = 0 # type: int
53 self.rws4k_100ms = 0 # type: int
54 self.bw_write_max = 0 # type: int
55 self.bw_read_max = 0 # type: int
56
57 self.bw = None # type: float
58 self.iops = None # type: float
59 self.lat = None # type: float
60 self.lat_50 = None # type: float
61 self.lat_95 = None # type: float
62
63
koder aka kdanilov108ac362017-01-19 20:17:16 +020064# -------------- AGGREGATION AND STAT FUNCTIONS ----------------------------------------------------------------------
koder aka kdanilov108ac362017-01-19 20:17:16 +020065
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +030066LEVEL_SENSORS = {("block-io", "io_queue"), ("system-cpu", "procs_blocked"), ("system-cpu", "procs_queue")}
koder aka kdanilova732a602017-02-01 20:29:56 +020067
68
69def is_level_sensor(sensor: str, metric: str) -> bool:
70 """Returns True if sensor measure level of any kind, E.g. queue depth."""
71 return (sensor, metric) in LEVEL_SENSORS
72
73
74def is_delta_sensor(sensor: str, metric: str) -> bool:
75 """Returns True if sensor provides deltas for cumulative value. E.g. io completed in given period"""
76 return not is_level_sensor(sensor, metric)
77
kdanylov aka koder736e5c12017-05-07 17:27:14 +030078
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +030079# def get_idle_load(rstorage: ResultStorage, *args, **kwargs) -> float:
80# if 'idle' not in rstorage.storage:
81# return 0.0
82# idle_time = rstorage.storage.get('idle')
83# ssum = summ_sensors(rstorage, time_range=idle_time, *args, **kwargs)
84# return numpy.average(ssum)
kdanylov aka koder736e5c12017-05-07 17:27:14 +030085
koder aka kdanilov108ac362017-01-19 20:17:16 +020086
87# -------------------- REPORT HELPERS --------------------------------------------------------------------------------
88
89
koder aka kdanilov7f59d562016-12-26 01:34:23 +020090class HTMLBlock:
91 data = None # type: str
92 js_links = [] # type: List[str]
93 css_links = [] # type: List[str]
koder aka kdanilova732a602017-02-01 20:29:56 +020094 order_attr = None # type: Any
95
96 def __init__(self, data: str, order_attr: Any = None) -> None:
97 self.data = data
98 self.order_attr = order_attr
99
kdanylov aka koder45183182017-04-30 23:55:40 +0300100 def __eq__(self, o: Any) -> bool:
koder aka kdanilova732a602017-02-01 20:29:56 +0200101 return o.order_attr == self.order_attr # type: ignore
102
kdanylov aka koder45183182017-04-30 23:55:40 +0300103 def __lt__(self, o: Any) -> bool:
koder aka kdanilova732a602017-02-01 20:29:56 +0200104 return o.order_attr > self.order_attr # type: ignore
105
106
107class Table:
108 def __init__(self, header: List[str]) -> None:
109 self.header = header
110 self.data = []
111
112 def add_line(self, values: List[str]) -> None:
113 self.data.append(values)
114
115 def html(self):
116 return html.table("", self.header, self.data)
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200117
118
koder aka kdanilov108ac362017-01-19 20:17:16 +0200119class Menu1st:
120 engineering = "Engineering"
121 summary = "Summary"
koder aka kdanilova732a602017-02-01 20:29:56 +0200122 per_job = "Per Job"
koder aka kdanilov108ac362017-01-19 20:17:16 +0200123
124
125class Menu2ndEng:
126 iops_time = "IOPS(time)"
127 hist = "IOPS/lat overall histogram"
128 lat_time = "Lat(time)"
129
130
131class Menu2ndSumm:
132 io_lat_qd = "IO & Lat vs QD"
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300133 cpu_usage_qd = "CPU usage"
koder aka kdanilov108ac362017-01-19 20:17:16 +0200134
135
koder aka kdanilova732a602017-02-01 20:29:56 +0200136menu_1st_order = [Menu1st.summary, Menu1st.engineering, Menu1st.per_job]
koder aka kdanilov108ac362017-01-19 20:17:16 +0200137
138
139# -------------------- REPORTS --------------------------------------------------------------------------------------
140
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300141class ReporterBase:
142 def __init__(self, rstorage: ResultStorage, style: StyleProfile, colors: ColorProfile) -> None:
143 self.style = style
144 self.colors = colors
145 self.rstorage = rstorage
koder aka kdanilov108ac362017-01-19 20:17:16 +0200146
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300147 def plt(self, func, ds: DataSource, *args, **kwargs) -> str:
148 return func(self.rstorage, self.style, self.colors, ds, *args, **kwargs)
149
150
151class SuiteReporter(ReporterBase, metaclass=abc.ABCMeta):
152 suite_types = set() # type: Set[str]
koder aka kdanilova732a602017-02-01 20:29:56 +0200153
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200154 @abc.abstractmethod
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300155 def get_divs(self, suite: SuiteConfig) -> Iterator[Tuple[str, str, HTMLBlock]]:
koder aka kdanilova732a602017-02-01 20:29:56 +0200156 pass
157
158
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300159class JobReporter(ReporterBase, metaclass=abc.ABCMeta):
koder aka kdanilova732a602017-02-01 20:29:56 +0200160 suite_type = set() # type: Set[str]
161
162 @abc.abstractmethod
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300163 def get_divs(self, suite: SuiteConfig, job: JobConfig) -> Iterator[Tuple[str, str, HTMLBlock]]:
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200164 pass
165
166
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300167# # Linearization report
168# class IOPSBsize(SuiteReporter):
169# """Creates graphs, which show how IOPS and Latency depend on block size"""
170#
171#
172# # Main performance report
173# class PerformanceSummary(SuiteReporter):
174# """Aggregated summary fro storage"""
175
176# # Node load over test time
177# class NodeLoad(SuiteReporter):
178# """IOPS/latency during test"""
179
180# # Ceph operation breakout report
181# class CephClusterSummary(SuiteReporter):
182# """IOPS/latency during test"""
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200183
184
185# Main performance report
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300186class IOQD(SuiteReporter):
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200187 """Creates graph, which show how IOPS and Latency depend on QD"""
koder aka kdanilova732a602017-02-01 20:29:56 +0200188 suite_types = {'fio'}
189
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300190 def get_divs(self, suite: SuiteConfig) -> Iterator[Tuple[str, str, HTMLBlock]]:
koder aka kdanilova732a602017-02-01 20:29:56 +0200191 ts_map = defaultdict(list) # type: Dict[FioJobParams, List[Tuple[SuiteConfig, FioJobConfig]]]
192 str_summary = {} # type: Dict[FioJobParams, List[IOSummary]]
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300193
194 for job in self.rstorage.iter_job(suite):
koder aka kdanilov108ac362017-01-19 20:17:16 +0200195 fjob = cast(FioJobConfig, job)
koder aka kdanilova732a602017-02-01 20:29:56 +0200196 fjob_no_qd = cast(FioJobParams, fjob.params.copy(qd=None))
197 str_summary[fjob_no_qd] = (fjob_no_qd.summary, fjob_no_qd.long_summary)
198 ts_map[fjob_no_qd].append((suite, fjob))
koder aka kdanilov108ac362017-01-19 20:17:16 +0200199
koder aka kdanilova732a602017-02-01 20:29:56 +0200200 for tpl, suites_jobs in ts_map.items():
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300201 if len(suites_jobs) >= self.style.min_iops_vs_qd_jobs:
202 iosums = [make_iosum(self.rstorage, suite, job, self.style.hist_boxes) for suite, job in suites_jobs]
koder aka kdanilova732a602017-02-01 20:29:56 +0200203 iosums.sort(key=lambda x: x.qd)
204 summary, summary_long = str_summary[tpl]
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300205
206 yield Menu1st.summary, Menu2ndSumm.io_lat_qd, \
207 HTMLBlock(html.H2(html.center("IOPS, BW, Lat = func(QD). " + summary_long)))
208
koder aka kdanilova732a602017-02-01 20:29:56 +0200209 ds = DataSource(suite_id=suite.storage_id,
210 job_id=summary,
211 node_id=AGG_TAG,
212 sensor="fio",
213 dev=AGG_TAG,
214 metric="io_over_qd",
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300215 tag=io_chart_format)
koder aka kdanilov108ac362017-01-19 20:17:16 +0200216
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300217 fpath = self.plt(io_chart, ds, title="", legend="IOPS/BW", iosums=iosums)
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300218 yield Menu1st.summary, Menu2ndSumm.io_lat_qd, HTMLBlock(html.center(html.img(fpath)))
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200219
220
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300221class ResourceQD(SuiteReporter):
222 suite_types = {'fio'}
223
224 def get_divs(self, suite: SuiteConfig) -> Iterator[Tuple[str, str, HTMLBlock]]:
225 qd_grouped_jobs = {} # type: Dict[FioJobParams, List[FioJobConfig]]
226 test_nc = len(list(find_nodes_by_roles(self.rstorage, ['testnode'])))
227 for job in self.rstorage.iter_job(suite):
228 fjob = cast(FioJobConfig, job)
229 if fjob.bsize != 4:
230 continue
231
232 fjob_no_qd = cast(FioJobParams, fjob.params.copy(qd=None))
233 qd_grouped_jobs.setdefault(fjob_no_qd, []).append(fjob)
234
235 for jc_no_qd, jobs in sorted(qd_grouped_jobs.items()):
236 cpu_usage2qd = {}
237 for job in jobs:
238 usage, iops_ok = get_resources_usage(suite, job, self.rstorage, hist_boxes=self.style.hist_boxes,
239 large_block=self.style.large_blocks)
240
241 if iops_ok:
242 cpu_usage2qd[job.qd] = usage[ResourceNames.storage_cpu_s]
243
244 if len(cpu_usage2qd) < StyleProfile.min_iops_vs_qd_jobs:
245 continue
246
247 labels, vals, errs = zip(*((l, avg, dev) for l, (_, avg, dev) in sorted(cpu_usage2qd.items())))
248
249 if test_nc == 1:
250 labels = list(map(str, labels))
251 else:
252 labels = ["{} * {}".format(label, test_nc) for label in labels]
253
254 ds = DataSource(suite_id=suite.storage_id,
255 job_id=jc_no_qd.summary,
256 node_id="cluster",
257 sensor=AGG_TAG,
258 dev='cpu',
259 metric="cpu_for_iop",
260 tag=io_chart_format)
261
262 fpath = self.plt(plot_simple_bars, ds, jc_no_qd.long_summary, labels, vals, errs,
263 xlabel="CPU time per IOP", ylabel="QD * Test nodes" if test_nc != 1 else "QD",
264 x_formatter=(lambda x, pos: b2ssize_10(x) + 's'),
265 one_point_zero_line=False)
266
267 yield Menu1st.summary, Menu2ndSumm.cpu_usage_qd, HTMLBlock(html.center(html.img(fpath)))
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200268
269
koder aka kdanilova732a602017-02-01 20:29:56 +0200270class StatInfo(JobReporter):
271 """Statistic info for job results"""
272 suite_types = {'fio'}
273
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300274 def get_divs(self, suite: SuiteConfig, job: JobConfig) -> Iterator[Tuple[str, str, HTMLBlock]]:
koder aka kdanilova732a602017-02-01 20:29:56 +0200275
276 fjob = cast(FioJobConfig, job)
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300277 io_sum = make_iosum(self.rstorage, suite, fjob, self.style.hist_boxes)
koder aka kdanilova732a602017-02-01 20:29:56 +0200278
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300279 caption = "Test summary - " + job.params.long_summary
280 test_nc = len(list(find_nodes_by_roles(self.rstorage, ['testnode'])))
281 if test_nc > 1:
282 caption += " * {} nodes".format(test_nc)
283
284 res = html.H2(html.center(caption))
285 stat_data_headers = ["Name",
286 "Total done",
287 "Average ~ Dev",
288 "Conf interval",
289 "Mediana",
290 "Mode",
291 "Kurt / Skew",
292 "95%",
293 "99%",
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300294 "ADF test"]
koder aka kdanilova732a602017-02-01 20:29:56 +0200295
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300296 align = ['left'] + ['right'] * (len(stat_data_headers) - 1)
297
298 bw_units = "B"
299 bw_target_units = bw_units + 'ps'
kdanylov aka koder45183182017-04-30 23:55:40 +0300300 bw_coef = float(unit_conversion_coef(io_sum.bw.units, bw_target_units))
301
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300302 adf_v, *_1, stats, _2 = adfuller(io_sum.bw.data)
303
304 for v in ("1%", "5%", "10%"):
305 if adf_v <= stats[v]:
306 ad_test = v
307 break
308 else:
309 ad_test = "Failed"
310
koder aka kdanilova732a602017-02-01 20:29:56 +0200311 bw_data = ["Bandwidth",
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300312 b2ssize(io_sum.bw.data.sum() * bw_coef) + bw_units,
kdanylov aka koder45183182017-04-30 23:55:40 +0300313 "{}{} ~ {}{}".format(b2ssize(io_sum.bw.average * bw_coef), bw_target_units,
314 b2ssize(io_sum.bw.deviation * bw_coef), bw_target_units),
315 b2ssize(io_sum.bw.confidence * bw_coef) + bw_target_units,
316 b2ssize(io_sum.bw.perc_50 * bw_coef) + bw_target_units,
koder aka kdanilova732a602017-02-01 20:29:56 +0200317 "-",
318 "{:.2f} / {:.2f}".format(io_sum.bw.kurt, io_sum.bw.skew),
kdanylov aka koder45183182017-04-30 23:55:40 +0300319 b2ssize(io_sum.bw.perc_5 * bw_coef) + bw_target_units,
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300320 b2ssize(io_sum.bw.perc_1 * bw_coef) + bw_target_units,
321 ad_test]
koder aka kdanilova732a602017-02-01 20:29:56 +0200322
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300323 stat_data = [bw_data]
koder aka kdanilova732a602017-02-01 20:29:56 +0200324
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300325 if fjob.bsize < StyleProfile.large_blocks:
326 iops_coef = float(unit_conversion_coef(io_sum.bw.units, 'KiBps')) / fjob.bsize
327 iops_data = ["IOPS",
328 b2ssize_10(io_sum.bw.data.sum() * iops_coef),
329 "{}IOPS ~ {}IOPS".format(b2ssize_10(io_sum.bw.average * iops_coef),
330 b2ssize_10(io_sum.bw.deviation * iops_coef)),
331 b2ssize_10(io_sum.bw.confidence * iops_coef) + "IOPS",
332 b2ssize_10(io_sum.bw.perc_50 * iops_coef) + "IOPS",
333 "-",
334 "{:.2f} / {:.2f}".format(io_sum.bw.kurt, io_sum.bw.skew),
335 b2ssize_10(io_sum.bw.perc_5 * iops_coef) + "IOPS",
336 b2ssize_10(io_sum.bw.perc_1 * iops_coef) + "IOPS",
337 ad_test]
koder aka kdanilova732a602017-02-01 20:29:56 +0200338
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300339 lat_target_unit = 's'
340 lat_coef = unit_conversion_coef(io_sum.lat.units, lat_target_unit)
341 # latency
342 lat_data = ["Latency",
343 "-",
344 "-",
345 "-",
346 b2ssize_10(io_sum.lat.perc_50 * lat_coef) + lat_target_unit,
347 "-",
348 "-",
349 b2ssize_10(io_sum.lat.perc_95 * lat_coef) + lat_target_unit,
350 b2ssize_10(io_sum.lat.perc_99 * lat_coef) + lat_target_unit,
351 '-']
352
353 # sensor usage
354 stat_data.extend([iops_data, lat_data])
355
356 res += html.center(html.table("Load stats info", stat_data_headers, stat_data, align=align))
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300357 yield Menu1st.per_job, job.summary, HTMLBlock(res)
koder aka kdanilova732a602017-02-01 20:29:56 +0200358
koder aka kdanilova732a602017-02-01 20:29:56 +0200359
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300360class Resources(JobReporter):
361 """Statistic info for job results"""
362 suite_types = {'fio'}
363
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300364 def get_divs(self, suite: SuiteConfig, job: JobConfig) -> Iterator[Tuple[str, str, HTMLBlock]]:
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300365
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300366 records, iops_ok = get_resources_usage(suite, job, self.rstorage,
367 large_block=self.style.large_blocks,
368 hist_boxes=self.style.hist_boxes)
koder aka kdanilova732a602017-02-01 20:29:56 +0200369
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300370 table_structure = [
371 "Service provided",
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300372 (ResourceNames.io_made, ResourceNames.data_tr),
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300373 "Test nodes total load",
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300374 (ResourceNames.test_send_pkt, ResourceNames.test_send),
375 (ResourceNames.test_recv_pkt, ResourceNames.test_recv),
376 (ResourceNames.test_net_pkt, ResourceNames.test_net),
377 (ResourceNames.test_write_iop, ResourceNames.test_write),
378 (ResourceNames.test_read_iop, ResourceNames.test_read),
379 (ResourceNames.test_iop, ResourceNames.test_rw),
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300380 "Storage nodes resource consumed",
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300381 (ResourceNames.storage_send_pkt, ResourceNames.storage_send),
382 (ResourceNames.storage_recv_pkt, ResourceNames.storage_recv),
383 (ResourceNames.storage_net_pkt, ResourceNames.storage_net),
384 (ResourceNames.storage_write_iop, ResourceNames.storage_write),
385 (ResourceNames.storage_read_iop, ResourceNames.storage_read),
386 (ResourceNames.storage_iop, ResourceNames.storage_rw),
387 (ResourceNames.storage_cpu_s, ResourceNames.storage_cpu_s_b),
388 ] # type: List[Union[str, Tuple[Optional[str], ...]]
389
390 if not iops_ok:
391 table_structure2 = []
392 for line in table_structure:
393 if isinstance(line, str):
394 table_structure2.append(line)
395 else:
396 assert len(line) == 2
397 table_structure2.append((line[1],))
398 table_structure = table_structure2
koder aka kdanilova732a602017-02-01 20:29:56 +0200399
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300400 yield Menu1st.per_job, job.summary, HTMLBlock(html.H2(html.center("Resources usage")))
401
402 doc = xmlbuilder3.XMLBuilder("table",
403 **{"class": "table table-bordered table-striped table-condensed table-hover",
404 "style": "width: auto;"})
405
406 with doc.thead:
407 with doc.tr:
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300408 [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 +0300409
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300410 cols = 6 if iops_ok else 3
411 col_per_tp = 3
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300412
413 short_name = {
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300414 name: (name if name in {ResourceNames.io_made, ResourceNames.data_tr}
415 else " ".join(name.split()[2:]).capitalize())
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300416 for name in records.keys()
417 }
418
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300419 short_name[ResourceNames.storage_cpu_s] = "CPU (s/IOP)"
420 short_name[ResourceNames.storage_cpu_s_b] = "CPU (s/B)"
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300421
422 with doc.tbody:
423 with doc.tr:
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300424 if iops_ok:
425 doc.td(colspan=str(col_per_tp)).center.b("Operations")
426 doc.td(colspan=str(col_per_tp)).center.b("Bytes")
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300427
428 for line in table_structure:
429 with doc.tr:
430 if isinstance(line, str):
431 with doc.td(colspan=str(cols)):
432 doc.center.b(line)
433 else:
434 for name in line:
435 if name is None:
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300436 doc.td("-", colspan=str(col_per_tp))
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300437 continue
438
439 amount_s, avg, dev = records[name]
440
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300441 if name in (ResourceNames.storage_cpu_s, ResourceNames.storage_cpu_s_b) and avg is not None:
442 if dev is None:
443 rel_val_s = b2ssize_10(avg) + 's'
444 else:
445 dev_s = str(int(dev * 100 / avg)) + "%" if avg > 1E-9 else b2ssize_10(dev) + 's'
446 rel_val_s = "{}s ~ {}".format(b2ssize_10(avg), dev_s)
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300447 else:
448 if avg is None:
449 rel_val_s = '-'
450 else:
451 avg_s = int(avg) if avg > 10 else '{:.1f}'.format(avg)
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300452 if dev is None:
453 rel_val_s = avg_s
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300454 else:
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300455 if avg > 1E-5:
456 dev_s = str(int(dev * 100 / avg)) + "%"
457 else:
458 dev_s = int(dev) if dev > 10 else '{:.1f}'.format(dev)
459 rel_val_s = "{} ~ {}".format(avg_s, dev_s)
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300460
461 doc.td(short_name[name], align="left")
462 doc.td(amount_s, align="right")
463
464 if avg is None or avg < 0.9:
465 doc.td(rel_val_s, align="right")
466 elif avg < 2.0:
467 doc.td(align="right").font(rel_val_s, color='green')
468 elif avg < 5.0:
469 doc.td(align="right").font(rel_val_s, color='orange')
470 else:
471 doc.td(align="right").font(rel_val_s, color='red')
472
473 res = xmlbuilder3.tostr(doc).split("\n", 1)[1]
474 yield Menu1st.per_job, job.summary, HTMLBlock(html.center(res))
475
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300476 iop_names = [ResourceNames.test_write_iop, ResourceNames.test_read_iop, ResourceNames.test_iop,
477 ResourceNames.storage_write_iop, ResourceNames.storage_read_iop, ResourceNames.storage_iop]
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300478
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300479 bytes_names = [ResourceNames.test_write, ResourceNames.test_read, ResourceNames.test_rw,
480 ResourceNames.test_send, ResourceNames.test_recv, ResourceNames.test_net,
481 ResourceNames.storage_write, ResourceNames.storage_read, ResourceNames.storage_rw,
482 ResourceNames.storage_send, ResourceNames.storage_recv, ResourceNames.storage_net]
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300483
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300484 net_pkt_names = [ResourceNames.test_send_pkt, ResourceNames.test_recv_pkt, ResourceNames.test_net_pkt,
485 ResourceNames.storage_send_pkt, ResourceNames.storage_recv_pkt, ResourceNames.storage_net_pkt]
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300486
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300487 pairs = [("bytes", bytes_names)]
488 if iops_ok:
489 pairs.insert(0, ('iop', iop_names))
490 pairs.append(('Net packets per IOP', net_pkt_names))
491
492 yield Menu1st.per_job, job.summary, \
493 HTMLBlock(html.H2(html.center("Resource consumption per service provided")))
494
495 for tp, names in pairs:
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300496 vals = []
497 devs = []
498 avail_names = []
499 for name in names:
500 if name in records:
501 avail_names.append(name)
502 _, avg, dev = records[name]
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300503
504 if dev is None:
505 dev = 0
506
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300507 vals.append(avg)
508 devs.append(dev)
509
510 # synchronously sort values and names, values is a key
511 vals, names, devs = map(list, zip(*sorted(zip(vals, names, devs))))
512
513 ds = DataSource(suite_id=suite.storage_id,
514 job_id=job.storage_id,
515 node_id=AGG_TAG,
516 sensor='resources',
517 dev=AGG_TAG,
518 metric=tp.replace(' ', "_") + '2service_bar',
519 tag=default_format)
520
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300521 fname = self.plt(plot_simple_bars, ds, tp.capitalize(),
522 [name.replace(" nodes", "") for name in names],
523 vals, devs)
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300524
525 yield Menu1st.per_job, job.summary, HTMLBlock(html.img(fname))
526
527
528class BottleNeck(JobReporter):
529 """Statistic info for job results"""
530 suite_types = {'fio'}
531
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300532 def get_divs(self, suite: SuiteConfig, job: JobConfig) -> Iterator[Tuple[str, str, HTMLBlock]]:
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300533
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300534 nodes = list(find_nodes_by_roles(self.rstorage, STORAGE_ROLES))
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300535
536 sensor = 'block-io'
537 metric = 'io_queue'
538 bn_val = 16
539
540 for node in nodes:
541 bn = 0
542 tot = 0
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300543 for _, ds in self.rstorage.iter_sensors(node_id=node.node_id, sensor=sensor, metric=metric):
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300544 if ds.dev in ('sdb', 'sdc', 'sdd', 'sde'):
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300545 data = self.rstorage.load_sensor(ds)
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300546 p1 = job.reliable_info_range_s[0] * unit_conversion_coef('s', data.time_units)
547 p2 = job.reliable_info_range_s[1] * unit_conversion_coef('s', data.time_units)
548 idx1, idx2 = numpy.searchsorted(data.times, (p1, p2))
549 bn += (data.data[idx1: idx2] > bn_val).sum()
550 tot += idx2 - idx1
551 print(node, bn, tot)
552
553 yield Menu1st.per_job, job.summary, HTMLBlock("")
koder aka kdanilova732a602017-02-01 20:29:56 +0200554
555
kdanylov aka kodercdfcdaf2017-04-29 10:03:39 +0300556# CPU load
557class CPULoadPlot(JobReporter):
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300558 def get_divs(self, suite: SuiteConfig, job: JobConfig) -> Iterator[Tuple[str, str, HTMLBlock]]:
kdanylov aka kodercdfcdaf2017-04-29 10:03:39 +0300559
kdanylov aka kodercdfcdaf2017-04-29 10:03:39 +0300560 # plot CPU time
561 for rt, roles in [('storage', STORAGE_ROLES), ('test', ['testnode'])]:
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300562 cpu_ts = get_cluster_cpu_load(self.rstorage, roles, job.reliable_info_range_s)
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300563 tss = [(name, ts.data * 100 / cpu_ts['total'].data)
564 for name, ts in cpu_ts.items()
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300565 if name in {'user', 'sys', 'idle', 'iowait'}]
566
567
568 ds = cpu_ts['idle'].source(job_id=job.storage_id, suite_id=suite.storage_id,
569 node_id=AGG_TAG, metric='allcpu', tag=rt + '.plt.' + default_format)
570
571 fname = self.plt(plot_simple_over_time, ds, tss=tss, average=True, ylabel="CPU time %",
572 title="{} nodes CPU usage".format(rt.capitalize()),
573 xlabel="Time from test begin")
kdanylov aka kodercdfcdaf2017-04-29 10:03:39 +0300574
575 yield Menu1st.per_job, job.summary, HTMLBlock(html.img(fname))
576
577
578# IO time and QD
579class QDIOTimeHeatmap(JobReporter):
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300580 def get_divs(self, suite: SuiteConfig, job: JobConfig) -> Iterator[Tuple[str, str, HTMLBlock]]:
kdanylov aka kodercdfcdaf2017-04-29 10:03:39 +0300581
582 # TODO: fix this hardcode, need to track what devices are actually used on test and storage nodes
583 # use saved storage info in nodes
584
585 journal_devs = None
586 storage_devs = None
587 test_nodes_devs = ['rbd0']
588
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300589 for node in find_nodes_by_roles(self.rstorage, STORAGE_ROLES):
kdanylov aka kodercdfcdaf2017-04-29 10:03:39 +0300590 cjd = set(node.params['ceph_journal_devs'])
591 if journal_devs is None:
592 journal_devs = cjd
593 else:
594 assert journal_devs == cjd, "{!r} != {!r}".format(journal_devs, cjd)
595
596 csd = set(node.params['ceph_storage_devs'])
597 if storage_devs is None:
598 storage_devs = csd
599 else:
600 assert storage_devs == csd, "{!r} != {!r}".format(storage_devs, csd)
601
kdanylov aka kodercdfcdaf2017-04-29 10:03:39 +0300602 trange = (job.reliable_info_range[0] // 1000, job.reliable_info_range[1] // 1000)
603
604 for name, devs, roles in [('storage', storage_devs, STORAGE_ROLES),
605 ('journal', journal_devs, STORAGE_ROLES),
606 ('test', test_nodes_devs, ['testnode'])]:
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300607
608 yield Menu1st.per_job, job.summary, \
609 HTMLBlock(html.H2(html.center("{} IO heatmaps".format(name.capitalize()))))
610
kdanylov aka kodercdfcdaf2017-04-29 10:03:39 +0300611 # QD heatmap
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300612 ioq2d = find_sensors_to_2d(self.rstorage, roles, sensor='block-io', devs=devs,
kdanylov aka kodercdfcdaf2017-04-29 10:03:39 +0300613 metric='io_queue', time_range=trange)
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300614
615 ds = DataSource(suite.storage_id, job.storage_id, AGG_TAG, 'block-io', name, tag="hmap." + default_format)
616
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300617 fname = self.plt(plot_hmap_from_2d, ds(metric='io_queue'), data2d=ioq2d, xlabel='Time', ylabel="IO QD",
618 title=name.capitalize() + " devs QD", bins=StyleProfile.qd_bins)
kdanylov aka kodercdfcdaf2017-04-29 10:03:39 +0300619 yield Menu1st.per_job, job.summary, HTMLBlock(html.img(fname))
620
621 # Block size heatmap
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300622 wc2d = find_sensors_to_2d(self.rstorage, roles, sensor='block-io', devs=devs,
kdanylov aka kodercdfcdaf2017-04-29 10:03:39 +0300623 metric='writes_completed', time_range=trange)
624 wc2d[wc2d < 1E-3] = 1
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300625 sw2d = find_sensors_to_2d(self.rstorage, roles, sensor='block-io', devs=devs,
kdanylov aka kodercdfcdaf2017-04-29 10:03:39 +0300626 metric='sectors_written', time_range=trange)
627 data2d = sw2d / wc2d / 1024
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300628 fname = self.plt(plot_hmap_from_2d, ds(metric='wr_block_size'),
629 data2d=data2d, title=name.capitalize() + " write block size",
630 ylabel="IO bsize, KiB", xlabel='Time', bins=StyleProfile.block_size_bins)
kdanylov aka kodercdfcdaf2017-04-29 10:03:39 +0300631 yield Menu1st.per_job, job.summary, HTMLBlock(html.img(fname))
632
633 # iotime heatmap
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300634 wtime2d = find_sensors_to_2d(self.rstorage, roles, sensor='block-io', devs=devs,
kdanylov aka kodercdfcdaf2017-04-29 10:03:39 +0300635 metric='io_time', time_range=trange)
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300636 fname = self.plt(plot_hmap_from_2d, ds(metric='io_time'), data2d=wtime2d,
637 xlabel='Time', ylabel="IO time (ms) per second",
638 title=name.capitalize() + " iotime", bins=StyleProfile.iotime_bins)
kdanylov aka kodercdfcdaf2017-04-29 10:03:39 +0300639 yield Menu1st.per_job, job.summary, HTMLBlock(html.img(fname))
640
641
koder aka kdanilov108ac362017-01-19 20:17:16 +0200642# IOPS/latency over test time for each job
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300643class LoadToolResults(JobReporter):
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200644 """IOPS/latency during test"""
koder aka kdanilova732a602017-02-01 20:29:56 +0200645 suite_types = {'fio'}
koder aka kdanilov108ac362017-01-19 20:17:16 +0200646
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300647 def get_divs(self, suite: SuiteConfig, job: JobConfig) -> Iterator[Tuple[str, str, HTMLBlock]]:
koder aka kdanilov108ac362017-01-19 20:17:16 +0200648
koder aka kdanilova732a602017-02-01 20:29:56 +0200649 fjob = cast(FioJobConfig, job)
koder aka kdanilov108ac362017-01-19 20:17:16 +0200650
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300651 yield Menu1st.per_job, job.summary, HTMLBlock(html.H2(html.center("Load tool results")))
652
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300653 agg_io = get_aggregated(self.rstorage, suite, fjob, "bw")
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300654 if fjob.bsize >= DefStyleProfile.large_blocks:
kdanylov aka kodercdfcdaf2017-04-29 10:03:39 +0300655 title = "Fio measured Bandwidth over time"
koder aka kdanilova732a602017-02-01 20:29:56 +0200656 units = "MiBps"
kdanylov aka koder45183182017-04-30 23:55:40 +0300657 agg_io.data //= int(unit_conversion_coef(units, agg_io.units))
koder aka kdanilova732a602017-02-01 20:29:56 +0200658 else:
kdanylov aka kodercdfcdaf2017-04-29 10:03:39 +0300659 title = "Fio measured IOPS over time"
kdanylov aka koder45183182017-04-30 23:55:40 +0300660 agg_io.data //= (int(unit_conversion_coef("KiBps", agg_io.units)) * fjob.bsize)
koder aka kdanilova732a602017-02-01 20:29:56 +0200661 units = "IOPS"
koder aka kdanilov108ac362017-01-19 20:17:16 +0200662
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300663 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 +0200664 yield Menu1st.per_job, fjob.summary, HTMLBlock(html.img(fpath))
koder aka kdanilov108ac362017-01-19 20:17:16 +0200665
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300666 if fjob.bsize < DefStyleProfile.large_blocks:
667 agg_lat = get_aggregated(self.rstorage, suite, fjob, "lat").copy()
668 TARGET_UNITS = 'ms'
669 coef = unit_conversion_coef(agg_lat.units, TARGET_UNITS)
670 agg_lat.histo_bins = agg_lat.histo_bins.copy() * float(coef)
671 agg_lat.units = TARGET_UNITS
koder aka kdanilov108ac362017-01-19 20:17:16 +0200672
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300673 fpath = self.plt(plot_lat_over_time, agg_lat.source(tag='ts.' + default_format), "Latency", agg_lat,
674 ylabel="Latency, " + agg_lat.units)
675 yield Menu1st.per_job, fjob.summary, HTMLBlock(html.img(fpath))
koder aka kdanilov108ac362017-01-19 20:17:16 +0200676
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300677 fpath = self.plt(plot_histo_heatmap, agg_lat.source(tag='hmap.' + default_format),
678 "Latency heatmap", agg_lat, ylabel="Latency, " + agg_lat.units, xlabel='Test time')
koder aka kdanilov108ac362017-01-19 20:17:16 +0200679
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300680 yield Menu1st.per_job, fjob.summary, HTMLBlock(html.img(fpath))
koder aka kdanilov108ac362017-01-19 20:17:16 +0200681
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300682 fjob = cast(FioJobConfig, job)
koder aka kdanilov108ac362017-01-19 20:17:16 +0200683
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300684 agg_io = get_aggregated(self.rstorage, suite, fjob, "bw")
koder aka kdanilov108ac362017-01-19 20:17:16 +0200685
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300686 if fjob.bsize >= DefStyleProfile.large_blocks:
687 title = "BW distribution"
688 units = "MiBps"
689 agg_io.data //= int(unit_conversion_coef(units, agg_io.units))
690 else:
691 title = "IOPS distribution"
692 agg_io.data //= (int(unit_conversion_coef("KiBps", agg_io.units)) * fjob.bsize)
693 units = "IOPS"
694
695 io_stat_prop = calc_norm_stat_props(agg_io, bins_count=StyleProfile.hist_boxes)
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300696 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 +0300697 yield Menu1st.per_job, fjob.summary, HTMLBlock(html.img(fpath))
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200698
699
700# Cluster load over test time
koder aka kdanilova732a602017-02-01 20:29:56 +0200701class ClusterLoad(JobReporter):
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200702 """IOPS/latency during test"""
703
koder aka kdanilova732a602017-02-01 20:29:56 +0200704 # TODO: units should came from sensor
koder aka kdanilov108ac362017-01-19 20:17:16 +0200705 storage_sensors = [
kdanylov aka koder45183182017-04-30 23:55:40 +0300706 ('block-io', 'reads_completed', "Read", 'iop'),
707 ('block-io', 'writes_completed', "Write", 'iop'),
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300708 ('block-io', 'sectors_read', "Read", 'MiB'),
709 ('block-io', 'sectors_written', "Write", 'MiB'),
koder aka kdanilov108ac362017-01-19 20:17:16 +0200710 ]
711
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300712 def get_divs(self, suite: SuiteConfig, job: JobConfig) -> Iterator[Tuple[str, str, HTMLBlock]]:
713
koder aka kdanilova732a602017-02-01 20:29:56 +0200714 yield Menu1st.per_job, job.summary, HTMLBlock(html.H2(html.center("Cluster load")))
koder aka kdanilov108ac362017-01-19 20:17:16 +0200715
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300716 sensors = []
717 max_iop = 0
718 max_bytes = 0
719
kdanylov aka koder45183182017-04-30 23:55:40 +0300720 for sensor, metric, op, units in self.storage_sensors:
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300721 ts = summ_sensors(self.rstorage, STORAGE_ROLES, sensor, metric, job.reliable_info_range_s)
koder aka kdanilova732a602017-02-01 20:29:56 +0200722 ds = DataSource(suite_id=suite.storage_id,
723 job_id=job.storage_id,
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300724 node_id="storage",
koder aka kdanilova732a602017-02-01 20:29:56 +0200725 sensor=sensor,
726 dev=AGG_TAG,
727 metric=metric,
kdanylov aka koder4e4af682017-05-01 01:52:14 +0300728 tag="ts." + default_format)
koder aka kdanilov108ac362017-01-19 20:17:16 +0200729
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300730 data = ts.data if units != 'MiB' else ts.data * float(unit_conversion_coef(ts.units, 'MiB'))
koder aka kdanilova732a602017-02-01 20:29:56 +0200731 ts = TimeSeries(name="",
kdanylov aka koder45183182017-04-30 23:55:40 +0300732 times=numpy.arange(*job.reliable_info_range_s),
kdanylov aka kodercdfcdaf2017-04-29 10:03:39 +0300733 data=data,
koder aka kdanilova732a602017-02-01 20:29:56 +0200734 raw=None,
kdanylov aka kodercdfcdaf2017-04-29 10:03:39 +0300735 units=units if ts.units is None else ts.units,
736 time_units=ts.time_units,
737 source=ds,
738 histo_bins=ts.histo_bins)
kdanylov aka koder0e0cfcb2017-03-27 22:19:09 +0300739
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300740 sensors.append(("{} {}".format(op, units), ds, ts, units))
koder aka kdanilov108ac362017-01-19 20:17:16 +0200741
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300742 if units == 'iop':
743 max_iop = max(max_iop, data.sum())
744 else:
745 assert units == 'MiB'
746 max_bytes = max(max_bytes, data.sum())
koder aka kdanilov108ac362017-01-19 20:17:16 +0200747
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300748 for title, ds, ts, units in sensors:
749 if ts.data.sum() >= (max_iop if units == 'iop' else max_bytes) * DefStyleProfile.min_load_diff:
750 fpath = self.plt(plot_v_over_time, ds, title, units, ts=ts)
751 yield Menu1st.per_job, job.summary, HTMLBlock(html.img(fpath))
752 else:
753 logger.info("Hide '%s' plot for %s, as it's cum load is less then %s%%",
754 title, job.summary, int(DefStyleProfile.min_load_diff * 100))
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200755
756
koder aka kdanilov108ac362017-01-19 20:17:16 +0200757# ------------------------------------------ REPORT STAGES -----------------------------------------------------------
758
759
760class HtmlReportStage(Stage):
761 priority = StepOrder.REPORT
762
763 def run(self, ctx: TestRun) -> None:
764 rstorage = ResultStorage(ctx.storage)
koder aka kdanilova732a602017-02-01 20:29:56 +0200765
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300766 job_reporters_cls = [StatInfo, Resources, LoadToolResults, ClusterLoad, CPULoadPlot, QDIOTimeHeatmap]
767 job_reporters = [rcls(rstorage, DefStyleProfile, DefColorProfile) for rcls in job_reporters_cls]
768
769 suite_reporters_cls = [IOQD, ResourceQD]
770 suite_reporters = [rcls(rstorage, DefStyleProfile, DefColorProfile) for rcls in suite_reporters_cls]
koder aka kdanilov108ac362017-01-19 20:17:16 +0200771
772 root_dir = os.path.dirname(os.path.dirname(wally.__file__))
773 doc_templ_path = os.path.join(root_dir, "report_templates/index.html")
774 report_template = open(doc_templ_path, "rt").read()
775 css_file_src = os.path.join(root_dir, "report_templates/main.css")
776 css_file = open(css_file_src, "rt").read()
777
778 menu_block = []
779 content_block = []
780 link_idx = 0
781
koder aka kdanilova732a602017-02-01 20:29:56 +0200782 # matplotlib.rcParams.update(ctx.config.reporting.matplotlib_params.raw())
783 # ColorProfile.__dict__.update(ctx.config.reporting.colors.raw())
784 # StyleProfile.__dict__.update(ctx.config.reporting.style.raw())
koder aka kdanilov108ac362017-01-19 20:17:16 +0200785
koder aka kdanilova732a602017-02-01 20:29:56 +0200786 items = defaultdict(lambda: defaultdict(list)) # type: Dict[str, Dict[str, List[HTMLBlock]]]
kdanylov aka kodercdfcdaf2017-04-29 10:03:39 +0300787 DEBUG = False
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300788 job_summ_sort_order = []
789
koder aka kdanilova732a602017-02-01 20:29:56 +0200790 # TODO: filter reporters
koder aka kdanilov108ac362017-01-19 20:17:16 +0200791 for suite in rstorage.iter_suite(FioTest.name):
koder aka kdanilova732a602017-02-01 20:29:56 +0200792 all_jobs = list(rstorage.iter_job(suite))
793 all_jobs.sort(key=lambda job: job.params)
koder aka kdanilova732a602017-02-01 20:29:56 +0200794
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300795 new_jobs_in_order = [job.summary for job in all_jobs]
796 same = set(new_jobs_in_order).intersection(set(job_summ_sort_order))
797 assert not same, "Job with same names in different suits found: " + ",".join(same)
798 job_summ_sort_order.extend(new_jobs_in_order)
799
800 for job in all_jobs[:1]:
801 try:
802 for reporter in job_reporters:
803 logger.debug("Start reporter %s on job %s suite %s",
804 reporter.__class__.__name__, job.summary, suite.test_type)
805 for block, item, html in reporter.get_divs(suite, job):
806 items[block][item].append(html)
807 if DEBUG:
808 break
809 except Exception:
810 logger.exception("Failed to generate report for %s", job.summary)
811
812 for reporter in suite_reporters:
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300813 try:
814 logger.debug("Start reporter %s on suite %s", reporter.__class__.__name__, suite.test_type)
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300815 for block, item, html in reporter.get_divs(suite):
kdanylov aka koder736e5c12017-05-07 17:27:14 +0300816 items[block][item].append(html)
817 except Exception as exc:
818 logger.exception("Failed to generate report")
koder aka kdanilov108ac362017-01-19 20:17:16 +0200819
koder aka kdanilova732a602017-02-01 20:29:56 +0200820 if DEBUG:
821 break
822
kdanylov aka kodercdfcdaf2017-04-29 10:03:39 +0300823 logger.debug("Generating result html")
824
koder aka kdanilov108ac362017-01-19 20:17:16 +0200825 for idx_1st, menu_1st in enumerate(sorted(items, key=lambda x: menu_1st_order.index(x))):
826 menu_block.append(
827 '<a href="#item{}" class="nav-group" data-toggle="collapse" data-parent="#MainMenu">{}</a>'
828 .format(idx_1st, menu_1st)
829 )
830 menu_block.append('<div class="collapse" id="item{}">'.format(idx_1st))
kdanylov aka koder3a9e5db2017-05-09 20:00:44 +0300831
832 if menu_1st == Menu1st.per_job:
833 in_order = sorted(items[menu_1st], key=job_summ_sort_order.index)
834 else:
835 in_order = sorted(items[menu_1st])
836
837 for menu_2nd in in_order:
koder aka kdanilov108ac362017-01-19 20:17:16 +0200838 menu_block.append(' <a href="#content{}" class="nav-group-item">{}</a>'
839 .format(link_idx, menu_2nd))
840 content_block.append('<div id="content{}">'.format(link_idx))
koder aka kdanilova732a602017-02-01 20:29:56 +0200841 content_block.extend(" " + x.data for x in items[menu_1st][menu_2nd])
koder aka kdanilov108ac362017-01-19 20:17:16 +0200842 content_block.append('</div>')
843 link_idx += 1
844 menu_block.append('</div>')
845
846 report = report_template.replace("{{{menu}}}", ("\n" + " " * 16).join(menu_block))
847 report = report.replace("{{{content}}}", ("\n" + " " * 16).join(content_block))
848 report_path = rstorage.put_report(report, "index.html")
849 rstorage.put_report(css_file, "main.css")
850 logger.info("Report is stored into %r", report_path)