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