argus-alm 0.12.7__tar.gz → 0.12.8__tar.gz
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_alm-0.12.7 → argus_alm-0.12.8}/PKG-INFO +1 -1
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/controller/api.py +7 -4
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/controller/client_api.py +1 -5
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/controller/testrun_api.py +2 -1
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/models/result.py +18 -6
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/plugins/sct/testrun.py +7 -3
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/service/client_service.py +9 -3
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/service/results_service.py +73 -12
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/service/testrun.py +0 -22
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/client/generic_result.py +10 -5
- argus_alm-0.12.8/argus/client/generic_result_old.py +143 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/pyproject.toml +2 -2
- {argus_alm-0.12.7 → argus_alm-0.12.8}/LICENSE +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/README.md +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/__init__.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/.gitkeep +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/__init__.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/cli.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/controller/__init__.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/controller/admin.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/controller/admin_api.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/controller/auth.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/controller/main.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/controller/notification_api.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/controller/notifications.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/controller/team.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/controller/team_ui.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/controller/view_api.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/db.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/error_handlers.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/events/event_processors.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/models/__init__.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/models/web.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/plugins/__init__.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/plugins/core.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/plugins/driver_matrix_tests/controller.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/plugins/driver_matrix_tests/model.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/plugins/driver_matrix_tests/plugin.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/plugins/driver_matrix_tests/raw_types.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/plugins/driver_matrix_tests/service.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/plugins/driver_matrix_tests/udt.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/plugins/generic/model.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/plugins/generic/plugin.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/plugins/generic/types.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/plugins/loader.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/plugins/sct/controller.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/plugins/sct/plugin.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/plugins/sct/resource_setup.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/plugins/sct/service.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/plugins/sct/types.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/plugins/sct/udt.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/plugins/sirenada/model.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/plugins/sirenada/plugin.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/plugins/sirenada/types.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/service/admin.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/service/argus_service.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/service/build_system_monitor.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/service/event_service.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/service/jenkins_service.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/service/notification_manager.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/service/release_manager.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/service/stats.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/service/team_manager_service.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/service/user.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/service/views.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/template_filters.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/util/common.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/util/config.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/util/encoders.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/util/enums.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/util/logsetup.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/util/module_loaders.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/util/send_email.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/client/__init__.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/client/base.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/client/driver_matrix_tests/cli.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/client/driver_matrix_tests/client.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/client/generic/cli.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/client/generic/client.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/client/sct/client.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/client/sct/types.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/client/sirenada/client.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/db/.gitkeep +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/db/argus_json.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/db/cloud_types.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/db/config.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/db/db_types.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/db/interface.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/db/testrun.py +0 -0
- {argus_alm-0.12.7 → argus_alm-0.12.8}/argus/db/utils.py +0 -0
|
@@ -4,7 +4,7 @@ import requests
|
|
|
4
4
|
from flask import (
|
|
5
5
|
Blueprint,
|
|
6
6
|
g,
|
|
7
|
-
request
|
|
7
|
+
request, Response
|
|
8
8
|
)
|
|
9
9
|
from flask.json import jsonify
|
|
10
10
|
from argus.backend.error_handlers import handle_api_exception
|
|
@@ -382,18 +382,21 @@ def test_info():
|
|
|
382
382
|
"response": info
|
|
383
383
|
}
|
|
384
384
|
|
|
385
|
-
@bp.route("/test-results", methods=["GET"])
|
|
385
|
+
@bp.route("/test-results", methods=["GET", "HEAD"])
|
|
386
386
|
@api_login_required
|
|
387
387
|
def test_results():
|
|
388
388
|
test_id = request.args.get("testId")
|
|
389
389
|
if not test_id:
|
|
390
390
|
raise Exception("No testId provided")
|
|
391
391
|
service = ResultsService()
|
|
392
|
-
|
|
392
|
+
if request.method == 'HEAD':
|
|
393
|
+
exists = service.is_results_exist(test_id=UUID(test_id))
|
|
394
|
+
return Response(status=200 if exists else 404)
|
|
395
|
+
graphs, ticks = service.get_test_graphs(test_id=UUID(test_id))
|
|
393
396
|
|
|
394
397
|
return {
|
|
395
398
|
"status": "ok",
|
|
396
|
-
"response":
|
|
399
|
+
"response": {"graphs": graphs, "ticks": ticks}
|
|
397
400
|
}
|
|
398
401
|
|
|
399
402
|
@bp.route("/test_run/comment/get", methods=["GET"]) # TODO: remove
|
|
@@ -105,8 +105,4 @@ def run_finalize(run_type: str, run_id: str):
|
|
|
105
105
|
@api_login_required
|
|
106
106
|
def submit_results(run_type: str, run_id: str):
|
|
107
107
|
payload = get_payload(request)
|
|
108
|
-
|
|
109
|
-
return {
|
|
110
|
-
"status": "ok",
|
|
111
|
-
"response": result
|
|
112
|
-
}
|
|
108
|
+
return ClientService().submit_results(run_type=run_type, run_id=run_id, results=payload)
|
|
@@ -8,6 +8,7 @@ from flask import (
|
|
|
8
8
|
from argus.backend.error_handlers import handle_api_exception
|
|
9
9
|
from argus.backend.models.web import ArgusTest
|
|
10
10
|
from argus.backend.service.jenkins_service import JenkinsService
|
|
11
|
+
from argus.backend.service.results_service import ResultsService
|
|
11
12
|
from argus.backend.service.testrun import TestRunService
|
|
12
13
|
from argus.backend.service.user import api_login_required
|
|
13
14
|
from argus.backend.util.common import get_payload
|
|
@@ -67,7 +68,7 @@ def test_run_activity(run_id: str):
|
|
|
67
68
|
@bp.route("/run/<string:test_id>/<string:run_id>/fetch_results", methods=["GET"])
|
|
68
69
|
@api_login_required
|
|
69
70
|
def fetch_results(test_id: str, run_id: str):
|
|
70
|
-
tables =
|
|
71
|
+
tables = ResultsService().get_run_results(test_id=UUID(test_id), run_id=UUID(run_id))
|
|
71
72
|
return {
|
|
72
73
|
"status": "ok",
|
|
73
74
|
"tables": tables
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
from cassandra.cqlengine import columns
|
|
2
2
|
from cassandra.cqlengine.models import Model
|
|
3
3
|
from cassandra.cqlengine.usertype import UserType
|
|
4
|
-
from enum import Enum
|
|
5
4
|
|
|
5
|
+
class BestResult(UserType):
|
|
6
|
+
date = columns.DateTime()
|
|
7
|
+
value = columns.Double()
|
|
8
|
+
run_id = columns.UUID()
|
|
6
9
|
|
|
7
|
-
class
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
class ValidationRules(UserType):
|
|
11
|
+
higher_is_better = columns.Boolean()
|
|
12
|
+
margin_percent = columns.Double()
|
|
13
|
+
margin_value = columns.Double()
|
|
14
|
+
limit = columns.Double()
|
|
12
15
|
|
|
13
16
|
class ColumnMetadata(UserType):
|
|
14
17
|
name = columns.Ascii()
|
|
@@ -22,10 +25,14 @@ class ArgusGenericResultMetadata(Model):
|
|
|
22
25
|
name = columns.Text(required=True, primary_key=True)
|
|
23
26
|
description = columns.Text()
|
|
24
27
|
columns_meta = columns.List(value_type=columns.UserDefinedType(ColumnMetadata))
|
|
28
|
+
validation_rules = columns.Map(key_type=columns.Ascii(), value_type=columns.List(value_type=columns.UserDefinedType(ValidationRules)))
|
|
29
|
+
best_results = columns.Map(key_type=columns.Ascii(), value_type=columns.UserDefinedType(BestResult))
|
|
25
30
|
rows_meta = columns.List(value_type=columns.Ascii())
|
|
26
31
|
|
|
27
32
|
def __init__(self, **kwargs):
|
|
28
33
|
kwargs["columns_meta"] = [ColumnMetadata(**col) for col in kwargs.pop('columns_meta', [])]
|
|
34
|
+
kwargs["best_results"] = {k: [BestResult(**z) for z in v] for k, v in kwargs.pop('best_results', {}).items()}
|
|
35
|
+
kwargs["validation_rules"] = {k: ValidationRules(**v) for k, v in kwargs.pop('validation_rules', {}).items()}
|
|
29
36
|
super().__init__(**kwargs)
|
|
30
37
|
|
|
31
38
|
def update_if_changed(self, new_data: dict) -> None:
|
|
@@ -44,6 +51,10 @@ class ArgusGenericResultMetadata(Model):
|
|
|
44
51
|
if row not in self.rows_meta:
|
|
45
52
|
added_rows.append(row)
|
|
46
53
|
value = self.rows_meta + added_rows
|
|
54
|
+
elif field == "best_results":
|
|
55
|
+
value = {k: [BestResult(**z) for z in v] for k, v in value.items()}
|
|
56
|
+
elif field == "validation_rules":
|
|
57
|
+
value = {k: ValidationRules(**v) for k, v in value.items()}
|
|
47
58
|
if getattr(self, field) != value:
|
|
48
59
|
setattr(self, field, value)
|
|
49
60
|
updated = True
|
|
@@ -60,4 +71,5 @@ class ArgusGenericResultData(Model):
|
|
|
60
71
|
row = columns.Ascii(primary_key=True, index=True)
|
|
61
72
|
sut_timestamp = columns.DateTime() # for sorting
|
|
62
73
|
value = columns.Double()
|
|
74
|
+
value_text = columns.Text()
|
|
63
75
|
status = columns.Ascii()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from enum import Enum
|
|
2
2
|
import logging
|
|
3
|
-
from datetime import datetime
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
4
|
from dataclasses import dataclass, field
|
|
5
5
|
from typing import Optional
|
|
6
6
|
from uuid import UUID
|
|
@@ -254,8 +254,12 @@ class SCTTestRun(PluginModelBase):
|
|
|
254
254
|
def sut_timestamp(self) -> float:
|
|
255
255
|
"""converts scylla-server date to timestamp and adds revision in subseconds precision to diffirentiate
|
|
256
256
|
scylla versions from the same day. It's not perfect, but we don't know exact version time."""
|
|
257
|
-
|
|
258
|
-
|
|
257
|
+
try:
|
|
258
|
+
scylla_package_upgraded = [package for package in self.packages if package.name == "scylla-server-upgraded"][0]
|
|
259
|
+
except IndexError:
|
|
260
|
+
scylla_package_upgraded = None
|
|
261
|
+
scylla_package = scylla_package_upgraded or [package for package in self.packages if package.name == "scylla-server"][0]
|
|
262
|
+
return (datetime.strptime(scylla_package.date, '%Y%m%d').replace(tzinfo=timezone.utc).timestamp()
|
|
259
263
|
+ int(scylla_package.revision_id, 16) % 1000000 / 1000000)
|
|
260
264
|
|
|
261
265
|
|
|
@@ -79,9 +79,15 @@ class ClientService:
|
|
|
79
79
|
|
|
80
80
|
return "Finalized"
|
|
81
81
|
|
|
82
|
-
def submit_results(self, run_type: str, run_id: str, results: dict) -> str:
|
|
82
|
+
def submit_results(self, run_type: str, run_id: str, results: dict) -> dict[str, str]:
|
|
83
83
|
model = self.get_model(run_type)
|
|
84
|
-
|
|
84
|
+
try:
|
|
85
|
+
run = model.load_test_run(UUID(run_id))
|
|
86
|
+
except model.DoesNotExist:
|
|
87
|
+
return {"status": "error", "response": {
|
|
88
|
+
"exception": "DoesNotExist",
|
|
89
|
+
"arguments": [run_id]
|
|
90
|
+
}}
|
|
85
91
|
existing_table = ArgusGenericResultMetadata.objects(test_id=run.test_id, name=results["meta"]["name"]).first()
|
|
86
92
|
if existing_table:
|
|
87
93
|
existing_table.update_if_changed(results["meta"])
|
|
@@ -98,4 +104,4 @@ class ClientService:
|
|
|
98
104
|
sut_timestamp=sut_timestamp,
|
|
99
105
|
**cell
|
|
100
106
|
).save()
|
|
101
|
-
return "
|
|
107
|
+
return {"status": "ok", "message": "Results submitted"}
|
|
@@ -67,12 +67,12 @@ colors = [
|
|
|
67
67
|
]
|
|
68
68
|
|
|
69
69
|
|
|
70
|
-
def get_sorted_data_for_column_and_row(data: List[
|
|
70
|
+
def get_sorted_data_for_column_and_row(data: List[ArgusGenericResultData], column: str, row: str) -> List[Dict[str, Any]]:
|
|
71
71
|
return sorted([{"x": entry.sut_timestamp.strftime('%Y-%m-%dT%H:%M:%SZ'),
|
|
72
72
|
"y": entry.value,
|
|
73
73
|
"id": entry.run_id}
|
|
74
|
-
for entry in data if entry
|
|
75
|
-
key=lambda
|
|
74
|
+
for entry in data if entry.column == column and entry.row == row],
|
|
75
|
+
key=lambda point: point["x"])
|
|
76
76
|
|
|
77
77
|
|
|
78
78
|
def get_min_max_y(datasets: List[Dict[str, Any]]) -> (float, float):
|
|
@@ -105,6 +105,9 @@ def round_datasets_to_min_max(datasets: List[Dict[str, Any]], min_y: float, max_
|
|
|
105
105
|
def create_chartjs(table, data):
|
|
106
106
|
graphs = []
|
|
107
107
|
for column in table.columns_meta:
|
|
108
|
+
if column.type == "TEXT":
|
|
109
|
+
# skip text columns
|
|
110
|
+
continue
|
|
108
111
|
datasets = [
|
|
109
112
|
{"label": row,
|
|
110
113
|
"borderColor": colors[idx % len(colors)],
|
|
@@ -118,7 +121,7 @@ def create_chartjs(table, data):
|
|
|
118
121
|
continue
|
|
119
122
|
options = copy.deepcopy(default_options)
|
|
120
123
|
options["plugins"]["title"]["text"] = f"{table.name} - {column.name}"
|
|
121
|
-
options["scales"]["y"]["title"]["text"] = f"[{column.unit}]"
|
|
124
|
+
options["scales"]["y"]["title"]["text"] = f"[{column.unit}]" if column.unit else ""
|
|
122
125
|
options["scales"]["y"]["min"] = min_y
|
|
123
126
|
options["scales"]["y"]["max"] = max_y
|
|
124
127
|
graphs.append({"options": options, "data":
|
|
@@ -126,15 +129,73 @@ def create_chartjs(table, data):
|
|
|
126
129
|
return graphs
|
|
127
130
|
|
|
128
131
|
|
|
129
|
-
|
|
132
|
+
def calculate_graph_ticks(graphs: List[Dict]) -> dict[str, str]:
|
|
133
|
+
min_x, max_x = None, None
|
|
134
|
+
|
|
135
|
+
for graph in graphs:
|
|
136
|
+
for dataset in graph["data"]["datasets"]:
|
|
137
|
+
if not dataset["data"]:
|
|
138
|
+
continue
|
|
139
|
+
first_x = dataset["data"][0]["x"]
|
|
140
|
+
last_x = dataset["data"][-1]["x"]
|
|
141
|
+
if min_x is None or first_x < min_x:
|
|
142
|
+
min_x = first_x
|
|
143
|
+
if max_x is None or last_x > max_x:
|
|
144
|
+
max_x = last_x
|
|
145
|
+
return {"min": min_x[:10], "max": max_x[:10]}
|
|
130
146
|
|
|
131
|
-
def __init__(self, database_session=None):
|
|
132
|
-
self.session = database_session if database_session else ScyllaCluster.get_session()
|
|
133
147
|
|
|
134
|
-
|
|
148
|
+
class ResultsService:
|
|
149
|
+
|
|
150
|
+
def __init__(self):
|
|
151
|
+
self.cluster = ScyllaCluster.get()
|
|
152
|
+
|
|
153
|
+
def _get_tables_metadata(self, test_id: UUID) -> list[ArgusGenericResultMetadata]:
|
|
154
|
+
query_fields = ["name", "description", "columns_meta", "rows_meta"]
|
|
155
|
+
raw_query = (f"SELECT {','.join(query_fields)}"
|
|
156
|
+
f" FROM generic_result_metadata_v1 WHERE test_id = ?")
|
|
157
|
+
query = self.cluster.prepare(raw_query)
|
|
158
|
+
tables_meta = self.cluster.session.execute(query=query, parameters=(test_id,))
|
|
159
|
+
return [ArgusGenericResultMetadata(**table) for table in tables_meta]
|
|
160
|
+
|
|
161
|
+
def get_run_results(self, test_id: UUID, run_id: UUID) -> list[dict]:
|
|
162
|
+
query_fields = ["column", "row", "value", "value_text", "status"]
|
|
163
|
+
raw_query = (f"SELECT {','.join(query_fields)},WRITETIME(status) as ordering"
|
|
164
|
+
f" FROM generic_result_data_v1 WHERE test_id = ? AND run_id = ? AND name = ?")
|
|
165
|
+
query = self.cluster.prepare(raw_query)
|
|
166
|
+
tables_meta = self._get_tables_metadata(test_id=test_id)
|
|
167
|
+
tables = []
|
|
168
|
+
for table in tables_meta:
|
|
169
|
+
cells = self.cluster.session.execute(query=query, parameters=(test_id, run_id, table.name))
|
|
170
|
+
if not cells:
|
|
171
|
+
continue
|
|
172
|
+
cells = [dict(cell.items()) for cell in cells]
|
|
173
|
+
tables.append({'meta': {
|
|
174
|
+
'name': table.name,
|
|
175
|
+
'description': table.description,
|
|
176
|
+
'columns_meta': table.columns_meta,
|
|
177
|
+
'rows_meta': table.rows_meta,
|
|
178
|
+
},
|
|
179
|
+
'cells': [{k: v for k, v in cell.items() if k in query_fields} for cell in cells],
|
|
180
|
+
'order': min([cell['ordering'] for cell in cells] or [0])})
|
|
181
|
+
return sorted(tables, key=lambda x: x['order'])
|
|
182
|
+
|
|
183
|
+
def get_test_graphs(self, test_id: UUID):
|
|
184
|
+
query_fields = ["run_id", "column", "row", "value", "status", "sut_timestamp"]
|
|
185
|
+
raw_query = (f"SELECT {','.join(query_fields)}"
|
|
186
|
+
f" FROM generic_result_data_v1 WHERE test_id = ? AND name = ? LIMIT 2147483647")
|
|
187
|
+
query = self.cluster.prepare(raw_query)
|
|
188
|
+
tables_meta = self._get_tables_metadata(test_id=test_id)
|
|
135
189
|
graphs = []
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
data = ArgusGenericResultData
|
|
190
|
+
for table in tables_meta:
|
|
191
|
+
data = self.cluster.session.execute(query=query, parameters=(test_id, table.name))
|
|
192
|
+
data = [ArgusGenericResultData(**cell) for cell in data]
|
|
193
|
+
if not data:
|
|
194
|
+
continue
|
|
139
195
|
graphs.extend(create_chartjs(table, data))
|
|
140
|
-
|
|
196
|
+
ticks = calculate_graph_ticks(graphs)
|
|
197
|
+
return graphs, ticks
|
|
198
|
+
|
|
199
|
+
def is_results_exist(self, test_id: UUID):
|
|
200
|
+
"""Verify if results for given test id exist at all."""
|
|
201
|
+
return bool(ArgusGenericResultMetadata.objects(test_id=test_id).only(["name"]).limit(1))
|
|
@@ -307,28 +307,6 @@ class TestRunService:
|
|
|
307
307
|
}
|
|
308
308
|
return response
|
|
309
309
|
|
|
310
|
-
def fetch_results(self, test_id: UUID, run_id: UUID) -> list[dict]:
|
|
311
|
-
cluster = ScyllaCluster.get()
|
|
312
|
-
query_fields = ["column", "row", "value", "status"]
|
|
313
|
-
raw_query = (f"SELECT {','.join(query_fields)},WRITETIME(value) as ordering "
|
|
314
|
-
f"FROM generic_result_data_v1 WHERE test_id = ? AND run_id = ? AND name = ?")
|
|
315
|
-
query = cluster.prepare(raw_query)
|
|
316
|
-
tables_meta = ArgusGenericResultMetadata.filter(test_id=test_id)
|
|
317
|
-
tables = []
|
|
318
|
-
for table in tables_meta:
|
|
319
|
-
cells = cluster.session.execute(query=query, parameters=(test_id, run_id, table.name))
|
|
320
|
-
if not cells:
|
|
321
|
-
continue
|
|
322
|
-
cells = [dict(cell.items()) for cell in cells]
|
|
323
|
-
tables.append({'meta': {
|
|
324
|
-
'name': table.name,
|
|
325
|
-
'description': table.description,
|
|
326
|
-
'columns_meta': table.columns_meta,
|
|
327
|
-
'rows_meta': table.rows_meta,
|
|
328
|
-
},
|
|
329
|
-
'cells': [{k: v for k, v in cell.items() if k in query_fields} for cell in cells],
|
|
330
|
-
'order': min([cell['ordering'] for cell in cells] or [0])})
|
|
331
|
-
return sorted(tables, key=lambda x: x['order'])
|
|
332
310
|
|
|
333
311
|
def submit_github_issue(self, issue_url: str, test_id: UUID, run_id: UUID):
|
|
334
312
|
user_tokens = UserOauthToken.filter(user_id=g.user.id).all()
|
|
@@ -7,6 +7,7 @@ class Status(Enum):
|
|
|
7
7
|
PASS = auto()
|
|
8
8
|
WARNING = auto()
|
|
9
9
|
ERROR = auto()
|
|
10
|
+
UNSET = auto()
|
|
10
11
|
|
|
11
12
|
def __str__(self):
|
|
12
13
|
return self.name
|
|
@@ -16,6 +17,7 @@ class ResultType(Enum):
|
|
|
16
17
|
INTEGER = auto()
|
|
17
18
|
FLOAT = auto()
|
|
18
19
|
DURATION = auto()
|
|
20
|
+
TEXT = auto()
|
|
19
21
|
|
|
20
22
|
def __str__(self):
|
|
21
23
|
return self.name
|
|
@@ -44,7 +46,7 @@ class ResultTableMeta(type):
|
|
|
44
46
|
cls_instance.name = meta.name
|
|
45
47
|
cls_instance.description = meta.description
|
|
46
48
|
cls_instance.columns = meta.Columns
|
|
47
|
-
cls_instance.
|
|
49
|
+
cls_instance.column_types = {column.name: column.type for column in cls_instance.columns}
|
|
48
50
|
cls_instance.rows = []
|
|
49
51
|
return cls_instance
|
|
50
52
|
|
|
@@ -57,12 +59,13 @@ class Cell:
|
|
|
57
59
|
status: Status
|
|
58
60
|
|
|
59
61
|
def as_dict(self) -> dict:
|
|
60
|
-
|
|
62
|
+
cell = {"value_text": self.value} if isinstance(self.value, str) else {"value": self.value}
|
|
63
|
+
cell.update({
|
|
61
64
|
"column": self.column,
|
|
62
65
|
"row": self.row,
|
|
63
|
-
"value": self.value,
|
|
64
66
|
"status": str(self.status)
|
|
65
|
-
}
|
|
67
|
+
})
|
|
68
|
+
return cell
|
|
66
69
|
|
|
67
70
|
|
|
68
71
|
@dataclass
|
|
@@ -94,6 +97,8 @@ class GenericResultTable(metaclass=ResultTableMeta):
|
|
|
94
97
|
}
|
|
95
98
|
|
|
96
99
|
def add_result(self, column: str, row: str, value: Union[int, float, str], status: Status):
|
|
97
|
-
if column not in self.
|
|
100
|
+
if column not in self.column_types:
|
|
98
101
|
raise ValueError(f"Column {column} not found in the table")
|
|
102
|
+
if isinstance(value, str) and self.column_types[column] != ResultType.TEXT:
|
|
103
|
+
raise ValueError(f"Column {column} is not of type TEXT")
|
|
99
104
|
self.results.append(Cell(column=column, row=row, value=value, status=status))
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
from dataclasses import dataclass, field, asdict
|
|
2
|
+
from typing import Dict, List, Tuple, Union, Type
|
|
3
|
+
from uuid import UUID
|
|
4
|
+
from enum import Enum
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Status(Enum):
|
|
10
|
+
PASS = 1
|
|
11
|
+
WARNING = 2
|
|
12
|
+
ERROR = 3
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ResultType(Enum):
|
|
16
|
+
INTEGER = int
|
|
17
|
+
FLOAT = float
|
|
18
|
+
TEXT = str
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class ColumnMetadata:
|
|
23
|
+
id: int
|
|
24
|
+
unit: str
|
|
25
|
+
type: ResultType
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class Result:
|
|
30
|
+
value: Union[int, float, str]
|
|
31
|
+
status: Status
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ResultTableMeta(type):
|
|
35
|
+
def __new__(cls, name, bases, dct):
|
|
36
|
+
cls_instance = super().__new__(cls, name, bases, dct)
|
|
37
|
+
meta = dct.get('Meta')
|
|
38
|
+
|
|
39
|
+
if meta:
|
|
40
|
+
cls_instance.table_name = meta.table_name
|
|
41
|
+
cls_instance.columns_map = {col_name: ColumnMetadata(id=col_id, unit=unit, type=result_type)
|
|
42
|
+
for col_name, (col_id, unit, result_type) in meta.Columns.items()}
|
|
43
|
+
cls_instance.rows_map = {row_name: row_id
|
|
44
|
+
for row_name, row_id in meta.Rows.items()}
|
|
45
|
+
return cls_instance
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class Row:
|
|
49
|
+
def __init__(self, columns_map: Dict[str, ColumnMetadata]):
|
|
50
|
+
self.columns_map = columns_map
|
|
51
|
+
self.data: Dict[str, Result] = {}
|
|
52
|
+
|
|
53
|
+
def __getitem__(self, column_name: str) -> Result:
|
|
54
|
+
return self.data[column_name]
|
|
55
|
+
|
|
56
|
+
def __setitem__(self, column_name: str, result: Result):
|
|
57
|
+
if column_name not in self.columns_map:
|
|
58
|
+
raise ValueError(f"Column name '{column_name}' not found in columns_map.")
|
|
59
|
+
column_metadata = self.columns_map[column_name]
|
|
60
|
+
if not isinstance(result.value, column_metadata.type.value):
|
|
61
|
+
raise ValueError(f"Value {result.value} for column '{column_name}' is not of type {column_metadata.type.name}")
|
|
62
|
+
self.data[column_name] = result
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class BaseResultTable(metaclass=ResultTableMeta):
|
|
67
|
+
results: Dict[str, Row] = field(default_factory=dict)
|
|
68
|
+
|
|
69
|
+
def as_dict(self) -> dict:
|
|
70
|
+
results_list = []
|
|
71
|
+
for row_name, row in self.results.items():
|
|
72
|
+
row_id = self.rows_map.get(row_name)
|
|
73
|
+
for column_name, result in row.data.items():
|
|
74
|
+
column_metadata = self.columns_map.get(column_name)
|
|
75
|
+
results_list.append({
|
|
76
|
+
"column_id": column_metadata.id,
|
|
77
|
+
"row_id": row_id,
|
|
78
|
+
"result": result.value,
|
|
79
|
+
"status": result.status.value
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
meta_info = {
|
|
83
|
+
"table_name": self.table_name,
|
|
84
|
+
"columns": {name: {"id": meta.id, "unit": meta.unit, "type": meta.type.name} for name, meta in self.columns_map.items()},
|
|
85
|
+
"rows": self.rows_map
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
"meta": meta_info,
|
|
89
|
+
"results": results_list
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
def to_json(self) -> str:
|
|
93
|
+
dict_result = self.as_dict()
|
|
94
|
+
return json.dumps(dict_result, default=str)
|
|
95
|
+
|
|
96
|
+
def __getitem__(self, row_name: str) -> Row:
|
|
97
|
+
if row_name not in self.rows_map:
|
|
98
|
+
raise ValueError(f"Row name '{row_name}' not found in rows_map.")
|
|
99
|
+
if row_name not in self.results:
|
|
100
|
+
self.results[row_name] = Row(self.columns_map)
|
|
101
|
+
return self.results[row_name]
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# Example of a specific result table with its own metadata
|
|
105
|
+
class LatencyResultTable(BaseResultTable):
|
|
106
|
+
class Meta:
|
|
107
|
+
table_name = "latency_percentile_write"
|
|
108
|
+
Columns = {
|
|
109
|
+
"latency": (1, "ms", ResultType.FLOAT),
|
|
110
|
+
"op_rate": (2, "ops", ResultType.INTEGER)
|
|
111
|
+
}
|
|
112
|
+
Rows = {
|
|
113
|
+
"mean": 1,
|
|
114
|
+
"p99": 2
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# Client code example
|
|
119
|
+
if __name__ == "__main__":
|
|
120
|
+
class LatencyResultTable(BaseResultTable):
|
|
121
|
+
class Meta:
|
|
122
|
+
table_name = "latency_percentile_write"
|
|
123
|
+
Columns = {
|
|
124
|
+
"latency": (1, "ms", ResultType.FLOAT),
|
|
125
|
+
"op_rate": (2, "ops", ResultType.INTEGER)
|
|
126
|
+
}
|
|
127
|
+
Rows = {
|
|
128
|
+
"mean": 1,
|
|
129
|
+
"p99": 2
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
result_table = LatencyResultTable()
|
|
133
|
+
|
|
134
|
+
result_table["mean"]["latency"] = Result(value=1.1, status=Status.WARNING)
|
|
135
|
+
result_table["mean"]["op_rate"] = Result(value=59988, status=Status.ERROR)
|
|
136
|
+
result_table["p99"]["latency"] = Result(value=2.7, status=Status.PASS)
|
|
137
|
+
result_table["p99"]["op_rate"] = Result(value=59988, status=Status.WARNING)
|
|
138
|
+
|
|
139
|
+
from argus.client.sct.client import ArgusSCTClient
|
|
140
|
+
|
|
141
|
+
run_id = UUID("24e09748-bba4-47fd-a615-bf7ea2c425eb")
|
|
142
|
+
client = ArgusSCTClient(run_id, auth_token="UO+2GXL9XqSgcVJijWk5WnbPXPit5ot5nfkLAHAr7SaqROfSCWycabpp/wxyY8+I", base_url="http://localhost:5000")
|
|
143
|
+
client.submit_results(result_table)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "argus-alm"
|
|
3
|
-
version = "0.12.
|
|
3
|
+
version = "0.12.8"
|
|
4
4
|
description = "Argus"
|
|
5
|
-
authors = ["Alexey Kartashov <alexey.kartashov@scylladb.com>"]
|
|
5
|
+
authors = ["Alexey Kartashov <alexey.kartashov@scylladb.com>", "Łukasz Sójka <lukasz.sojka@scylladb.com>"]
|
|
6
6
|
license = "Apache-2.0"
|
|
7
7
|
repository = "https://github.com/scylladb/argus"
|
|
8
8
|
readme = "README.md"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/plugins/driver_matrix_tests/controller.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{argus_alm-0.12.7 → argus_alm-0.12.8}/argus/backend/plugins/driver_matrix_tests/raw_types.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|