argus-alm 0.12.9__py3-none-any.whl → 0.13.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- argus/client/base.py +1 -1
- argus/client/driver_matrix_tests/cli.py +2 -2
- argus/client/driver_matrix_tests/client.py +1 -1
- argus/client/generic/cli.py +2 -2
- argus/client/generic_result.py +3 -2
- argus/client/sct/client.py +3 -3
- argus/client/sirenada/client.py +1 -1
- {argus_alm-0.12.9.dist-info → argus_alm-0.13.0.dist-info}/METADATA +2 -4
- argus_alm-0.13.0.dist-info/RECORD +20 -0
- argus/backend/.gitkeep +0 -0
- argus/backend/cli.py +0 -41
- argus/backend/controller/__init__.py +0 -0
- argus/backend/controller/admin.py +0 -20
- argus/backend/controller/admin_api.py +0 -354
- argus/backend/controller/api.py +0 -529
- argus/backend/controller/auth.py +0 -67
- argus/backend/controller/client_api.py +0 -108
- argus/backend/controller/main.py +0 -274
- argus/backend/controller/notification_api.py +0 -72
- argus/backend/controller/notifications.py +0 -13
- argus/backend/controller/team.py +0 -126
- argus/backend/controller/team_ui.py +0 -18
- argus/backend/controller/testrun_api.py +0 -482
- argus/backend/controller/view_api.py +0 -162
- argus/backend/db.py +0 -100
- argus/backend/error_handlers.py +0 -21
- argus/backend/events/event_processors.py +0 -34
- argus/backend/models/__init__.py +0 -0
- argus/backend/models/result.py +0 -138
- argus/backend/models/web.py +0 -389
- argus/backend/plugins/__init__.py +0 -0
- argus/backend/plugins/core.py +0 -225
- argus/backend/plugins/driver_matrix_tests/controller.py +0 -63
- argus/backend/plugins/driver_matrix_tests/model.py +0 -421
- argus/backend/plugins/driver_matrix_tests/plugin.py +0 -22
- argus/backend/plugins/driver_matrix_tests/raw_types.py +0 -62
- argus/backend/plugins/driver_matrix_tests/service.py +0 -60
- argus/backend/plugins/driver_matrix_tests/udt.py +0 -42
- argus/backend/plugins/generic/model.py +0 -79
- argus/backend/plugins/generic/plugin.py +0 -16
- argus/backend/plugins/generic/types.py +0 -13
- argus/backend/plugins/loader.py +0 -40
- argus/backend/plugins/sct/controller.py +0 -185
- argus/backend/plugins/sct/plugin.py +0 -38
- argus/backend/plugins/sct/resource_setup.py +0 -178
- argus/backend/plugins/sct/service.py +0 -491
- argus/backend/plugins/sct/testrun.py +0 -272
- argus/backend/plugins/sct/udt.py +0 -101
- argus/backend/plugins/sirenada/model.py +0 -113
- argus/backend/plugins/sirenada/plugin.py +0 -17
- argus/backend/service/admin.py +0 -27
- argus/backend/service/argus_service.py +0 -688
- argus/backend/service/build_system_monitor.py +0 -188
- argus/backend/service/client_service.py +0 -122
- argus/backend/service/event_service.py +0 -18
- argus/backend/service/jenkins_service.py +0 -240
- argus/backend/service/notification_manager.py +0 -150
- argus/backend/service/release_manager.py +0 -230
- argus/backend/service/results_service.py +0 -317
- argus/backend/service/stats.py +0 -540
- argus/backend/service/team_manager_service.py +0 -83
- argus/backend/service/testrun.py +0 -559
- argus/backend/service/user.py +0 -307
- argus/backend/service/views.py +0 -258
- argus/backend/template_filters.py +0 -27
- argus/backend/tests/__init__.py +0 -0
- argus/backend/tests/argus_web.test.yaml +0 -39
- argus/backend/tests/conftest.py +0 -44
- argus/backend/tests/results_service/__init__.py +0 -0
- argus/backend/tests/results_service/test_best_results.py +0 -70
- argus/backend/util/common.py +0 -65
- argus/backend/util/config.py +0 -38
- argus/backend/util/encoders.py +0 -41
- argus/backend/util/logsetup.py +0 -81
- argus/backend/util/module_loaders.py +0 -30
- argus/backend/util/send_email.py +0 -91
- argus/client/generic_result_old.py +0 -143
- argus/db/.gitkeep +0 -0
- argus/db/argus_json.py +0 -14
- argus/db/cloud_types.py +0 -125
- argus/db/config.py +0 -135
- argus/db/db_types.py +0 -139
- argus/db/interface.py +0 -370
- argus/db/testrun.py +0 -740
- argus/db/utils.py +0 -15
- argus_alm-0.12.9.dist-info/RECORD +0 -96
- /argus/{backend → common}/__init__.py +0 -0
- /argus/{backend/util → common}/enums.py +0 -0
- /argus/{backend/plugins/sct/types.py → common/sct_types.py} +0 -0
- /argus/{backend/plugins/sirenada/types.py → common/sirenada_types.py} +0 -0
- {argus_alm-0.12.9.dist-info → argus_alm-0.13.0.dist-info}/LICENSE +0 -0
- {argus_alm-0.12.9.dist-info → argus_alm-0.13.0.dist-info}/WHEEL +0 -0
- {argus_alm-0.12.9.dist-info → argus_alm-0.13.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,317 +0,0 @@
|
|
|
1
|
-
import copy
|
|
2
|
-
import logging
|
|
3
|
-
import math
|
|
4
|
-
import operator
|
|
5
|
-
from datetime import datetime, timezone
|
|
6
|
-
from functools import partial
|
|
7
|
-
from typing import List, Dict, Any
|
|
8
|
-
from uuid import UUID
|
|
9
|
-
|
|
10
|
-
from dataclasses import dataclass
|
|
11
|
-
from argus.backend.db import ScyllaCluster
|
|
12
|
-
from argus.backend.models.result import ArgusGenericResultMetadata, ArgusGenericResultData, ArgusBestResultData
|
|
13
|
-
|
|
14
|
-
LOGGER = logging.getLogger(__name__)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
@dataclass
|
|
18
|
-
class BestResult:
|
|
19
|
-
key: str
|
|
20
|
-
value: float
|
|
21
|
-
result_date: datetime
|
|
22
|
-
run_id: str
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
@dataclass
|
|
26
|
-
class Cell:
|
|
27
|
-
column: str
|
|
28
|
-
row: str
|
|
29
|
-
status: str
|
|
30
|
-
value: Any | None = None
|
|
31
|
-
value_text: str | None = None
|
|
32
|
-
|
|
33
|
-
def update_cell_status_based_on_rules(self, table_metadata: ArgusGenericResultMetadata, best_results: dict[str, BestResult],
|
|
34
|
-
) -> None:
|
|
35
|
-
column_validation_rules = table_metadata.validation_rules.get(self.column)
|
|
36
|
-
rules = column_validation_rules[-1] if column_validation_rules else {}
|
|
37
|
-
higher_is_better = next((col.higher_is_better for col in table_metadata.columns_meta if col.name == self.column), None)
|
|
38
|
-
if not rules or self.status != "UNSET" or higher_is_better is None:
|
|
39
|
-
return
|
|
40
|
-
is_better = partial(operator.gt, self.value) if higher_is_better else partial(operator.lt, self.value)
|
|
41
|
-
key = f"{self.column}:{self.row}"
|
|
42
|
-
limits = []
|
|
43
|
-
if rules.fixed_limit is not None:
|
|
44
|
-
limits.append(rules.fixed_limit)
|
|
45
|
-
|
|
46
|
-
if best_result := best_results.get(key):
|
|
47
|
-
best_value = best_result.value
|
|
48
|
-
if (best_pct := rules.best_pct) is not None:
|
|
49
|
-
multiplier = 1 - best_pct / 100 if higher_is_better else 1 + best_pct / 100
|
|
50
|
-
limits.append(best_value * multiplier)
|
|
51
|
-
if (best_abs := rules.best_abs) is not None:
|
|
52
|
-
limits.append(best_value - best_abs if higher_is_better else best_value + best_abs)
|
|
53
|
-
if all(is_better(limit) for limit in limits):
|
|
54
|
-
self.status = "PASS"
|
|
55
|
-
else:
|
|
56
|
-
self.status = "ERROR"
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
default_options = {
|
|
60
|
-
"scales": {
|
|
61
|
-
"y": {
|
|
62
|
-
"beginAtZero": True,
|
|
63
|
-
"title": {
|
|
64
|
-
"display": True,
|
|
65
|
-
"text": ''
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
"x": {
|
|
69
|
-
"type": "time",
|
|
70
|
-
"time": {
|
|
71
|
-
"unit": "day",
|
|
72
|
-
"displayFormats": {
|
|
73
|
-
"day": "yyyy-MM-dd",
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
"title": {
|
|
77
|
-
"display": True,
|
|
78
|
-
"text": 'SUT Date'
|
|
79
|
-
}
|
|
80
|
-
},
|
|
81
|
-
},
|
|
82
|
-
"elements": {
|
|
83
|
-
"line": {
|
|
84
|
-
"tension": .1,
|
|
85
|
-
}
|
|
86
|
-
},
|
|
87
|
-
"plugins": {
|
|
88
|
-
"legend": {
|
|
89
|
-
"position": 'top',
|
|
90
|
-
},
|
|
91
|
-
"title": {
|
|
92
|
-
"display": True,
|
|
93
|
-
"text": ''
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
colors = [
|
|
99
|
-
'rgba(255, 0, 0, 1.0)', # Red
|
|
100
|
-
'rgba(0, 255, 0, 1.0)', # Green
|
|
101
|
-
'rgba(0, 0, 255, 1.0)', # Blue
|
|
102
|
-
'rgba(0, 255, 255, 1.0)', # Cyan
|
|
103
|
-
'rgba(255, 0, 255, 1.0)', # Magenta
|
|
104
|
-
'rgba(255, 255, 0, 1.0)', # Yellow
|
|
105
|
-
'rgba(255, 165, 0, 1.0)', # Orange
|
|
106
|
-
'rgba(128, 0, 128, 1.0)', # Purple
|
|
107
|
-
'rgba(50, 205, 50, 1.0)', # Lime
|
|
108
|
-
'rgba(255, 192, 203, 1.0)', # Pink
|
|
109
|
-
'rgba(0, 128, 128, 1.0)', # Teal
|
|
110
|
-
'rgba(165, 42, 42, 1.0)', # Brown
|
|
111
|
-
'rgba(0, 0, 128, 1.0)', # Navy
|
|
112
|
-
'rgba(128, 128, 0, 1.0)', # Olive
|
|
113
|
-
'rgba(255, 127, 80, 1.0)' # Coral
|
|
114
|
-
]
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
def get_sorted_data_for_column_and_row(data: List[ArgusGenericResultData], column: str, row: str) -> List[Dict[str, Any]]:
|
|
118
|
-
return sorted([{"x": entry.sut_timestamp.strftime('%Y-%m-%dT%H:%M:%SZ'),
|
|
119
|
-
"y": entry.value,
|
|
120
|
-
"id": entry.run_id}
|
|
121
|
-
for entry in data if entry.column == column and entry.row == row],
|
|
122
|
-
key=lambda point: point["x"])
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
def get_min_max_y(datasets: List[Dict[str, Any]]) -> (float, float):
|
|
126
|
-
"""0.5 - 1.5 of min/max of 50% results"""
|
|
127
|
-
y = [entry['y'] for dataset in datasets for entry in dataset['data']]
|
|
128
|
-
if not y:
|
|
129
|
-
return 0, 0
|
|
130
|
-
sorted_y = sorted(y)
|
|
131
|
-
lower_percentile_index = int(0.25 * len(sorted_y))
|
|
132
|
-
upper_percentile_index = int(0.75 * len(sorted_y)) - 1
|
|
133
|
-
y_min = sorted_y[lower_percentile_index]
|
|
134
|
-
y_max = sorted_y[upper_percentile_index]
|
|
135
|
-
return math.floor(0.5 * y_min), math.ceil(1.5 * y_max)
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
def round_datasets_to_min_max(datasets: List[Dict[str, Any]], min_y: float, max_y: float) -> List[Dict[str, Any]]:
|
|
139
|
-
"""Round values to min/max and provide original value for tooltip"""
|
|
140
|
-
for dataset in datasets:
|
|
141
|
-
for entry in dataset['data']:
|
|
142
|
-
val = entry['y']
|
|
143
|
-
if val > max_y:
|
|
144
|
-
entry['y'] = max_y
|
|
145
|
-
entry['ori'] = val
|
|
146
|
-
elif val < min_y:
|
|
147
|
-
entry['y'] = min_y
|
|
148
|
-
entry['ori'] = val
|
|
149
|
-
return datasets
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
def create_chartjs(table, data):
|
|
153
|
-
graphs = []
|
|
154
|
-
for column in table.columns_meta:
|
|
155
|
-
if column.type == "TEXT":
|
|
156
|
-
# skip text columns
|
|
157
|
-
continue
|
|
158
|
-
datasets = [
|
|
159
|
-
{"label": row,
|
|
160
|
-
"borderColor": colors[idx % len(colors)],
|
|
161
|
-
"borderWidth": 3,
|
|
162
|
-
"showLine": True,
|
|
163
|
-
"data": get_sorted_data_for_column_and_row(data, column.name, row)} for idx, row in enumerate(table.rows_meta)]
|
|
164
|
-
min_y, max_y = get_min_max_y(datasets)
|
|
165
|
-
datasets = round_datasets_to_min_max(datasets, min_y, max_y)
|
|
166
|
-
if not min_y + max_y:
|
|
167
|
-
# filter out those without data
|
|
168
|
-
continue
|
|
169
|
-
options = copy.deepcopy(default_options)
|
|
170
|
-
options["plugins"]["title"]["text"] = f"{table.name} - {column.name}"
|
|
171
|
-
options["scales"]["y"]["title"]["text"] = f"[{column.unit}]" if column.unit else ""
|
|
172
|
-
options["scales"]["y"]["min"] = min_y
|
|
173
|
-
options["scales"]["y"]["max"] = max_y
|
|
174
|
-
graphs.append({"options": options, "data":
|
|
175
|
-
{"datasets": datasets}})
|
|
176
|
-
return graphs
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
def calculate_graph_ticks(graphs: List[Dict]) -> dict[str, str]:
|
|
180
|
-
min_x, max_x = None, None
|
|
181
|
-
|
|
182
|
-
for graph in graphs:
|
|
183
|
-
for dataset in graph["data"]["datasets"]:
|
|
184
|
-
if not dataset["data"]:
|
|
185
|
-
continue
|
|
186
|
-
first_x = dataset["data"][0]["x"]
|
|
187
|
-
last_x = dataset["data"][-1]["x"]
|
|
188
|
-
if min_x is None or first_x < min_x:
|
|
189
|
-
min_x = first_x
|
|
190
|
-
if max_x is None or last_x > max_x:
|
|
191
|
-
max_x = last_x
|
|
192
|
-
return {"min": min_x[:10], "max": max_x[:10]}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
class ResultsService:
|
|
196
|
-
|
|
197
|
-
def __init__(self):
|
|
198
|
-
self.cluster = ScyllaCluster.get()
|
|
199
|
-
|
|
200
|
-
def _get_tables_metadata(self, test_id: UUID) -> list[ArgusGenericResultMetadata]:
|
|
201
|
-
query_fields = ["name", "description", "columns_meta", "rows_meta"]
|
|
202
|
-
raw_query = (f"SELECT {','.join(query_fields)}"
|
|
203
|
-
f" FROM generic_result_metadata_v1 WHERE test_id = ?")
|
|
204
|
-
query = self.cluster.prepare(raw_query)
|
|
205
|
-
tables_meta = self.cluster.session.execute(query=query, parameters=(test_id,))
|
|
206
|
-
return [ArgusGenericResultMetadata(**table) for table in tables_meta]
|
|
207
|
-
|
|
208
|
-
def get_table_metadata(self, test_id: UUID, table_name: str) -> ArgusGenericResultMetadata:
|
|
209
|
-
raw_query = ("SELECT * FROM generic_result_metadata_v1 WHERE test_id = ? AND name = ?")
|
|
210
|
-
query = self.cluster.prepare(raw_query)
|
|
211
|
-
table_meta = self.cluster.session.execute(query=query, parameters=(test_id, table_name))
|
|
212
|
-
return [ArgusGenericResultMetadata(**table) for table in table_meta][0] if table_meta else None
|
|
213
|
-
|
|
214
|
-
def get_run_results(self, test_id: UUID, run_id: UUID) -> list[dict]:
|
|
215
|
-
query_fields = ["column", "row", "value", "value_text", "status"]
|
|
216
|
-
raw_query = (f"SELECT {','.join(query_fields)},WRITETIME(status) as ordering"
|
|
217
|
-
f" FROM generic_result_data_v1 WHERE test_id = ? AND run_id = ? AND name = ?")
|
|
218
|
-
query = self.cluster.prepare(raw_query)
|
|
219
|
-
tables_meta = self._get_tables_metadata(test_id=test_id)
|
|
220
|
-
tables = []
|
|
221
|
-
for table in tables_meta:
|
|
222
|
-
cells = self.cluster.session.execute(query=query, parameters=(test_id, run_id, table.name))
|
|
223
|
-
if not cells:
|
|
224
|
-
continue
|
|
225
|
-
cells = [dict(cell.items()) for cell in cells]
|
|
226
|
-
tables.append({'meta': {
|
|
227
|
-
'name': table.name,
|
|
228
|
-
'description': table.description,
|
|
229
|
-
'columns_meta': table.columns_meta,
|
|
230
|
-
'rows_meta': table.rows_meta,
|
|
231
|
-
},
|
|
232
|
-
'cells': [{k: v for k, v in cell.items() if k in query_fields} for cell in cells],
|
|
233
|
-
'order': min([cell['ordering'] for cell in cells] or [0])})
|
|
234
|
-
return sorted(tables, key=lambda x: x['order'])
|
|
235
|
-
|
|
236
|
-
def get_test_graphs(self, test_id: UUID):
|
|
237
|
-
query_fields = ["run_id", "column", "row", "value", "status", "sut_timestamp"]
|
|
238
|
-
raw_query = (f"SELECT {','.join(query_fields)}"
|
|
239
|
-
f" FROM generic_result_data_v1 WHERE test_id = ? AND name = ? LIMIT 2147483647")
|
|
240
|
-
query = self.cluster.prepare(raw_query)
|
|
241
|
-
tables_meta = self._get_tables_metadata(test_id=test_id)
|
|
242
|
-
graphs = []
|
|
243
|
-
for table in tables_meta:
|
|
244
|
-
data = self.cluster.session.execute(query=query, parameters=(test_id, table.name))
|
|
245
|
-
data = [ArgusGenericResultData(**cell) for cell in data]
|
|
246
|
-
if not data:
|
|
247
|
-
continue
|
|
248
|
-
graphs.extend(create_chartjs(table, data))
|
|
249
|
-
ticks = calculate_graph_ticks(graphs)
|
|
250
|
-
return graphs, ticks
|
|
251
|
-
|
|
252
|
-
def is_results_exist(self, test_id: UUID):
|
|
253
|
-
"""Verify if results for given test id exist at all."""
|
|
254
|
-
return bool(ArgusGenericResultMetadata.objects(test_id=test_id).only(["name"]).limit(1))
|
|
255
|
-
|
|
256
|
-
def get_best_results(self, test_id: UUID, name: str) -> List[BestResult]:
|
|
257
|
-
query_fields = ["key", "value", "result_date", "run_id"]
|
|
258
|
-
raw_query = (f"SELECT {','.join(query_fields)}"
|
|
259
|
-
f" FROM generic_result_best_v1 WHERE test_id = ? and name = ?")
|
|
260
|
-
query = self.cluster.prepare(raw_query)
|
|
261
|
-
best_results = self.cluster.session.execute(query=query, parameters=(test_id, name))
|
|
262
|
-
return [BestResult(**best) for best in best_results]
|
|
263
|
-
|
|
264
|
-
@staticmethod
|
|
265
|
-
def _update_best_value(best_results: dict[str, list[dict]], higher_is_better_map: dict[str, bool | None], cells: list[dict],
|
|
266
|
-
sut_timestamp: float, run_id: str
|
|
267
|
-
) -> dict[str, list[dict]]:
|
|
268
|
-
|
|
269
|
-
for cell in cells:
|
|
270
|
-
if "column" not in cell or "row" not in cell or "value" not in cell:
|
|
271
|
-
continue
|
|
272
|
-
column, row, value = cell["column"], cell["row"], cell["value"]
|
|
273
|
-
key_name = f"{column}_{row}"
|
|
274
|
-
if higher_is_better_map[column] is None:
|
|
275
|
-
# skipping updating best value when higher_is_better is not set (not enabled by user)
|
|
276
|
-
return best_results
|
|
277
|
-
if key_name not in best_results:
|
|
278
|
-
best_results[key_name] = []
|
|
279
|
-
current_best = None
|
|
280
|
-
else:
|
|
281
|
-
current_best = best_results[key_name][-1]
|
|
282
|
-
if current_best["sut_timestamp"].timestamp() > sut_timestamp:
|
|
283
|
-
# skip updating best value when testing older version than current best
|
|
284
|
-
# as would have to update all values between these dates to make cells statuses to be consistent
|
|
285
|
-
return best_results
|
|
286
|
-
|
|
287
|
-
is_better = partial(operator.gt, value) if higher_is_better_map[column] else partial(operator.lt, value)
|
|
288
|
-
if current_best is None or is_better(current_best["value"]):
|
|
289
|
-
best_results[key_name].append({"sut_timestamp": sut_timestamp, "value": value, "run_id": run_id})
|
|
290
|
-
return best_results
|
|
291
|
-
|
|
292
|
-
def update_best_results(self, test_id: UUID, table_name: str, cells: list[Cell],
|
|
293
|
-
table_metadata: ArgusGenericResultMetadata, run_id: str) -> dict[str, BestResult]:
|
|
294
|
-
"""update best results for given test_id and table_name based on cells values - if any value is better than current best"""
|
|
295
|
-
higher_is_better_map = {meta["name"]: meta.higher_is_better for meta in table_metadata.columns_meta}
|
|
296
|
-
best_results = {}
|
|
297
|
-
for best in self.get_best_results(test_id=test_id, name=table_name):
|
|
298
|
-
if best.key not in best_results:
|
|
299
|
-
best_results[best.key] = best
|
|
300
|
-
|
|
301
|
-
for cell in cells:
|
|
302
|
-
if cell.value is None:
|
|
303
|
-
# textual value, skip
|
|
304
|
-
continue
|
|
305
|
-
key = f"{cell.column}:{cell.row}"
|
|
306
|
-
if higher_is_better_map[cell.column] is None:
|
|
307
|
-
# skipping updating best value when higher_is_better is not set (not enabled by user)
|
|
308
|
-
continue
|
|
309
|
-
current_best = best_results.get(key)
|
|
310
|
-
is_better = partial(operator.gt, cell.value) if higher_is_better_map[cell.column] \
|
|
311
|
-
else partial(operator.lt, cell.value)
|
|
312
|
-
if current_best is None or is_better(current_best.value):
|
|
313
|
-
result_date = datetime.now(timezone.utc)
|
|
314
|
-
best_results[key] = BestResult(key=key, value=cell.value, result_date=result_date, run_id=run_id)
|
|
315
|
-
ArgusBestResultData(test_id=test_id, name=table_name, key=key, value=cell.value, result_date=result_date,
|
|
316
|
-
run_id=run_id).save()
|
|
317
|
-
return best_results
|