Skip to content

Commit aabb3b2

Browse files
committed
python: Implemented run_speed_tests() and related update_speed_test_results().
1 parent 19d9c2c commit aabb3b2

File tree

4 files changed

+265
-24
lines changed

4 files changed

+265
-24
lines changed

python/inet/test/speed.py

Lines changed: 0 additions & 24 deletions
This file was deleted.

python/inet/test/speed/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
"""
2+
This package supports automated speed testing.
3+
4+
The main function is :py:func:`run_speed_tests <inet.test.speed.task.run_speed_tests>`. It allows running multiple speed tests matching
5+
the provided filter criteria.
6+
"""
7+
8+
from inet.test.speed.store import *
9+
from inet.test.speed.task import *
10+
11+
__all__ = [k for k,v in locals().items() if k[0] != "_" and v.__class__.__name__ != "module"]

python/inet/test/speed/store.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import json
2+
import logging
3+
import os
4+
import subprocess
5+
import time
6+
7+
from inet.common import *
8+
from inet.simulation.project import *
9+
10+
__sphinx_mock__ = True # ignore this module in documentation
11+
12+
_logger = logging.getLogger(__name__)
13+
14+
_speed_measurement_stores = dict()
15+
16+
class SpeedMeasurementStore:
17+
def __init__(self, simulation_project, file_name):
18+
self.simulation_project = simulation_project
19+
self.file_name = file_name
20+
self.entries = None
21+
22+
def read(self):
23+
_logger.info(f"Reading speed measurements from {self.file_name}")
24+
file = open(self.file_name, "r")
25+
self.entries = json.load(file)
26+
file.close()
27+
28+
def write(self):
29+
self.get_entries().sort(key=lambda element: (element["working_directory"], element["ini_file"], element["config"], element["run_number"], element["sim_time_limit"]))
30+
_logger.info(f"Writing speed measurements to {self.file_name}")
31+
file = open(self.file_name, "w")
32+
json.dump(self.entries, file, indent=True)
33+
file.close()
34+
35+
def ensure(self):
36+
if os.path.exists(self.file_name):
37+
self.read()
38+
else:
39+
self.entries = []
40+
self.write()
41+
42+
def clear(self):
43+
self.entries = []
44+
45+
def reset(self):
46+
self.entries = None
47+
48+
def get_entries(self):
49+
if self.entries is None:
50+
self.ensure()
51+
return self.entries
52+
53+
def set_entries(self, entries):
54+
self.entries = entries
55+
56+
def find_entry(self, **kwargs):
57+
result = self.filter_entries(**kwargs)
58+
return result[0] if len(result) == 1 else None
59+
60+
def get_entry(self, **kwargs):
61+
result = self.find_entry(**kwargs)
62+
if result is not None:
63+
return result
64+
else:
65+
print(kwargs)
66+
raise Exception("Entry not found")
67+
68+
def remove_entry(self, entry):
69+
self.get_entries().remove(entry)
70+
71+
def filter_entries(self, test_result=None, working_directory=os.getcwd(), ini_file="omnetpp.ini", config="General", run_number=0, sim_time_limit=None, itervars=None):
72+
def f(entry):
73+
return (working_directory is None or entry["working_directory"] == working_directory) and \
74+
(ini_file is None or entry["ini_file"] == ini_file) and \
75+
(config is None or entry["config"] == config) and \
76+
(run_number is None or entry["run_number"] == run_number) and \
77+
(sim_time_limit is None or entry["sim_time_limit"] == sim_time_limit) and \
78+
(itervars is None or entry["itervars"] == itervars) and \
79+
(test_result is None or entry["test_result"] == test_result)
80+
return list(filter(f, self.get_entries()))
81+
82+
def find_elapsed_wall_time(self, **kwargs):
83+
entry = self.find_entry(**kwargs)
84+
if entry is not None:
85+
return entry["elapsed_wall_time"]
86+
else:
87+
return None
88+
89+
def get_elapsed_wall_time(self, **kwargs):
90+
return self.get_entry(**kwargs)["elapsed_wall_time"]
91+
92+
def set_elapsed_wall_time(self, elapsed_wall_time, **kwargs):
93+
self.get_entry(**kwargs)["elapsed_wall_time"] = elapsed_wall_time
94+
95+
def insert_elapsed_wall_time(self, elapsed_wall_time, test_result=None, working_directory=os.getcwd(), ini_file="omnetpp.ini", config="General", run_number=0, sim_time_limit=None, itervars="$repetition==0"):
96+
# assert test_result == "ERROR" or sim_time_limit is not None
97+
self.get_entries().append({"working_directory": working_directory,
98+
"ini_file": ini_file,
99+
"config": config,
100+
"run_number": run_number,
101+
"sim_time_limit": sim_time_limit,
102+
"test_result": test_result,
103+
"elapsed_wall_time": elapsed_wall_time,
104+
"timestamp": time.time(),
105+
"itervars": itervars})
106+
107+
def update_elapsed_wall_time(self, elapsed_wall_time, **kwargs):
108+
entry = self.find_entry(**kwargs)
109+
if entry:
110+
entry["elapsed_wall_time"] = elapsed_wall_time
111+
else:
112+
self.insert_elapsed_wall_time(elapsed_wall_time, **kwargs)
113+
114+
def remove_elapsed_wall_times(self, **kwargs):
115+
list(map(lambda element: self.entries.remove(element), self.filter_entries(**kwargs)))
116+
117+
def get_speed_measurement_store(simulation_project):
118+
if not simulation_project in _speed_measurement_stores:
119+
_speed_measurement_stores[simulation_project] = SpeedMeasurementStore(simulation_project, simulation_project.get_full_path(simulation_project.speed_store))
120+
return _speed_measurement_stores[simulation_project]

python/inet/test/speed/task.py

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import logging
2+
3+
from inet.simulation import *
4+
from inet.test.simulation import *
5+
from inet.test.task import *
6+
from inet.test.speed.store import *
7+
8+
__sphinx_mock__ = True # ignore this module in documentation
9+
10+
_logger = logging.getLogger(__name__)
11+
12+
_speed_test_extra_args = ["--cmdenv-express-mode=true", "--cmdenv-performance-display=false", "--cmdenv-status-frequency=1000000s",
13+
"--record-eventlog=false", "--vector-recording=false", "--scalar-recording=false", "--bin-recording=false", "--param-recording=false"]
14+
15+
class SpeedTestTask(SimulationTestTask):
16+
def __init__(self, expected_wall_time=None, max_relative_error = 0.2, **kwargs):
17+
super().__init__(**kwargs)
18+
self.expected_wall_time = expected_wall_time
19+
self.max_relative_error = max_relative_error
20+
21+
def check_simulation_task_result(self, simulation_task_result, **kwargs):
22+
if (simulation_task_result.elapsed_wall_time - self.expected_wall_time) / self.expected_wall_time > self.max_relative_error:
23+
return self.task_result_class(task=self, simulation_task_result=simulation_task_result, result="FAIL", expected_result="PASS", reason="Elapsed wall time is too large")
24+
elif (self.expected_wall_time - simulation_task_result.elapsed_wall_time) / self.expected_wall_time > self.max_relative_error:
25+
return self.task_result_class(task=self, simulation_task_result=simulation_task_result, result="FAIL", expected_result="PASS", reason="Elapsed wall time is too small")
26+
else:
27+
return super().check_simulation_task_result(simulation_task_result, **kwargs)
28+
29+
def run_protected(self, **kwargs):
30+
return super().run_protected(nice=-10, extra_args=_speed_test_extra_args, **kwargs)
31+
32+
def get_speed_test_tasks(mode="release", run_number=0, working_directory_filter="showcases", **kwargs):
33+
multiple_simulation_tasks = get_simulation_tasks(name="speed test", mode=mode, run_number=run_number, working_directory_filter=working_directory_filter, **kwargs)
34+
simulation_project = multiple_simulation_tasks.simulation_project
35+
speed_measurement_store = get_speed_measurement_store(simulation_project)
36+
tasks = []
37+
for simulation_task in multiple_simulation_tasks.tasks:
38+
simulation_config = simulation_task.simulation_config
39+
expected_wall_time = speed_measurement_store.get_elapsed_wall_time(working_directory=simulation_config.working_directory, ini_file=simulation_config.ini_file, config=simulation_config.config, run_number=simulation_task.run_number)
40+
tasks.append(SpeedTestTask(simulation_task=simulation_task, expected_wall_time=expected_wall_time, **dict(kwargs, simulation_project=simulation_project)))
41+
return MultipleSimulationTestTasks(tasks=tasks, **dict(kwargs, simulation_project=simulation_project, concurrent=False))
42+
43+
def run_speed_tests(**kwargs):
44+
multiple_test_tasks = get_speed_test_tasks(**kwargs)
45+
return multiple_test_tasks.run(**kwargs)
46+
47+
class SpeedUpdateTask(SimulationUpdateTask):
48+
def __init__(self, action="Updating speed", **kwargs):
49+
super().__init__(action=action, **kwargs)
50+
self.locals = locals()
51+
self.locals.pop("self")
52+
self.kwargs = kwargs
53+
54+
def run_protected(self, **kwargs):
55+
simulation_config = self.simulation_task.simulation_config
56+
simulation_project = simulation_config.simulation_project
57+
start_time = time.time()
58+
simulation_task_result = self.simulation_task.run_protected(nice=-10, extra_args=_speed_test_extra_args, **kwargs)
59+
end_time = time.time()
60+
simulation_task_result.elapsed_wall_time = end_time - start_time
61+
speed_measurement_store = get_speed_measurement_store(simulation_project)
62+
expected_wall_time = speed_measurement_store.find_elapsed_wall_time(working_directory=simulation_config.working_directory, ini_file=simulation_config.ini_file, config=simulation_config.config, run_number=self.simulation_task.run_number)
63+
return SpeedUpdateTaskResult(task=self, simulation_task_result=simulation_task_result, expected_wall_time=expected_wall_time, elapsed_wall_time=simulation_task_result.elapsed_wall_time)
64+
65+
class MultipleSpeedUpdateTasks(MultipleSimulationUpdateTasks):
66+
def __init__(self, multiple_simulation_tasks=None, name="update speed", **kwargs):
67+
super().__init__(name=name, concurrent=False, **kwargs)
68+
self.locals = locals()
69+
self.locals.pop("self")
70+
self.kwargs = kwargs
71+
self.multiple_simulation_tasks = multiple_simulation_tasks
72+
73+
def run(self, simulation_project=None, concurrent=None, build=True, **kwargs):
74+
if concurrent is None:
75+
concurrent = self.multiple_simulation_tasks.concurrent
76+
simulation_project = simulation_project or self.multiple_simulation_tasks.simulation_project
77+
if build:
78+
build_project(simulation_project=simulation_project, **kwargs)
79+
multiple_speed_update_results = super().run(**kwargs)
80+
speed_measurement_store = get_speed_measurement_store(simulation_project)
81+
for speed_update_result in multiple_speed_update_results.results:
82+
speed_update_task = speed_update_result.task
83+
simulation_task = speed_update_task.simulation_task
84+
simulation_config = simulation_task.simulation_config
85+
elapsed_wall_time = speed_update_result.elapsed_wall_time
86+
if elapsed_wall_time is not None:
87+
speed_measurement_store.update_elapsed_wall_time(elapsed_wall_time, test_result="PASS", sim_time_limit=simulation_task.sim_time_limit,
88+
working_directory=simulation_config.working_directory, ini_file=simulation_config.ini_file, config=simulation_config.config, run_number=simulation_task.run_number)
89+
speed_measurement_store.write()
90+
return speed_measurement_store
91+
92+
class SpeedUpdateTaskResult(SimulationUpdateTaskResult):
93+
def __init__(self, expected_wall_time=None, elapsed_wall_time=None, reason=None, max_relative_error=0.2, **kwargs):
94+
super().__init__(**kwargs)
95+
self.locals = locals()
96+
self.locals.pop("self")
97+
self.kwargs = kwargs
98+
if expected_wall_time is None:
99+
self.result = "INSERT"
100+
self.reason = None
101+
self.color = COLOR_YELLOW
102+
elif abs(expected_wall_time - elapsed_wall_time) / expected_wall_time > max_relative_error:
103+
self.result = "UPDATE"
104+
self.reason = None
105+
self.color = COLOR_YELLOW
106+
else:
107+
self.result = "KEEP"
108+
self.reason = None
109+
self.color = COLOR_GREEN
110+
self.expected = self.result == self.expected_result
111+
112+
def get_description(self, complete_error_message=True, include_parameters=False, **kwargs):
113+
return (self.task.simulation_task.get_parameters_string() + " " if include_parameters else "") + \
114+
self.color + self.result + COLOR_RESET + \
115+
((" " + self.simulation_task_result.get_error_message(complete_error_message=complete_error_message)) if self.simulation_task_result and self.simulation_task_result.result == "ERROR" else "") + \
116+
(" (" + self.reason + ")" if self.reason else "")
117+
118+
def __repr__(self):
119+
return "Speed update result: " + self.get_description(include_parameters=True)
120+
121+
def print_result(self, complete_error_message=True, output_stream=sys.stdout, **kwargs):
122+
print(self.get_description(complete_error_message=complete_error_message), file=output_stream)
123+
124+
def get_update_speed_test_results_tasks(mode="release", run_number=0, working_directory_filter="showcases", **kwargs):
125+
update_tasks = []
126+
multiple_simulation_tasks = get_simulation_tasks(mode=mode, run_number=run_number, working_directory_filter=working_directory_filter, **kwargs)
127+
for simulation_task in multiple_simulation_tasks.tasks:
128+
update_task = SpeedUpdateTask(simulation_task=simulation_task, **kwargs)
129+
update_tasks.append(update_task)
130+
return MultipleSpeedUpdateTasks(multiple_simulation_tasks, tasks=update_tasks, **kwargs)
131+
132+
def update_speed_test_results(**kwargs):
133+
multiple_speed_update_tasks = get_update_speed_test_results_tasks(**kwargs)
134+
return multiple_speed_update_tasks.run(**kwargs)

0 commit comments

Comments
 (0)