188 lines
6.3 KiB
Python
188 lines
6.3 KiB
Python
import json
|
|
from collections import defaultdict
|
|
|
|
from ScEpTIC.analysis.options import AnalysisResultFormat
|
|
from ScEpTIC.exceptions import ConfigurationException
|
|
|
|
|
|
class CustomMetricsManager:
|
|
|
|
def __init__(self, vmstate):
|
|
self.metrics = {}
|
|
self.names = []
|
|
self.vmstate = vmstate
|
|
|
|
self.last_energy = defaultdict(float)
|
|
self.last_time = defaultdict(float)
|
|
self.last_cc = defaultdict(float)
|
|
|
|
for metric_id, metric_settings in self.vmstate.config.custom_metrics.metrics.items():
|
|
self._register(
|
|
metric_id,
|
|
metric_settings['metric_name'],
|
|
metric_settings['collect_energy'],
|
|
metric_settings['collect_time'],
|
|
metric_settings['collect_cc'],
|
|
metric_settings['data_diffs'],
|
|
metric_settings['print_data'],
|
|
)
|
|
|
|
def _register(self, metric_id, metric_name, collect_energy=False, collect_time=False, collect_cc=False, data_diffs=False, print_data=False):
|
|
"""
|
|
Register a custom metric
|
|
:param metric_id: Metric ID
|
|
:param metric_name: Metric name
|
|
:param collect_energy: Records simulation energy when metric is incremented
|
|
:param collect_time: Record simulation energy when metric is incremented
|
|
"""
|
|
if metric_id in self.metrics:
|
|
raise ValueError(f"Metric with id #{metric_id} already registered")
|
|
|
|
if metric_name in self.names:
|
|
raise ValueError(f"Metric with name {metric_name} already registered")
|
|
|
|
self.names.append(metric_name)
|
|
self.metrics[metric_id] = {
|
|
'name': metric_name,
|
|
'value': 0,
|
|
'energy': [] if collect_energy else None,
|
|
'time': [] if collect_time else None,
|
|
'cc': [] if collect_cc else None,
|
|
'data_diffs': data_diffs,
|
|
'print_data': print_data,
|
|
}
|
|
|
|
def increment(self, metric_id, val=1):
|
|
"""
|
|
Increment custom metric by val
|
|
:param metric_id: metric_id
|
|
:param val: value to increment
|
|
"""
|
|
if metric_id not in self.metrics:
|
|
raise ValueError(f"Metric with id #{metric_id} not registered")
|
|
|
|
self.metrics[metric_id]['value'] += val
|
|
|
|
#print(f"{metric_id}: {self.metrics[metric_id]['value']}")
|
|
|
|
if self.metrics[metric_id]['energy'] is not None:
|
|
e = self.vmstate.config.analysis.energy.system_model.get_used_energy()
|
|
|
|
if self.metrics[metric_id]['data_diffs']:
|
|
last_energy = self.last_energy[metric_id]
|
|
self.last_energy[metric_id] = e
|
|
e -= last_energy
|
|
|
|
self.metrics[metric_id]['energy'].append(e)
|
|
|
|
if self.metrics[metric_id]['time'] is not None:
|
|
t = self.vmstate.config.analysis.energy.system_model.get_simulation_time()
|
|
|
|
if self.metrics[metric_id]['data_diffs']:
|
|
last_time = self.last_time[metric_id]
|
|
self.last_time[metric_id] = t
|
|
t -= last_time
|
|
|
|
self.metrics[metric_id]['time'].append(t)
|
|
|
|
if self.metrics[metric_id]['cc'] is not None:
|
|
cc = self.vmstate.config.analysis.energy.system_model.get_elapsed_ticks()
|
|
|
|
if self.metrics[metric_id]['data_diffs']:
|
|
last_cc = self.last_cc[metric_id]
|
|
self.last_cc[metric_id] = cc
|
|
cc -= last_cc
|
|
|
|
self.metrics[metric_id]['cc'].append(cc)
|
|
|
|
if self.metrics[metric_id]['print_data']:
|
|
m = self.metrics[metric_id]
|
|
print(f"{m['name']} ({m['value']}) -> E={m['energy'][-1]}J; T={m['time'][-1]}s; CC={m['cc'][-1]}")
|
|
|
|
def get_name(self, metric_id):
|
|
"""
|
|
Returns metric name from metric_id
|
|
:param metric_id: metric_id
|
|
:return: metric name
|
|
"""
|
|
if metric_id not in self.metrics:
|
|
raise ValueError("Metric not registered")
|
|
|
|
return self.metrics[metric_id]['name']
|
|
|
|
def exists(self, metric_id):
|
|
"""
|
|
Returns if metric exists
|
|
:param metric_id: metric_id
|
|
:return: bool
|
|
"""
|
|
return metric_id in self.metrics
|
|
|
|
def reset(self):
|
|
"""
|
|
Resets all custom metrics
|
|
"""
|
|
for values in self.metrics.values():
|
|
values['value'] = 0
|
|
collect_energy = values['energy'] is None
|
|
collect_time = values['time'] is None
|
|
|
|
# Free space
|
|
del values['energy']
|
|
del values['time']
|
|
import gc
|
|
gc.collect()
|
|
|
|
values['energy'] = [] if collect_energy is not None else None
|
|
values['time'] = [] if collect_time is not None else None
|
|
|
|
|
|
def save_metrics(self, save_dir, result_format):
|
|
"""
|
|
Saves custom metrics
|
|
:param save_dir: the path where to save the metrics
|
|
:param result_format: the metrics format
|
|
"""
|
|
if not isinstance(result_format, AnalysisResultFormat):
|
|
raise Exception(f"Wrong result_format for {self.__class__.__name__}: {result_format} is not of type AnalysisResultFormat")
|
|
|
|
results = self.get_result(result_format)
|
|
|
|
with open(f"{save_dir}_custom_metrics.{result_format}", "w") as fp:
|
|
fp.write(results)
|
|
|
|
def get_result(self, result_format):
|
|
"""
|
|
Returns custom metrics values
|
|
:param result_format: the result format
|
|
"""
|
|
data = {}
|
|
|
|
for values in self.metrics.values():
|
|
data[values['name']] = {'value': values['value'], 'energy': values['energy'], 'time': values['time'], 'cc': values['cc']}
|
|
|
|
if result_format == AnalysisResultFormat.JSON:
|
|
retval = json.dumps(self.metrics)
|
|
|
|
elif result_format == AnalysisResultFormat.TEXT:
|
|
retval = ""
|
|
|
|
for name, val in data.items():
|
|
retval += f"{name}:\n"
|
|
retval += f" value: {val['value']}\n"
|
|
|
|
if val['energy'] is not None:
|
|
retval += f" energy:\n"
|
|
for e in val['energy']:
|
|
retval += f" - {e}J\n"
|
|
|
|
if val['time'] is not None:
|
|
retval += f" time:\n"
|
|
for t in val['time']:
|
|
retval += f" - {t}s\n"
|
|
|
|
else:
|
|
raise ConfigurationException(f"Invalid result format {result_format}")
|
|
|
|
return retval
|