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