Skip to content

Commit

Permalink
improvement(graphs): Mark dependency changes
Browse files Browse the repository at this point in the history
In performance tests it's important to note changes of dependencies,
like kernel, drivers, stress tools and SUT version.

This commit changes the way point is displayed:
when there's package change (except SUT change) then the point gets
a white background.
Hovering over the point shows tooltip with all packages changes
(including SUT).

Tooltip position was adjusted to be show above the point so mouse is not
covering text.

closes: scylladb#490
  • Loading branch information
soyacz committed Nov 14, 2024
1 parent eb3097c commit 5fe45ee
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 38 deletions.
44 changes: 33 additions & 11 deletions argus/backend/service/results_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,35 @@ class RunsDetails:
shapes = ["circle", "triangle", "rect", "star", "dash", "crossRot", "line"]


def get_sorted_data_for_column_and_row(data: List[ArgusGenericResultData], column: str, row: str) -> List[Dict[str, Any]]:
return sorted([{"x": entry.sut_timestamp.strftime('%Y-%m-%dT%H:%M:%SZ'),
def get_sorted_data_for_column_and_row(data: List[ArgusGenericResultData], column: str, row: str,
runs_details: RunsDetails, main_package: str) -> List[Dict[str, Any]]:
points = sorted([{"x": entry.sut_timestamp.strftime('%Y-%m-%dT%H:%M:%SZ'),
"y": entry.value,
"id": entry.run_id}
"id": entry.run_id,
}
for entry in data if entry.column == column and entry.row == row],
key=lambda point: point["x"])

if not points:
return points
packages = runs_details.packages
points[0]['changes'] = []
points[0]['dep_change'] = False
prev_versions = {pkg.name: pkg.version + (f" ({pkg.date})" if pkg.date else "") for pkg in packages.get(points[0]["id"], [])}
for point in points[1:]:
changes = []
mark_dependency_change = False
current_versions = {pkg.name: pkg.version + (f" ({pkg.date})" if pkg.date else "") for pkg in packages.get(point["id"], [])}
for pkg_name in current_versions.keys() | prev_versions.keys():
curr_ver = current_versions.get(pkg_name)
prev_ver = prev_versions.get(pkg_name)
if curr_ver != prev_ver:
changes.append({'name': pkg_name, 'prev_version': prev_ver, 'curr_version': curr_ver})
if pkg_name != main_package:
mark_dependency_change = True
point['changes'] = [f"{change['name']}: {change['prev_version']} -> {change['curr_version']}" for change in changes]
point['dep_change'] = mark_dependency_change
prev_versions = current_versions
return points

def get_min_max_y(datasets: List[Dict[str, Any]]) -> (float, float):
"""0.5 - 1.5 of min/max of 50% results"""
Expand Down Expand Up @@ -191,7 +213,8 @@ def calculate_limits(points: List[dict], best_results: List, validation_rules_li


def create_datasets_for_column(table: ArgusGenericResultMetadata, data: list[ArgusGenericResultData],
best_results: dict[str, List[BestResult]], releases_map: ReleasesMap, column: ColumnMetadata) -> List[Dict]:
best_results: dict[str, List[BestResult]], releases_map: ReleasesMap, column: ColumnMetadata,
runs_details: RunsDetails, main_package:str) -> List[Dict]:
"""
Create datasets (series) for a specific column, splitting by version and showing limit lines.
"""
Expand All @@ -200,7 +223,7 @@ def create_datasets_for_column(table: ArgusGenericResultMetadata, data: list[Arg

for idx, row in enumerate(table.rows_meta):
line_color = colors[idx % len(colors)]
points = get_sorted_data_for_column_and_row(data, column.name, row)
points = get_sorted_data_for_column_and_row(data, column.name, row, runs_details, main_package)

datasets.extend(create_release_datasets(points, row, releases_map, line_color))

Expand All @@ -226,7 +249,7 @@ def create_release_datasets(points: list[Dict], row: str, releases_map: Releases
"label": f"{release} - {row}",
"borderColor": line_color,
"borderWidth": 2,
"pointRadius": 2,
"pointRadius": 3,
"showLine": True,
"data": release_points,
"pointStyle": shapes[v_idx % len(shapes)]
Expand Down Expand Up @@ -324,15 +347,15 @@ def _split_results_by_release(packages: dict[str, list[PackageVersion]], main_pa


def create_chartjs(table: ArgusGenericResultMetadata, data: list[ArgusGenericResultData], best_results: dict[str, List[BestResult]],
releases_map: ReleasesMap) -> List[Dict]:
releases_map: ReleasesMap, runs_details: RunsDetails, main_package: str) -> List[Dict]:
"""
Create Chart.js-compatible graph for each column in the table.
"""
graphs = []
columns = [column for column in table.columns_meta if column.type != "TEXT"]

for column in columns:
datasets = create_datasets_for_column(table, data, best_results, releases_map, column)
datasets = create_datasets_for_column(table, data, best_results, releases_map, column, runs_details, main_package)

if datasets:
min_y, max_y = get_min_max_y(datasets)
Expand Down Expand Up @@ -430,8 +453,7 @@ def get_test_graphs(self, test_id: UUID, start_date: datetime | None = None, end
best_results = self.get_best_results(test_id=test_id, name=table.name)
main_package = _identify_most_changed_package([pkg for sublist in runs_details.packages.values() for pkg in sublist])
releases_map = _split_results_by_release(runs_details.packages, main_package=main_package)
graphs.extend(create_chartjs(table, data, best_results,
releases_map=releases_map))
graphs.extend(create_chartjs(table, data, best_results, releases_map=releases_map, runs_details=runs_details, main_package=main_package))
releases_filters.update(releases_map.keys())
ticks = calculate_graph_ticks(graphs)
return graphs, ticks, list(releases_filters)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
create_limit_dataset,
calculate_limits,
calculate_graph_ticks, _identify_most_changed_package, _split_results_by_release,
BestResult
BestResult, RunsDetails
)
from argus.backend.models.result import ArgusGenericResultMetadata, ArgusGenericResultData, ColumnMetadata, ValidationRules

Expand Down Expand Up @@ -49,24 +49,33 @@ def test_split_results_by_versions_should_group_correctly(package_data):


def test_get_sorted_data_for_column_and_row():
run_id1 = uuid4()
run_id2 = uuid4()
run_id3 = uuid4()
data = [
ArgusGenericResultData(run_id=uuid4(), column="col1", row="row1", value=1.5, status="PASS", sut_timestamp=datetime(2023, 10, 23)),
ArgusGenericResultData(run_id=uuid4(), column="col1", row="row1", value=2.5, status="PASS", sut_timestamp=datetime(2023, 10, 24)),
ArgusGenericResultData(run_id=uuid4(), column="col1", row="row1", value=0.5, status="PASS", sut_timestamp=datetime(2023, 10, 22)),
ArgusGenericResultData(run_id=uuid4(), column="col1", row="row2", value=3.5, status="PASS", sut_timestamp=datetime(2023, 10, 25)),
ArgusGenericResultData(run_id=uuid4(), column="col2", row="row1", value=4.5, status="PASS", sut_timestamp=datetime(2023, 10, 26)),
ArgusGenericResultData(run_id=run_id1, column="col1", row="row1", value=1.5, status="PASS",
sut_timestamp=datetime(2023, 10, 23)),
ArgusGenericResultData(run_id=run_id2, column="col1", row="row1", value=2.5, status="PASS",
sut_timestamp=datetime(2023, 10, 24)),
ArgusGenericResultData(run_id=run_id3, column="col1", row="row1", value=0.5, status="PASS",
sut_timestamp=datetime(2023, 10, 22)),
]
result = get_sorted_data_for_column_and_row(data, "col1", "row1")
packages = {
run_id3: [PackageVersion(name='pkg1', version='1.0', date='', revision_id='', build_id='')],
run_id1: [PackageVersion(name='pkg1', version='1.0', date='', revision_id='', build_id=''),
PackageVersion(name='pkg2', version='1.0', date='', revision_id='', build_id='')],
run_id2: [PackageVersion(name='pkg1', version='1.1', date='20241111', revision_id='', build_id=''),
PackageVersion(name='pkg2', version='1.0', date='', revision_id='', build_id='')],
}
runs_details = RunsDetails(ignored=[], packages=packages)
result = get_sorted_data_for_column_and_row(data, "col1", "row1", runs_details, main_package="pkg1")
expected = [
{"x": "2023-10-22T00:00:00Z", "y": 0.5},
{"x": "2023-10-23T00:00:00Z", "y": 1.5},
{"x": "2023-10-24T00:00:00Z", "y": 2.5},
{"x": "2023-10-22T00:00:00Z", "y": 0.5, "changes": []},
{"x": "2023-10-23T00:00:00Z", "y": 1.5, "changes": ["pkg2: None -> 1.0"]},
{"x": "2023-10-24T00:00:00Z", "y": 2.5, "changes": ["pkg1: 1.0 -> 1.1 (20241111)"]},
]

result_without_id = [{"x": item["x"], "y": item["y"]} for item in result]

assert result_without_id == expected

result_data = [{"x": item["x"], "y": item["y"], "changes": item["changes"]} for item in result]
assert result_data == expected

def test_get_min_max_y():
datasets = [
Expand Down Expand Up @@ -138,7 +147,8 @@ def test_create_datasets_for_column():
best_results = {}
releases_map = {"2024.2": [point.run_id for point in data][:1], "2024.3": [point.run_id for point in data][2:]}
column = table.columns_meta[0]
datasets = create_datasets_for_column(table, data, best_results, releases_map, column)
runs_details = RunsDetails(ignored=[], packages={})
datasets = create_datasets_for_column(table, data, best_results, releases_map, column, runs_details, main_package="pkg1")
assert len(datasets) == 2
labels = [dataset["label"] for dataset in datasets]
assert "2024.2 - row1" in labels
Expand Down
20 changes: 13 additions & 7 deletions argus/backend/tests/results_service/test_create_chartjs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from uuid import uuid4

from argus.backend.models.result import ArgusGenericResultMetadata, ColumnMetadata, ArgusGenericResultData, ValidationRules
from argus.backend.service.results_service import create_chartjs, BestResult
from argus.backend.service.results_service import create_chartjs, BestResult, RunsDetails


def test_create_chartjs_without_validation_rules_should_create_chart_without_limits_series():
Expand Down Expand Up @@ -31,7 +31,8 @@ def test_create_chartjs_without_validation_rules_should_create_chart_without_lim
'col1:row1': [BestResult(key='col1:row1', value=100.0, result_date=datetime(2021, 1, 1), run_id=str(uuid4()))]
}
releases_map = {"1.0": [point.run_id for point in data]}
graphs = create_chartjs(table, data, best_results, releases_map)
runs_details = RunsDetails(ignored=[], packages={})
graphs = create_chartjs(table, data, best_results, releases_map, runs_details, main_package="pkg1")
assert len(graphs) == 1
assert len(graphs[0]['data']['datasets']) == 1 # no limits series

Expand Down Expand Up @@ -59,7 +60,8 @@ def test_create_chartjs_without_best_results_should_not_fail():
]
best_results = {}
releases_map = {"1.0": [point.run_id for point in data]}
graphs = create_chartjs(table, data, best_results, releases_map)
runs_details = RunsDetails(ignored=[], packages={})
graphs = create_chartjs(table, data, best_results, releases_map, runs_details, main_package="pkg1")
assert len(graphs) == 1
assert len(graphs[0]['data']['datasets']) == 1 # no limits series

Expand Down Expand Up @@ -91,7 +93,8 @@ def test_create_chartjs_with_validation_rules_should_add_limit_series():
'col1:row1': [BestResult(key='col1:row1', value=100.0, result_date=datetime(2021, 1, 1), run_id=str(uuid4()))]
}
releases_map = {"1.0": [point.run_id for point in data]}
graphs = create_chartjs(table, data, best_results, releases_map)
runs_details = RunsDetails(ignored=[], packages={})
graphs = create_chartjs(table, data, best_results, releases_map, runs_details, main_package="pkg1")
assert 'limit' in graphs[0]['data']['datasets'][0]['data'][0]

def test_chartjs_with_multiple_best_results_and_validation_rules_should_adjust_limits_for_each_point():
Expand Down Expand Up @@ -138,7 +141,8 @@ def test_chartjs_with_multiple_best_results_and_validation_rules_should_adjust_l
]
}
releases_map = {"1.0": [point.run_id for point in data]}
graphs = create_chartjs(table, data, best_results, releases_map)
runs_details = RunsDetails(ignored=[], packages={})
graphs = create_chartjs(table, data, best_results, releases_map, runs_details, main_package="pkg1")
datasets = graphs[0]['data']['datasets']
limits = [point.get('limit') for dataset in datasets for point in dataset['data'] if 'limit' in point]
assert len(limits) == 2
Expand All @@ -156,7 +160,8 @@ def test_create_chartjs_no_data_should_not_fail():
data = []
best_results = {}
releases_map = {"1.0": []}
graphs = create_chartjs(table, data, best_results, releases_map)
runs_details = RunsDetails(ignored=[], packages={})
graphs = create_chartjs(table, data, best_results, releases_map, runs_details, main_package="pkg1")
assert len(graphs) == 0

def test_create_chartjs_multiple_columns_and_rows():
Expand Down Expand Up @@ -203,7 +208,8 @@ def test_create_chartjs_multiple_columns_and_rows():
]
}
releases_map = {"1.0": [point.run_id for point in data]}
graphs = create_chartjs(table, data, best_results, releases_map)
runs_details = RunsDetails(ignored=[], packages={})
graphs = create_chartjs(table, data, best_results, releases_map, runs_details, main_package="pkg1")
assert len(graphs) == 2
assert len(graphs[0]['data']['datasets']) == 2 # should have also limits dataset
assert len(graphs[1]['data']['datasets']) == 1 # no limits series
29 changes: 26 additions & 3 deletions frontend/TestRun/ResultsGraph.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script>
import {Chart, registerables} from "chart.js";
import {Chart, registerables, Tooltip} from "chart.js";
import {createEventDispatcher, onMount} from "svelte";
import "chartjs-adapter-date-fns";
Expand All @@ -13,6 +13,18 @@
let chart;
const ticksGapPx = 40;
const dispatch = createEventDispatcher();
Tooltip.positioners.above = function (elements, eventPosition) {
const tooltip = this;
if (elements.length === 0) {
return false;
}
return {
x: elements[0].element.x,
y: elements[0].element.y - tooltip.height / 2 - 5,
xAlign: "center",
yAlign: "center",
};
};
function calculateTickValues(xValues, width, minGapInPixels) {
// trying to calculate the best tick values for x-axis - to be evenly distributed as possible but matching actual points
Expand Down Expand Up @@ -74,14 +86,17 @@
graph.options.responsive = false;
graph.options.lazy = true;
graph.options.plugins.tooltip = {
position: "above",
usePointStyle: true,
callbacks: {
label: function (tooltipItem) {
const y = tooltipItem.parsed.y.toFixed(2);
const x = new Date(tooltipItem.parsed.x).toLocaleDateString("sv-SE");
const ori = tooltipItem.raw.ori;
const limit = tooltipItem.raw.limit;
return `${x}: ${ori ? ori : y} (limit: ${limit?.toFixed(2) || "N/A"})`;
}
const changes = tooltipItem.raw.changes;
return [`${x}: ${ori ? ori : y} (limit: ${limit?.toFixed(2) || "N/A"})`, ...changes];
},
}
};
graph.options.scales.x.min = ticks["min"];
Expand All @@ -94,6 +109,14 @@
}
}
};
graph.data.datasets.forEach((dataset) => {
const pointBackgroundColors = dataset.data.map((point) =>
point.dep_change ? 'white' : dataset.backgroundColor || dataset.borderColor
);
dataset.pointBackgroundColor = pointBackgroundColors;
});
chart = new Chart(
document.getElementById(`graph-${test_id}-${index}`),
{type: "scatter", data: graph.data, options: {...graph.options, ...actions}}
Expand Down
1 change: 0 additions & 1 deletion frontend/TestRun/ResultsGraphs.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,6 @@
onMount(() => {
setDefaultDateRange();
fetchTestResults(test_id);
});
</script>
<div class="filters-container">
Expand Down

0 comments on commit 5fe45ee

Please sign in to comment.