blob: 3616f477b9d42354b991ffafcfb0bb7e4c715f56 [file] [log] [blame]
koder aka kdanilov23e6bdf2016-12-24 02:18:54 +02001import array
koder aka kdanilovf2865172016-12-30 03:35:11 +02002from typing import Dict, List, Any, Optional, Tuple, cast
3
koder aka kdanilovffaf48d2016-12-27 02:25:29 +02004
5import numpy
koder aka kdanilovf2865172016-12-30 03:35:11 +02006from scipy.stats.mstats_basic import NormaltestResult
koder aka kdanilovffaf48d2016-12-27 02:25:29 +02007
8
koder aka kdanilovf2865172016-12-30 03:35:11 +02009from .node_interfaces import IRPCNode
10from .istorable import IStorable, Storable
11from .utils import round_digits, Number
koder aka kdanilov70227062016-11-26 23:23:21 +020012
13
koder aka kdanilovf2865172016-12-30 03:35:11 +020014class TestJobConfig(Storable):
15 def __init__(self) -> None:
16 self.summary = None # type: str
koder aka kdanilov70227062016-11-26 23:23:21 +020017
koder aka kdanilovf2865172016-12-30 03:35:11 +020018
19class TestSuiteConfig(IStorable):
20 """
21 Test suite input configuration.
22
23 test_type - test type name
24 params - parameters from yaml file for this test
25 run_uuid - UUID to be used to create file names & Co
26 nodes - nodes to run tests on
27 remote_dir - directory on nodes to be used for local files
28 """
29 def __init__(self,
30 test_type: str,
31 params: Dict[str, Any],
32 run_uuid: str,
33 nodes: List[IRPCNode],
34 remote_dir: str) -> None:
35 self.test_type = test_type
36 self.params = params
37 self.run_uuid = run_uuid
38 self.nodes = nodes
39 self.nodes_ids = [node.info.node_id() for node in nodes]
40 self.remote_dir = remote_dir
41
42 def __eq__(self, other: 'TestSuiteConfig') -> bool:
43 return (self.test_type == other.test_type and
44 self.params == other.params and
45 set(self.nodes_ids) == set(other.nodes_ids))
46
47 def raw(self) -> Dict[str, Any]:
48 res = self.__dict__.copy()
49 del res['nodes']
50 del res['run_uuid']
51 del res['remote_dir']
52 return res
53
54 @classmethod
55 def fromraw(cls, data: Dict[str, Any]) -> 'IStorable':
56 obj = cls.__new__(cls)
57 data = data.copy()
58 data['nodes'] = None
59 data['run_uuid'] = None
60 data['remote_dir'] = None
61 obj.__dict__.update(data)
62 return obj
63
64
65class TimeSeries:
66 """Data series from sensor - either system sensor or from load generator tool (e.g. fio)"""
67
68 def __init__(self,
69 name: str,
70 raw: Optional[bytes],
71 data: array.array,
72 times: array.array,
73 second_axis_size: int = 1,
74 bins_edges: List[float] = None) -> None:
75
76 # Sensor name. Typically DEV_NAME.METRIC
koder aka kdanilov23e6bdf2016-12-24 02:18:54 +020077 self.name = name
koder aka kdanilovf2865172016-12-30 03:35:11 +020078
79 # Time series times and values. Time in ms from Unix epoch.
80 self.times = times # type: List[int]
81 self.data = data # type: List[int]
82
83 # Not equal to 1 in case of 2d sensors, like latency, when each measurement is a histogram.
koder aka kdanilov23e6bdf2016-12-24 02:18:54 +020084 self.second_axis_size = second_axis_size
koder aka kdanilovf2865172016-12-30 03:35:11 +020085
86 # Raw sensor data (is provided). Like log file for fio iops/bw/lat.
koder aka kdanilov23e6bdf2016-12-24 02:18:54 +020087 self.raw = raw
koder aka kdanilov70227062016-11-26 23:23:21 +020088
koder aka kdanilovf2865172016-12-30 03:35:11 +020089 # bin edges for historgam timeseries
90 self.bins_edges = bins_edges
koder aka kdanilov70227062016-11-26 23:23:21 +020091
92
koder aka kdanilovf2865172016-12-30 03:35:11 +020093# (node_name, source_dev, metric_name) => metric_results
94JobMetrics = Dict[Tuple[str, str, str], TimeSeries]
koder aka kdanilov70227062016-11-26 23:23:21 +020095
96
koder aka kdanilovf2865172016-12-30 03:35:11 +020097class StatProps(IStorable):
98 "Statistic properties for timeseries with unknown data distribution"
koder aka kdanilovffaf48d2016-12-27 02:25:29 +020099 def __init__(self, data: List[Number]) -> None:
koder aka kdanilovffaf48d2016-12-27 02:25:29 +0200100 self.perc_99 = None # type: float
101 self.perc_95 = None # type: float
102 self.perc_90 = None # type: float
103 self.perc_50 = None # type: float
koder aka kdanilov70227062016-11-26 23:23:21 +0200104
koder aka kdanilovffaf48d2016-12-27 02:25:29 +0200105 self.min = None # type: Number
106 self.max = None # type: Number
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200107
koder aka kdanilovffaf48d2016-12-27 02:25:29 +0200108 # bin_center: bin_count
109 self.bins_populations = None # type: List[int]
110 self.bins_edges = None # type: List[float]
111 self.data = data
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200112
koder aka kdanilovf2865172016-12-30 03:35:11 +0200113 def __str__(self) -> str:
114 res = ["{}(size = {}):".format(self.__class__.__name__, len(self.data))]
115 for name in ["perc_50", "perc_90", "perc_95", "perc_99"]:
116 res.append(" {} = {}".format(name, round_digits(getattr(self, name))))
117 res.append(" range {} {}".format(round_digits(self.min), round_digits(self.max)))
118 return "\n".join(res)
119
120 def __repr__(self) -> str:
121 return str(self)
122
123 def raw(self) -> Dict[str, Any]:
124 data = self.__dict__.copy()
125 data['bins_edges'] = list(self.bins_edges)
126 data['bins_populations'] = list(self.bins_populations)
127 return data
128
129 @classmethod
130 def fromraw(cls, data: Dict[str, Any]) -> 'StatProps':
131 data['bins_edges'] = numpy.array(data['bins_edges'])
132 data['bins_populations'] = numpy.array(data['bins_populations'])
133 res = cls.__new__(cls)
134 res.__dict__.update(data)
135 return res
136
137
138class HistoStatProps(StatProps):
139 """Statistic properties for 2D timeseries with unknown data distribution and histogram as input value.
140 Used for latency"""
141 def __init__(self, data: List[Number], second_axis_size: int) -> None:
142 self.second_axis_size = second_axis_size
143 StatProps.__init__(self, data)
144
145
146class NormStatProps(StatProps):
147 "Statistic properties for timeseries with normal data distribution. Used for iops/bw"
148 def __init__(self, data: List[Number]) -> None:
149 StatProps.__init__(self, data)
150
151 self.average = None # type: float
152 self.deviation = None # type: float
153 self.confidence = None # type: float
154 self.confidence_level = None # type: float
155 self.normtest = None # type: NormaltestResult
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200156
koder aka kdanilovffaf48d2016-12-27 02:25:29 +0200157 def __str__(self) -> str:
koder aka kdanilovf2865172016-12-30 03:35:11 +0200158 res = ["NormStatProps(size = {}):".format(len(self.data)),
koder aka kdanilovffaf48d2016-12-27 02:25:29 +0200159 " distr = {} ~ {}".format(round_digits(self.average), round_digits(self.deviation)),
160 " confidence({0.confidence_level}) = {1}".format(self, round_digits(self.confidence)),
161 " perc_50 = {}".format(round_digits(self.perc_50)),
162 " perc_90 = {}".format(round_digits(self.perc_90)),
163 " perc_95 = {}".format(round_digits(self.perc_95)),
164 " perc_99 = {}".format(round_digits(self.perc_99)),
165 " range {} {}".format(round_digits(self.min), round_digits(self.max)),
166 " normtest = {0.normtest}".format(self)]
167 return "\n".join(res)
168
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200169 def raw(self) -> Dict[str, Any]:
koder aka kdanilovffaf48d2016-12-27 02:25:29 +0200170 data = self.__dict__.copy()
koder aka kdanilovf2865172016-12-30 03:35:11 +0200171 data['normtest'] = (data['nortest'].statistic, data['nortest'].pvalue)
koder aka kdanilovffaf48d2016-12-27 02:25:29 +0200172 data['bins_edges'] = list(self.bins_edges)
173 return data
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200174
koder aka kdanilovffaf48d2016-12-27 02:25:29 +0200175 @classmethod
176 def fromraw(cls, data: Dict[str, Any]) -> 'NormStatProps':
koder aka kdanilovf2865172016-12-30 03:35:11 +0200177 data['normtest'] = NormaltestResult(*data['normtest'])
178 obj = StatProps.fromraw(data)
179 obj.__class__ = cls
180 return cast('NormStatProps', obj)
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200181
182
koder aka kdanilovf2865172016-12-30 03:35:11 +0200183JobStatMetrics = Dict[Tuple[str, str, str], StatProps]
184
185
186class TestJobResult:
187 """Contains done test job information"""
188
189 def __init__(self,
190 info: TestJobConfig,
191 begin_time: int,
192 end_time: int,
193 raw: JobMetrics) -> None:
koder aka kdanilovffaf48d2016-12-27 02:25:29 +0200194 self.info = info
koder aka kdanilovf2865172016-12-30 03:35:11 +0200195 self.run_interval = (begin_time, end_time)
196 self.raw = raw # type: JobMetrics
197 self.processed = None # type: JobStatMetrics