koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 1 | import os |
kdanylov aka koder | 150b219 | 2017-04-01 16:53:01 +0300 | [diff] [blame] | 2 | import pprint |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 3 | import logging |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 4 | from typing import cast, Iterator, Tuple, Type, Dict, Optional, List, Any |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 5 | |
| 6 | import numpy |
| 7 | |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 8 | from .suits.job import JobConfig |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 9 | from .result_classes import SuiteConfig, TimeSeries, DataSource, StatProps, IResultStorage, ArrayData |
kdanylov aka koder | 0e0cfcb | 2017-03-27 22:19:09 +0300 | [diff] [blame] | 10 | from .storage import Storage |
| 11 | from .utils import StopTestError |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 12 | from .suits.all_suits import all_suits |
| 13 | |
| 14 | |
| 15 | logger = logging.getLogger('wally') |
| 16 | |
| 17 | |
| 18 | class DB_re: |
| 19 | node_id = r'\d+.\d+.\d+.\d+:\d+' |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 20 | job_id = r'[-a-zA-Z0-9_]+_\d+' |
| 21 | suite_id = r'[a-z_]+_\d+' |
| 22 | sensor = r'[-a-z_]+' |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 23 | dev = r'[-a-zA-Z0-9_]+' |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 24 | tag = r'[a-z_.]+' |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 25 | metric = r'[a-z_.]+' |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 26 | |
| 27 | |
| 28 | class DB_paths: |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 29 | suite_cfg_r = r'results/{suite_id}\.info\.yml' |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 30 | |
kdanylov aka koder | 150b219 | 2017-04-01 16:53:01 +0300 | [diff] [blame] | 31 | job_root = r'results/{suite_id}\.{job_id}/' |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 32 | job_cfg_r = job_root + r'info\.yml' |
| 33 | |
| 34 | # time series, data from load tool, sensor is a tool name |
kdanylov aka koder | 150b219 | 2017-04-01 16:53:01 +0300 | [diff] [blame] | 35 | ts_r = job_root + r'{node_id}\.{sensor}\.{metric}\.{tag}' |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 36 | |
| 37 | # statistica data for ts |
kdanylov aka koder | 150b219 | 2017-04-01 16:53:01 +0300 | [diff] [blame] | 38 | stat_r = job_root + r'{node_id}\.{sensor}\.{metric}\.stat\.yaml' |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 39 | |
| 40 | # sensor data |
kdanylov aka koder | 4518318 | 2017-04-30 23:55:40 +0300 | [diff] [blame] | 41 | sensor_data_r = r'sensors/{node_id}_{sensor}\.{dev}\.{metric}\.{tag}' |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 42 | sensor_time_r = r'sensors/{node_id}_collected_at\.csv' |
| 43 | |
| 44 | report_root = 'report/' |
kdanylov aka koder | 0e0cfcb | 2017-03-27 22:19:09 +0300 | [diff] [blame] | 45 | plot_r = r'{suite_id}\.{job_id}/{node_id}\.{sensor}\.{dev}\.{metric}\.{tag}' |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 46 | txt_report = report_root + '{suite_id}_report.txt' |
| 47 | |
| 48 | job_extra = 'meta/{suite_id}.{job_id}/{tag}' |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 49 | |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 50 | job_cfg = job_cfg_r.replace("\\.", '.') |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 51 | suite_cfg = suite_cfg_r.replace("\\.", '.') |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 52 | ts = ts_r.replace("\\.", '.') |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 53 | stat = stat_r.replace("\\.", '.') |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 54 | sensor_data = sensor_data_r.replace("\\.", '.') |
| 55 | sensor_time = sensor_time_r.replace("\\.", '.') |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 56 | plot = plot_r.replace("\\.", '.') |
| 57 | |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 58 | |
| 59 | DB_rr = {name: r"(?P<{}>{})".format(name, rr) |
| 60 | for name, rr in DB_re.__dict__.items() |
| 61 | if not name.startswith("__")} |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 62 | |
| 63 | |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 64 | def fill_path(path: str, **params) -> str: |
| 65 | for name, val in params.items(): |
| 66 | if val is not None: |
| 67 | path = path.replace("{" + name + "}", val) |
| 68 | return path |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 69 | |
| 70 | |
| 71 | class ResultStorage(IResultStorage): |
| 72 | # TODO: check that all path components match required patterns |
| 73 | |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 74 | ts_header_size = 64 |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 75 | ts_header_format = "!IIIcc" |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 76 | ts_arr_tag = 'csv' |
kdanylov aka koder | 0e0cfcb | 2017-03-27 22:19:09 +0300 | [diff] [blame] | 77 | csv_file_encoding = 'ascii' |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 78 | |
| 79 | def __init__(self, storage: Storage) -> None: |
| 80 | self.storage = storage |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 81 | self.cache = {} # type: Dict[str, Tuple[int, int, ArrayData]] |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 82 | |
| 83 | def sync(self) -> None: |
| 84 | self.storage.sync() |
| 85 | |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 86 | # ----------------- SERIALIZATION / DESERIALIZATION ------------------------------------------------------------- |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 87 | def read_headers(self, fd) -> Tuple[str, List[str], List[str], Optional[numpy.ndarray]]: |
| 88 | header = fd.readline().decode(self.csv_file_encoding).rstrip().split(",") |
| 89 | dtype, has_header2, header2_dtype, *ext_header = header |
| 90 | |
| 91 | if has_header2 == 'true': |
| 92 | ln = fd.readline().decode(self.csv_file_encoding).strip() |
| 93 | header2 = numpy.fromstring(ln, sep=',', dtype=header2_dtype) |
| 94 | else: |
| 95 | assert has_header2 == 'false', \ |
| 96 | "In file {} has_header2 is not true/false, but {!r}".format(fd.name, has_header2) |
| 97 | header2 = None |
| 98 | return dtype, ext_header, header, header2 |
| 99 | |
| 100 | def load_array(self, path: str) -> ArrayData: |
| 101 | """ |
| 102 | Load array from file, shoult not be called directly |
| 103 | :param path: file path |
| 104 | :return: ArrayData |
| 105 | """ |
kdanylov aka koder | 0e0cfcb | 2017-03-27 22:19:09 +0300 | [diff] [blame] | 106 | with self.storage.get_fd(path, "rb") as fd: |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 107 | fd.seek(0, os.SEEK_SET) |
| 108 | |
kdanylov aka koder | 0e0cfcb | 2017-03-27 22:19:09 +0300 | [diff] [blame] | 109 | stats = os.fstat(fd.fileno()) |
| 110 | if path in self.cache: |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 111 | size, atime, arr_info = self.cache[path] |
kdanylov aka koder | 0e0cfcb | 2017-03-27 22:19:09 +0300 | [diff] [blame] | 112 | if size == stats.st_size and atime == stats.st_atime_ns: |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 113 | return arr_info |
kdanylov aka koder | 0e0cfcb | 2017-03-27 22:19:09 +0300 | [diff] [blame] | 114 | |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 115 | data_dtype, header, _, header2 = self.read_headers(fd) |
| 116 | assert data_dtype == 'uint64', path |
| 117 | dt = fd.read().decode(self.csv_file_encoding).strip() |
kdanylov aka koder | 150b219 | 2017-04-01 16:53:01 +0300 | [diff] [blame] | 118 | |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 119 | if len(dt) != 0: |
| 120 | arr = numpy.fromstring(dt.replace("\n", ','), sep=',', dtype=data_dtype) |
| 121 | lines = dt.count("\n") + 1 |
| 122 | assert len(set(ln.count(',') for ln in dt.split("\n"))) == 1, \ |
| 123 | "Data lines in {!r} have different element count".format(path) |
| 124 | arr.shape = [lines] if lines == arr.size else [lines, -1] |
| 125 | else: |
| 126 | arr = None |
kdanylov aka koder | 150b219 | 2017-04-01 16:53:01 +0300 | [diff] [blame] | 127 | |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 128 | arr_data = ArrayData(header, header2, arr) |
| 129 | self.cache[path] = (stats.st_size, stats.st_atime_ns, arr_data) |
| 130 | return arr_data |
kdanylov aka koder | 0e0cfcb | 2017-03-27 22:19:09 +0300 | [diff] [blame] | 131 | |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 132 | def put_array(self, path: str, data: numpy.array, header: List[str], header2: numpy.ndarray = None, |
| 133 | append_on_exists: bool = False) -> None: |
kdanylov aka koder | 0e0cfcb | 2017-03-27 22:19:09 +0300 | [diff] [blame] | 134 | |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 135 | header = [data.dtype.name] + \ |
| 136 | (['false', ''] if header2 is None else ['true', header2.dtype.name]) + \ |
| 137 | header |
kdanylov aka koder | 0e0cfcb | 2017-03-27 22:19:09 +0300 | [diff] [blame] | 138 | |
| 139 | exists = append_on_exists and path in self.storage |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 140 | vw = data.view().reshape((data.shape[0], 1)) if len(data.shape) == 1 else data |
| 141 | mode = "cb" if not exists else "rb+" |
kdanylov aka koder | 0e0cfcb | 2017-03-27 22:19:09 +0300 | [diff] [blame] | 142 | |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 143 | with self.storage.get_fd(path, mode) as fd: |
kdanylov aka koder | 0e0cfcb | 2017-03-27 22:19:09 +0300 | [diff] [blame] | 144 | if exists: |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 145 | data_dtype, _, full_header, curr_header2 = self.read_headers(fd) |
| 146 | |
| 147 | assert data_dtype == data.dtype.name, \ |
| 148 | "Path {!r}. Passed data type ({!r}) and current data type ({!r}) doesn't match"\ |
| 149 | .format(path, data.dtype.name, data_dtype) |
| 150 | |
| 151 | assert header == full_header, \ |
| 152 | "Path {!r}. Passed header ({!r}) and current header ({!r}) doesn't match"\ |
| 153 | .format(path, header, full_header) |
| 154 | |
| 155 | assert header2 == curr_header2, \ |
| 156 | "Path {!r}. Passed header2 != current header2: {!r}\n{!r}"\ |
| 157 | .format(path, header2, curr_header2) |
| 158 | |
kdanylov aka koder | 0e0cfcb | 2017-03-27 22:19:09 +0300 | [diff] [blame] | 159 | fd.seek(0, os.SEEK_END) |
| 160 | else: |
| 161 | fd.write((",".join(header) + "\n").encode(self.csv_file_encoding)) |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 162 | if header2 is not None: |
| 163 | fd.write((",".join(map(str, header2)) + "\n").encode(self.csv_file_encoding)) |
kdanylov aka koder | 0e0cfcb | 2017-03-27 22:19:09 +0300 | [diff] [blame] | 164 | |
| 165 | numpy.savetxt(fd, vw, delimiter=',', newline="\n", fmt="%lu") |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 166 | |
| 167 | def load_ts(self, ds: DataSource, path: str) -> TimeSeries: |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 168 | """ |
| 169 | Load time series, generated by fio or other tool, should not be called directly, |
| 170 | use iter_ts istead. |
| 171 | :param ds: data source path |
| 172 | :param path: path in data storage |
| 173 | :return: TimeSeries |
| 174 | """ |
| 175 | (units, time_units), header2, data = self.load_array(path) |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 176 | times = data[:,0].copy() |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 177 | ts_data = data[:,1:] |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 178 | |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 179 | if ts_data.shape[1] == 1: |
| 180 | ts_data.shape = (ts_data.shape[0],) |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 181 | |
| 182 | return TimeSeries("{}.{}".format(ds.dev, ds.sensor), |
| 183 | raw=None, |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 184 | data=ts_data, |
| 185 | times=times, |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 186 | source=ds, |
| 187 | units=units, |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 188 | time_units=time_units, |
| 189 | histo_bins=header2) |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 190 | |
kdanylov aka koder | 4518318 | 2017-04-30 23:55:40 +0300 | [diff] [blame] | 191 | def load_sensor_raw(self, ds: DataSource) -> bytes: |
| 192 | path = DB_paths.sensor_data.format(**ds.__dict__) |
| 193 | with self.storage.get_fd(path, "rb") as fd: |
| 194 | return fd.read() |
| 195 | |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 196 | def load_sensor(self, ds: DataSource) -> TimeSeries: |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 197 | # sensors has no shape |
| 198 | path = DB_paths.sensor_time.format(**ds.__dict__) |
| 199 | collect_header, must_be_none, collected_at = self.load_array(path) |
| 200 | |
| 201 | # cut 'collection end' time |
kdanylov aka koder | 4518318 | 2017-04-30 23:55:40 +0300 | [diff] [blame] | 202 | # .copy needed to really remove 'collection end' element to make c_interpolate_.. works correctly |
| 203 | collected_at = collected_at[::2].copy() |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 204 | |
| 205 | # there must be no histogram for collected_at |
| 206 | assert must_be_none is None, "Extra header2 {!r} in collect_at file at {!r}".format(must_be_none, path) |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 207 | node, tp, units = collect_header |
| 208 | assert node == ds.node_id and tp == 'collected_at' and units in ('ms', 'us'),\ |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 209 | "Unexpected collect_at header {!r} at {!r}".format(collect_header, path) |
| 210 | assert len(collected_at.shape) == 1, "Collected_at must be 1D at {!r}".format(path) |
| 211 | |
| 212 | data_path = DB_paths.sensor_data.format(**ds.__dict__) |
| 213 | data_header, must_be_none, data = self.load_array(data_path) |
| 214 | |
| 215 | # there must be no histogram for any sensors |
| 216 | assert must_be_none is None, "Extra header2 {!r} in sensor data file {!r}".format(must_be_none, data_path) |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 217 | |
| 218 | data_units = data_header[2] |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 219 | assert data_header == [ds.node_id, ds.metric_fqdn, data_units], \ |
| 220 | "Unexpected data header {!r} at {!r}".format(data_header, data_path) |
| 221 | assert len(data.shape) == 1, "Sensor data must be 1D at {!r}".format(data_path) |
kdanylov aka koder | 0e0cfcb | 2017-03-27 22:19:09 +0300 | [diff] [blame] | 222 | |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 223 | return TimeSeries(ds.metric_fqdn, |
| 224 | raw=None, |
| 225 | data=data, |
| 226 | times=collected_at, |
| 227 | source=ds, |
| 228 | units=data_units, |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 229 | time_units=units) |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 230 | |
| 231 | # ------------- CHECK DATA IN STORAGE ---------------------------------------------------------------------------- |
| 232 | |
| 233 | def check_plot_file(self, source: DataSource) -> Optional[str]: |
| 234 | path = DB_paths.plot.format(**source.__dict__) |
kdanylov aka koder | 0e0cfcb | 2017-03-27 22:19:09 +0300 | [diff] [blame] | 235 | fpath = self.storage.resolve_raw(DB_paths.report_root + path) |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 236 | return path if os.path.exists(fpath) else None |
| 237 | |
| 238 | # ------------- PUT DATA INTO STORAGE -------------------------------------------------------------------------- |
| 239 | |
| 240 | def put_or_check_suite(self, suite: SuiteConfig) -> None: |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 241 | path = DB_paths.suite_cfg.format(suite_id=suite.storage_id) |
| 242 | if path in self.storage: |
kdanylov aka koder | 150b219 | 2017-04-01 16:53:01 +0300 | [diff] [blame] | 243 | db_cfg = self.storage.load(SuiteConfig, path) |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 244 | if db_cfg != suite: |
| 245 | logger.error("Current suite %s config is not equal to found in storage at %s", suite.test_type, path) |
kdanylov aka koder | 150b219 | 2017-04-01 16:53:01 +0300 | [diff] [blame] | 246 | logger.debug("Current: \n%s\nStorage:\n%s", pprint.pformat(db_cfg), pprint.pformat(suite)) |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 247 | raise StopTestError() |
kdanylov aka koder | 150b219 | 2017-04-01 16:53:01 +0300 | [diff] [blame] | 248 | else: |
| 249 | self.storage.put(suite, path) |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 250 | |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 251 | def put_job(self, suite: SuiteConfig, job: JobConfig) -> None: |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 252 | path = DB_paths.job_cfg.format(suite_id=suite.storage_id, job_id=job.storage_id) |
| 253 | self.storage.put(job, path) |
| 254 | |
| 255 | def put_ts(self, ts: TimeSeries) -> None: |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 256 | assert ts.data.dtype == ts.times.dtype, "Data type {!r} != time type {!r}".format(ts.data.dtype, ts.times.dtype) |
| 257 | assert ts.data.dtype.kind == 'u', "Only unsigned ints are accepted" |
| 258 | assert ts.source.tag == self.ts_arr_tag, "Incorrect source tag == {!r}, must be {!r}".format(ts.source.tag, |
| 259 | self.ts_arr_tag) |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 260 | csv_path = DB_paths.ts.format(**ts.source.__dict__) |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 261 | header = [ts.units, ts.time_units] |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 262 | |
kdanylov aka koder | 0e0cfcb | 2017-03-27 22:19:09 +0300 | [diff] [blame] | 263 | tv = ts.times.view().reshape((-1, 1)) |
| 264 | if len(ts.data.shape) == 1: |
| 265 | dv = ts.data.view().reshape((ts.times.shape[0], -1)) |
| 266 | else: |
| 267 | dv = ts.data |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 268 | |
kdanylov aka koder | 0e0cfcb | 2017-03-27 22:19:09 +0300 | [diff] [blame] | 269 | result = numpy.concatenate((tv, dv), axis=1) |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 270 | if ts.histo_bins is not None: |
| 271 | self.put_array(csv_path, result, header, header2=ts.histo_bins) |
| 272 | else: |
| 273 | self.put_array(csv_path, result, header) |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 274 | |
| 275 | if ts.raw: |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 276 | raw_path = DB_paths.ts.format(**ts.source(tag=ts.raw_tag).__dict__) |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 277 | self.storage.put_raw(ts.raw, raw_path) |
| 278 | |
| 279 | def put_extra(self, data: bytes, source: DataSource) -> None: |
kdanylov aka koder | 150b219 | 2017-04-01 16:53:01 +0300 | [diff] [blame] | 280 | self.storage.put_raw(data, DB_paths.ts.format(**source.__dict__)) |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 281 | |
| 282 | def put_stat(self, data: StatProps, source: DataSource) -> None: |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 283 | self.storage.put(data, DB_paths.stat.format(**source.__dict__)) |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 284 | |
| 285 | # return path to file to be inserted into report |
| 286 | def put_plot_file(self, data: bytes, source: DataSource) -> str: |
| 287 | path = DB_paths.plot.format(**source.__dict__) |
kdanylov aka koder | 0e0cfcb | 2017-03-27 22:19:09 +0300 | [diff] [blame] | 288 | self.storage.put_raw(data, DB_paths.report_root + path) |
| 289 | return path |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 290 | |
koder aka kdanilov | 108ac36 | 2017-01-19 20:17:16 +0200 | [diff] [blame] | 291 | def put_report(self, report: str, name: str) -> str: |
kdanylov aka koder | 0e0cfcb | 2017-03-27 22:19:09 +0300 | [diff] [blame] | 292 | return self.storage.put_raw(report.encode(self.csv_file_encoding), DB_paths.report_root + name) |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 293 | |
kdanylov aka koder | 4518318 | 2017-04-30 23:55:40 +0300 | [diff] [blame] | 294 | def put_sensor_raw(self, data: bytes, ds: DataSource) -> None: |
| 295 | path = DB_paths.sensor_data.format(**ds.__dict__) |
| 296 | with self.storage.get_fd(path, "cb") as fd: |
| 297 | fd.write(data) |
| 298 | |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 299 | def append_sensor(self, data: numpy.array, ds: DataSource, units: str, histo_bins: numpy.ndarray = None) -> None: |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 300 | if ds.metric == 'collected_at': |
| 301 | path = DB_paths.sensor_time |
| 302 | metrics_fqn = 'collected_at' |
| 303 | else: |
| 304 | path = DB_paths.sensor_data |
| 305 | metrics_fqn = ds.metric_fqdn |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 306 | |
| 307 | if ds.metric == 'lat': |
| 308 | assert len(data.shape) == 2, "Latency should be histo array" |
| 309 | assert histo_bins is not None, "Latency should have histo bins" |
| 310 | |
| 311 | path = path.format(**ds.__dict__) |
| 312 | self.put_array(path, data, [ds.node_id, metrics_fqn, units], header2=histo_bins, append_on_exists=True) |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 313 | |
| 314 | # ------------- GET DATA FROM STORAGE -------------------------------------------------------------------------- |
| 315 | |
| 316 | def get_stat(self, stat_cls: Type[StatProps], source: DataSource) -> StatProps: |
| 317 | return self.storage.load(stat_cls, DB_paths.stat.format(**source.__dict__)) |
| 318 | |
| 319 | # ------------- ITER OVER STORAGE ------------------------------------------------------------------------------ |
| 320 | |
| 321 | def iter_paths(self, path_glob) -> Iterator[Tuple[bool, str, Dict[str, str]]]: |
| 322 | path = path_glob.format(**DB_rr).split("/") |
| 323 | yield from self.storage._iter_paths("", path, {}) |
| 324 | |
| 325 | def iter_suite(self, suite_type: str = None) -> Iterator[SuiteConfig]: |
| 326 | for is_file, suite_info_path, groups in self.iter_paths(DB_paths.suite_cfg_r): |
| 327 | assert is_file |
| 328 | suite = self.storage.load(SuiteConfig, suite_info_path) |
| 329 | # suite = cast(SuiteConfig, self.storage.load(SuiteConfig, suite_info_path)) |
| 330 | assert suite.storage_id == groups['suite_id'] |
| 331 | if not suite_type or suite.test_type == suite_type: |
| 332 | yield suite |
| 333 | |
| 334 | def iter_job(self, suite: SuiteConfig) -> Iterator[JobConfig]: |
| 335 | job_glob = fill_path(DB_paths.job_cfg_r, suite_id=suite.storage_id) |
| 336 | job_config_cls = all_suits[suite.test_type].job_config_cls |
| 337 | for is_file, path, groups in self.iter_paths(job_glob): |
| 338 | assert is_file |
| 339 | job = cast(JobConfig, self.storage.load(job_config_cls, path)) |
| 340 | assert job.storage_id == groups['job_id'] |
| 341 | yield job |
| 342 | |
| 343 | # iterate over test tool data |
| 344 | def iter_ts(self, suite: SuiteConfig, job: JobConfig, **filters) -> Iterator[TimeSeries]: |
| 345 | filters.update(suite_id=suite.storage_id, job_id=job.storage_id) |
| 346 | ts_glob = fill_path(DB_paths.ts_r, **filters) |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 347 | for is_file, path, groups in self.iter_paths(ts_glob): |
kdanylov aka koder | 150b219 | 2017-04-01 16:53:01 +0300 | [diff] [blame] | 348 | tag = groups["tag"] |
| 349 | if tag != 'csv': |
| 350 | continue |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 351 | assert is_file |
| 352 | groups = groups.copy() |
| 353 | groups.update(filters) |
| 354 | ds = DataSource(suite_id=suite.storage_id, |
| 355 | job_id=job.storage_id, |
| 356 | node_id=groups["node_id"], |
| 357 | sensor=groups["sensor"], |
| 358 | dev=None, |
| 359 | metric=groups["metric"], |
kdanylov aka koder | 150b219 | 2017-04-01 16:53:01 +0300 | [diff] [blame] | 360 | tag=tag) |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 361 | yield self.load_ts(ds, path) |
| 362 | |
| 363 | def iter_sensors(self, node_id: str = None, sensor: str = None, dev: str = None, metric: str = None) -> \ |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 364 | Iterator[Tuple[str, DataSource]]: |
| 365 | vls = dict(node_id=node_id, sensor=sensor, dev=dev, metric=metric) |
| 366 | path = fill_path(DB_paths.sensor_data_r, **vls) |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 367 | for is_file, path, groups in self.iter_paths(path): |
kdanylov aka koder | cdfcdaf | 2017-04-29 10:03:39 +0300 | [diff] [blame] | 368 | cvls = vls.copy() |
| 369 | cvls.update(groups) |
| 370 | yield path, DataSource(**cvls) |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 371 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 372 | def get_txt_report(self, suite: SuiteConfig) -> Optional[str]: |
| 373 | path = DB_paths.txt_report.format(suite_id=suite.storage_id) |
| 374 | if path in self.storage: |
| 375 | return self.storage.get_raw(path).decode('utf8') |
koder aka kdanilov | a732a60 | 2017-02-01 20:29:56 +0200 | [diff] [blame] | 376 | |
kdanylov aka koder | 3a9e5db | 2017-05-09 20:00:44 +0300 | [diff] [blame^] | 377 | def put_txt_report(self, suite: SuiteConfig, report: str) -> None: |
| 378 | path = DB_paths.txt_report.format(suite_id=suite.storage_id) |
| 379 | self.storage.put_raw(report.encode('utf8'), path) |
| 380 | |
| 381 | def put_job_info(self, suite: SuiteConfig, job: JobConfig, key: str, data: Any) -> None: |
| 382 | path = DB_paths.job_extra.format(suite_id=suite.storage_id, job_id=job.storage_id, tag=key) |
| 383 | self.storage.put(data, path) |
| 384 | |
| 385 | def get_job_info(self, suite: SuiteConfig, job: JobConfig, key: str) -> Any: |
| 386 | path = DB_paths.job_extra.format(suite_id=suite.storage_id, job_id=job.storage_id, tag=key) |
| 387 | return self.storage.get(path, None) |