argus-alm 0.12.10__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.
Files changed (92) hide show
  1. argus/client/base.py +1 -1
  2. argus/client/driver_matrix_tests/cli.py +2 -2
  3. argus/client/driver_matrix_tests/client.py +1 -1
  4. argus/client/generic/cli.py +2 -2
  5. argus/client/sct/client.py +3 -3
  6. argus/client/sirenada/client.py +1 -1
  7. {argus_alm-0.12.10.dist-info → argus_alm-0.13.0.dist-info}/METADATA +2 -4
  8. argus_alm-0.13.0.dist-info/RECORD +20 -0
  9. argus/backend/.gitkeep +0 -0
  10. argus/backend/cli.py +0 -41
  11. argus/backend/controller/__init__.py +0 -0
  12. argus/backend/controller/admin.py +0 -20
  13. argus/backend/controller/admin_api.py +0 -354
  14. argus/backend/controller/api.py +0 -529
  15. argus/backend/controller/auth.py +0 -67
  16. argus/backend/controller/client_api.py +0 -108
  17. argus/backend/controller/main.py +0 -274
  18. argus/backend/controller/notification_api.py +0 -72
  19. argus/backend/controller/notifications.py +0 -13
  20. argus/backend/controller/team.py +0 -126
  21. argus/backend/controller/team_ui.py +0 -18
  22. argus/backend/controller/testrun_api.py +0 -482
  23. argus/backend/controller/view_api.py +0 -162
  24. argus/backend/db.py +0 -100
  25. argus/backend/error_handlers.py +0 -21
  26. argus/backend/events/event_processors.py +0 -34
  27. argus/backend/models/__init__.py +0 -0
  28. argus/backend/models/result.py +0 -138
  29. argus/backend/models/web.py +0 -389
  30. argus/backend/plugins/__init__.py +0 -0
  31. argus/backend/plugins/core.py +0 -225
  32. argus/backend/plugins/driver_matrix_tests/controller.py +0 -63
  33. argus/backend/plugins/driver_matrix_tests/model.py +0 -421
  34. argus/backend/plugins/driver_matrix_tests/plugin.py +0 -22
  35. argus/backend/plugins/driver_matrix_tests/raw_types.py +0 -62
  36. argus/backend/plugins/driver_matrix_tests/service.py +0 -60
  37. argus/backend/plugins/driver_matrix_tests/udt.py +0 -42
  38. argus/backend/plugins/generic/model.py +0 -79
  39. argus/backend/plugins/generic/plugin.py +0 -16
  40. argus/backend/plugins/generic/types.py +0 -13
  41. argus/backend/plugins/loader.py +0 -40
  42. argus/backend/plugins/sct/controller.py +0 -185
  43. argus/backend/plugins/sct/plugin.py +0 -38
  44. argus/backend/plugins/sct/resource_setup.py +0 -178
  45. argus/backend/plugins/sct/service.py +0 -491
  46. argus/backend/plugins/sct/testrun.py +0 -272
  47. argus/backend/plugins/sct/udt.py +0 -101
  48. argus/backend/plugins/sirenada/model.py +0 -113
  49. argus/backend/plugins/sirenada/plugin.py +0 -17
  50. argus/backend/service/admin.py +0 -27
  51. argus/backend/service/argus_service.py +0 -688
  52. argus/backend/service/build_system_monitor.py +0 -188
  53. argus/backend/service/client_service.py +0 -122
  54. argus/backend/service/event_service.py +0 -18
  55. argus/backend/service/jenkins_service.py +0 -240
  56. argus/backend/service/notification_manager.py +0 -150
  57. argus/backend/service/release_manager.py +0 -230
  58. argus/backend/service/results_service.py +0 -317
  59. argus/backend/service/stats.py +0 -540
  60. argus/backend/service/team_manager_service.py +0 -83
  61. argus/backend/service/testrun.py +0 -559
  62. argus/backend/service/user.py +0 -307
  63. argus/backend/service/views.py +0 -258
  64. argus/backend/template_filters.py +0 -27
  65. argus/backend/tests/__init__.py +0 -0
  66. argus/backend/tests/argus_web.test.yaml +0 -39
  67. argus/backend/tests/conftest.py +0 -44
  68. argus/backend/tests/results_service/__init__.py +0 -0
  69. argus/backend/tests/results_service/test_best_results.py +0 -70
  70. argus/backend/util/common.py +0 -65
  71. argus/backend/util/config.py +0 -38
  72. argus/backend/util/encoders.py +0 -41
  73. argus/backend/util/logsetup.py +0 -81
  74. argus/backend/util/module_loaders.py +0 -30
  75. argus/backend/util/send_email.py +0 -91
  76. argus/client/generic_result_old.py +0 -143
  77. argus/db/.gitkeep +0 -0
  78. argus/db/argus_json.py +0 -14
  79. argus/db/cloud_types.py +0 -125
  80. argus/db/config.py +0 -135
  81. argus/db/db_types.py +0 -139
  82. argus/db/interface.py +0 -370
  83. argus/db/testrun.py +0 -740
  84. argus/db/utils.py +0 -15
  85. argus_alm-0.12.10.dist-info/RECORD +0 -96
  86. /argus/{backend → common}/__init__.py +0 -0
  87. /argus/{backend/util → common}/enums.py +0 -0
  88. /argus/{backend/plugins/sct/types.py → common/sct_types.py} +0 -0
  89. /argus/{backend/plugins/sirenada/types.py → common/sirenada_types.py} +0 -0
  90. {argus_alm-0.12.10.dist-info → argus_alm-0.13.0.dist-info}/LICENSE +0 -0
  91. {argus_alm-0.12.10.dist-info → argus_alm-0.13.0.dist-info}/WHEEL +0 -0
  92. {argus_alm-0.12.10.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