diff options
author | Daniel Friesel <daniel.friesel@uos.de> | 2020-09-10 16:02:32 +0200 |
---|---|---|
committer | Daniel Friesel <daniel.friesel@uos.de> | 2020-09-10 16:02:32 +0200 |
commit | 2546a93b8b2a3ecdea77bbf38332c4dd77d83239 (patch) | |
tree | 86006a0e64215789b2fb97cf3c5470e5749b03f1 | |
parent | d8bc1ccd39986f9b8af066636921f91667dc2492 (diff) |
add kconfig benchmark loader and model generation
-rw-r--r-- | .gitlab-ci.yml | 4 | ||||
-rw-r--r-- | lib/loader.py | 58 | ||||
-rw-r--r-- | lib/model.py | 119 |
3 files changed, 168 insertions, 13 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c0b6c96..95d5dc2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -14,6 +14,8 @@ lint_python: run_tests: stage: test + variables: + GIT_SUBMODULE_STRATEGY: recursive script: - apt-get update -qy - apt-get install -y python3-dev python3-coverage python3-numpy python3-scipy python3-pytest python3-pytest-cov python3-sklearn python3-yaml python3-zbar wget @@ -30,7 +32,7 @@ run_tests: - wget -qO test-data/20190815_111745_nRF24_no-rx.json https://lib.finalrewind.org/energy-models/20190815_111745_nRF24_no-rx.json - wget -qO test-data/20190815_122531_nRF24_no-rx.json https://lib.finalrewind.org/energy-models/20190815_122531_nRF24_no-rx.json - pytest-3 --cov=lib - - python3-coverage html -i + - python3-coverage html artifacts: paths: - htmlcov/ diff --git a/lib/loader.py b/lib/loader.py index 57b3d30..3f0662e 100644 --- a/lib/loader.py +++ b/lib/loader.py @@ -10,6 +10,7 @@ import re import struct import tarfile import hashlib +import kconfiglib from multiprocessing import Pool from .utils import running_mean, soft_cast_int @@ -1090,7 +1091,7 @@ def _add_trace_data_to_aggregate(aggregate, key, element): def pta_trace_to_aggregate(traces, ignore_trace_indexes=[]): - u""" + """ Convert preprocessed DFA traces from peripherals/drivers to by_name aggregate for PTAModel. arguments: @@ -1296,7 +1297,7 @@ class EnergyTraceLog: return self._ts_to_index(timestamp, mid_index, right_index) def analyze_states(self, traces, offline_index: int): - u""" + """ Split log data into states and transitions and return duration, energy, and mean power for each element. :param traces: expected traces, needed to synchronize with the measurement. @@ -1630,7 +1631,7 @@ class MIMOSA: self.errors = list() def charge_to_current_nocal(self, charge): - u""" + """ Convert charge per 10µs (in pJ) to mean currents (in µA) without accounting for calibration. :param charge: numpy array of charges (pJ per 10µs) as returned by `load_data` or `load_file` @@ -1642,7 +1643,7 @@ class MIMOSA: return charge * ua_step def _load_tf(self, tf): - u""" + """ Load MIMOSA log data from an open `tarfile` instance. :param tf: `tarfile` instance @@ -1663,7 +1664,7 @@ class MIMOSA: return charges, triggers def load_data(self, raw_data): - u""" + """ Load MIMOSA log data from a MIMOSA log file passed as raw byte string :param raw_data: MIMOSA log file, passed as raw byte string @@ -1675,7 +1676,7 @@ class MIMOSA: return self._load_tf(tf) def load_file(self, filename): - u""" + """ Load MIMOSA log data from a MIMOSA log file :param filename: MIMOSA log file @@ -1686,7 +1687,7 @@ class MIMOSA: return self._load_tf(tf) def currents_nocal(self, charges): - u""" + """ Convert charges (pJ per 10µs) to mean currents without accounting for calibration. :param charges: numpy array of charges (pJ per 10µs) @@ -1743,7 +1744,7 @@ class MIMOSA: return trigidx def calibration_edges(self, currents): - u""" + """ Return start/stop indexes of calibration measurements. :param currents: uncalibrated currents as reported by MIMOSA. For best results, @@ -1780,7 +1781,7 @@ class MIMOSA: ) def calibration_function(self, charges, cal_edges): - u""" + """ Calculate calibration function from previously determined calibration edges. :param charges: raw charges from MIMOSA @@ -1870,7 +1871,7 @@ class MIMOSA: return calfunc, caldata def analyze_states(self, charges, trigidx, ua_func): - u""" + """ Split log data into states and transitions and return duration, energy, and mean power for each element. :param charges: raw charges (each element describes the charge in pJ transferred during 10 µs) @@ -1940,3 +1941,40 @@ class MIMOSA: previdx = idx is_state = not is_state return iterdata + + +class KConfigAttributes: + def __init__(self, kconfig_path, datadir): + experiments = list() + for direntry in os.listdir(datadir): + config_path = f"{datadir}/{direntry}/.config" + attr_path = f"{datadir}/{direntry}/attributes.json" + if os.path.exists(attr_path): + experiments.append((config_path, attr_path)) + + kconf = kconfiglib.Kconfig(kconfig_path) + + self.symbols = sorted( + map( + lambda sym: sym.name, + filter( + lambda sym: kconfiglib.TYPE_TO_STR[sym.type] == "bool", + kconf.syms.values(), + ), + ) + ) + + self.data = list() + + config_vectors = set() + + for config_path, attr_path in experiments: + kconf.load_config(config_path) + with open(attr_path, "r") as f: + attr = json.load(f) + + config_vector = tuple( + map(lambda sym: kconf.syms[sym].tri_value == 2, self.symbols) + ) + config_vectors.add(config_vector) + self.data.append((config_vector, attr)) diff --git a/lib/model.py b/lib/model.py index bb4a45b..a953c46 100644 --- a/lib/model.py +++ b/lib/model.py @@ -2,6 +2,7 @@ import logging import numpy as np +import kconfiglib from scipy import optimize from sklearn.metrics import r2_score from multiprocessing import Pool @@ -375,7 +376,7 @@ def _num_args_from_by_name(by_name): class AnalyticModel: - u""" + """ Parameter-aware analytic energy/data size/... model. Supports both static and parameter-based model attributes, and automatic detection of parameter-dependence. @@ -663,7 +664,7 @@ class AnalyticModel: class PTAModel: - u""" + """ Parameter-aware PTA-based energy model. Supports both static and parameter-based model attributes, and automatic detection of parameter-dependence. @@ -1154,3 +1155,117 @@ class PTAModel: np.array(model_state_energy_list), np.array(real_energy_list) ), } + + +class KConfigModel: + class Leaf: + def __init__(self, value, stddev): + self.value = value + self.stddev = stddev + + def model(self, kconf): + return self.value + + def __repr__(self): + return f"<Leaf({self.value}, {self.stddev})>" + + def to_json(self): + return {"value": self.value, "stddev": self.stddev} + + class Node: + def __init__(self, symbol): + self.symbol = symbol + self.child_n = None + self.child_y = None + + def set_child_n(self, child_node): + self.child_n = child_node + + def set_child_y(self, child_node): + self.child_y = child_node + + def model(self, kconf): + if kconf.syms[self.symbol].tri_value == 0 and self.child_n: + return self.child_n.model(kconf) + if kconf.syms[self.symbol].tri_value == 2 and self.child_y: + return self.child_y.model(kconf) + return None + + def __repr__(self): + return f"<Node(n={self.child_n}, y={self.child_y})>" + + def to_json(self): + ret = {"symbol": self.symbol} + if self.child_n: + ret["n"] = self.child_n.to_json() + else: + ret["n"] = None + if self.child_y: + ret["y"] = self.child_y.to_json() + else: + ret["y"] = None + return ret + + def __init__(self, kconfig_benchmark): + self.data = kconfig_benchmark.data + self.symbols = kconfig_benchmark.symbols + model = self.get_min(self.symbols, self.data, 0) + + output = {"model": model.to_json(), "symbols": self.symbols} + print(output) + + # with open("kconfigmodel.json", "w") as f: + # json.dump(output, f) + + def get_min(self, this_symbols, this_data, level): + + rom_sizes = list(map(lambda x: x[1]["total"]["ROM"], this_data)) + + if np.std(rom_sizes) < 100 or len(this_symbols) == 0: + return self.Leaf(np.mean(rom_sizes), np.std(rom_sizes)) + + mean_stds = list() + for i, param in enumerate(this_symbols): + enabled = list(filter(lambda vrr: vrr[0][i] == True, this_data)) + disabled = list(filter(lambda vrr: vrr[0][i] == False, this_data)) + + enabled_std_rom = np.std(list(map(lambda x: x[1]["total"]["ROM"], enabled))) + disabled_std_rom = np.std( + list(map(lambda x: x[1]["total"]["ROM"], disabled)) + ) + children = [enabled_std_rom, disabled_std_rom] + + if np.any(np.isnan(children)): + mean_stds.append(np.inf) + else: + mean_stds.append(np.mean(children)) + + symbol_index = np.argmin(mean_stds) + symbol = this_symbols[symbol_index] + enabled = list(filter(lambda vrr: vrr[0][symbol_index] == True, this_data)) + disabled = list(filter(lambda vrr: vrr[0][symbol_index] == False, this_data)) + + node = self.Node(symbol) + + new_symbols = this_symbols[:symbol_index] + this_symbols[symbol_index + 1 :] + enabled = list( + map( + lambda x: (x[0][:symbol_index] + x[0][symbol_index + 1 :], x[1]), + enabled, + ) + ) + disabled = list( + map( + lambda x: (x[0][:symbol_index] + x[0][symbol_index + 1 :], x[1]), + disabled, + ) + ) + print( + f"Level {level} split on {symbol} has {len(enabled)} children when enabled and {len(disabled)} children when disabled" + ) + if len(enabled): + node.set_child_y(self.get_min(new_symbols, enabled, level + 1)) + if len(disabled): + node.set_child_n(self.get_min(new_symbols, disabled, level + 1)) + + return node |