Skip to content

Commit 4dde06a

Browse files
committed
Report disposable performance in JSON
For: QubesOS/qubes-issues#1512
1 parent 8de4cd0 commit 4dde06a

2 files changed

Lines changed: 56 additions & 42 deletions

File tree

run-tests

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ case $0 in (/*) cd "${0%/*}/";; (*/*) cd "./${0%/*}";; esac
66
install_rpm_deps () {
77
local applications
88
applications=(lvm2 python3-docutils python3-pyyaml python3-jinja2
9-
python3-lxml btrfs-progs vim-common python3-coverage python3-inotify cryptsetup)
9+
python3-lxml btrfs-progs vim-common python3-coverage python3-inotify
10+
python3-deepmerge cryptsetup)
1011

1112
if test -f /etc/qubes-release; then
1213
sudo qubes-dom0-update "${applications[@]}" || :
@@ -20,7 +21,7 @@ install_apt_deps(){
2021
local applications
2122
applications=(lvm2 python3-docutils python3-yaml python3-jinja2
2223
python3-lxml btrfs-progs python3-coverage python3-inotify
23-
python3-libvirt cryptsetup)
24+
python3-libvirt python3-deepmerge cryptsetup)
2425
sudo apt --no-install-recommends -- install "${applications[@]}" ||
2526
: # we don’t actually care if this succeeds
2627
}

tests/dispvm_perf.py

Lines changed: 53 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,12 @@
3030
import asyncio
3131
import concurrent.futures
3232
import dataclasses
33+
import json
3334
import os
35+
import statistics
3436
import subprocess
3537
import time
38+
from deepmerge import always_merger
3639

3740

3841
import qubesadmin
@@ -74,8 +77,8 @@ class TestConfig:
7477
it is simpler to achieve.
7578
- Concurrent calls are multiple requests that are done without regards
7679
to the previous request completion.
77-
- Concurrency average time is skewed as there are multiples
78-
simultaneous calls.
80+
- Concurrency mean time is skewed as there are multiples simultaneous
81+
calls.
7982
Normal VS Preloaded:
8083
- Improving normal qube startup will shorten preload usage time, but
8184
the reverse is not true. Normal disposables are a control group for
@@ -396,19 +399,22 @@ def run_latency_api_calls(self, test):
396399
qube = self.dvm
397400

398401
results = {}
402+
results["api_results"] = {}
403+
results["api_results"]["iteration"] = {}
404+
results["api_results"]["stage"] = {}
399405
start_time = get_time()
400406
if test.concurrent:
401407
all_results = asyncio.run(self.api_thread(test, service, qube))
402408
for i in range(1, self.iterations + 1):
403-
results[i] = all_results[i - 1]
409+
results["api_results"]["iteration"][i] = all_results[i - 1]
404410
else:
405411
for i in range(1, self.iterations + 1):
406-
results[i] = self.call_api(
412+
results["api_results"]["iteration"][i] = self.call_api(
407413
test=test, service=service, qube=qube
408414
)
409415
end_time = get_time()
410416

411-
sample_keys = list(results[1].keys())
417+
sample_keys = list(results["api_results"]["iteration"][1].keys())
412418
value_keys = [k for k in sample_keys if k != "total"]
413419
headers = (
414420
["iter"]
@@ -417,7 +423,7 @@ def run_latency_api_calls(self, test):
417423
+ [f"{k}%" for k in value_keys]
418424
)
419425
rows = []
420-
for key, values in results.items():
426+
for key, values in results["api_results"]["iteration"].items():
421427
total = values.get("total", 0)
422428
row_values = [str(key)]
423429
for k in value_keys:
@@ -442,56 +448,63 @@ def run_latency_api_calls(self, test):
442448
" ".join(val.rjust(col_widths[i]) for i, val in enumerate(row))
443449
)
444450

451+
values_by_stage = {key: {} for key in sample_keys}
452+
for subdict in results["api_results"]["iteration"].values():
453+
for key, value in subdict.items():
454+
values_by_stage[key].setdefault("values", []).append(value)
455+
for key, value in values_by_stage.items():
456+
values = value["values"]
457+
mean = round(statistics.mean(values), ROUND_PRECISION)
458+
median = round(statistics.median(values), ROUND_PRECISION)
459+
values_by_stage[key]["mean"] = mean
460+
values_by_stage[key]["median"] = median
461+
results["api_results"]["stage"].update(values_by_stage)
462+
445463
total_time = round(end_time - start_time, ROUND_PRECISION)
446464
return total_time, results
447465

448466
def report_result(self, test, result):
449-
items = " ".join(
450-
"{}={}".format(key, value) for key, value in vars(test).items()
451-
)
467+
try:
468+
template = self.vm1.template.name
469+
except AttributeError:
470+
template = self.vm1.name
471+
data = vars(test)
472+
data["template"] = str(template)
452473
if test.admin_api:
453474
total_time = result[0]
454-
average = round(total_time / self.iterations, ROUND_PRECISION)
455-
pretty_average = f"{average:.{ROUND_PRECISION}f}"
456-
compiled_result = []
457-
for key, value in result[1].items():
458-
individual_result = (
459-
f"{key}=("
460-
+ ",".join(
461-
f"{k}={v:.{ROUND_PRECISION}f}" for k, v in value.items()
462-
)
463-
+ ")"
464-
)
465-
compiled_result.append(individual_result)
466-
items += f" iterations={self.iterations} average={pretty_average} "
467-
items += " ".join(compiled_result)
475+
data.update(result[1].items())
468476
else:
469477
total_time = result
470-
average = total_time / self.iterations
471-
pretty_average = f"{average:.{ROUND_PRECISION}f}"
472-
items += f" iterations={self.iterations} average={pretty_average}"
478+
mean = round(total_time / self.iterations, ROUND_PRECISION)
479+
data.update(
480+
{
481+
"iterations": self.iterations,
482+
"mean": mean,
483+
"total_time": total_time,
484+
}
485+
)
486+
pretty_mean = f"{mean:.{ROUND_PRECISION}f}"
473487
pretty_total_time = f"{total_time:.{ROUND_PRECISION}f}"
474-
final_result = pretty_total_time + " " + items
475488
pretty_items = "iterations=" + str(self.iterations)
476-
pretty_items += " average=" + pretty_average
489+
pretty_items += " mean=" + pretty_mean
477490
print(f"Run time ({pretty_items}): {pretty_total_time}s")
478491
results_file = os.environ.get("QUBES_TEST_PERF_FILE")
479492
if not results_file:
480493
return
481494
try:
482-
if self.vm2 and self.vm1.template != self.vm2.template:
483-
name_prefix = (
484-
f"{self.vm1.template!s}_" f"{self.vm2.template!s}_"
485-
)
486-
else:
487-
name_prefix = f"{self.vm1.template!s}_"
495+
name_prefix = f"{template!s}_"
488496
except AttributeError:
489-
if self.vm2:
490-
name_prefix = f"{self.vm1!s}_{self.vm2!s}_"
491-
else:
492-
name_prefix = f"{self.vm1!s}_"
493-
with open(results_file, "a", encoding="ascii") as file:
494-
file.write(name_prefix + test.name + " " + str(final_result) + "\n")
497+
name_prefix = f"{template!s}_"
498+
data_final = {}
499+
data_final[name_prefix + test.name] = data
500+
try:
501+
with open(results_file, "r", encoding="ascii") as file:
502+
old_data = json.load(file)
503+
except (FileNotFoundError, json.JSONDecodeError):
504+
old_data = {}
505+
data_final = always_merger.merge(old_data, data_final)
506+
with open(results_file, "w", encoding="ascii") as file:
507+
json.dump(data_final, file)
495508

496509
def run_test(self, test: TestConfig):
497510
with open(POLICY_FILE, "w", encoding="ascii") as policy:

0 commit comments

Comments
 (0)