summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Friesel <daniel.friesel@uos.de>2020-09-10 16:02:32 +0200
committerDaniel Friesel <daniel.friesel@uos.de>2020-09-10 16:02:32 +0200
commit2546a93b8b2a3ecdea77bbf38332c4dd77d83239 (patch)
tree86006a0e64215789b2fb97cf3c5470e5749b03f1
parentd8bc1ccd39986f9b8af066636921f91667dc2492 (diff)
add kconfig benchmark loader and model generation
-rw-r--r--.gitlab-ci.yml4
-rw-r--r--lib/loader.py58
-rw-r--r--lib/model.py119
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