3030import asyncio
3131import concurrent .futures
3232import dataclasses
33+ import json
3334import os
35+ import statistics
3436import subprocess
3537import time
38+ from deepmerge import always_merger
3639
3740
3841import 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