summaryrefslogtreecommitdiff
path: root/bin
diff options
context:
space:
mode:
Diffstat (limited to 'bin')
-rwxr-xr-xbin/analyze-config.py199
1 files changed, 199 insertions, 0 deletions
diff --git a/bin/analyze-config.py b/bin/analyze-config.py
new file mode 100755
index 0000000..197faf2
--- /dev/null
+++ b/bin/analyze-config.py
@@ -0,0 +1,199 @@
+#!/usr/bin/env python3
+"""
+analyze-config - generate NFP model from system config benchmarks
+
+analyze-connfig generates an NFP model from benchmarks with various system
+configs (.config entries generated from a common Kconfig definition).
+"""
+
+import argparse
+import json
+import kconfiglib
+import logging
+import os
+
+import numpy as np
+
+from dfatool.model import AnalyticModel
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ formatter_class=argparse.RawDescriptionHelpFormatter, description=__doc__
+ )
+ parser.add_argument(
+ "--log-level",
+ metavar="LEVEL",
+ choices=["debug", "info", "warning", "error"],
+ default="warning",
+ help="Set log level",
+ )
+ parser.add_argument("kconfig_file")
+ parser.add_argument("data_dir")
+
+ args = parser.parse_args()
+
+ if args.log_level:
+ numeric_level = getattr(logging, args.log_level.upper(), None)
+ if not isinstance(numeric_level, int):
+ print(f"Invalid log level: {args.log_level}", file=sys.stderr)
+ sys.exit(1)
+ logging.basicConfig(level=numeric_level)
+
+ experiments = list()
+
+ for direntry in os.listdir(args.data_dir):
+ if "Multipass" in direntry:
+ config_path = f"{args.data_dir}/{direntry}/.config"
+ attr_path = f"{args.data_dir}/{direntry}/attributes.json"
+ if os.path.exists(attr_path):
+ experiments.append((config_path, attr_path))
+
+ kconf = kconfiglib.Kconfig(args.kconfig_file)
+
+ symbols = sorted(
+ map(
+ lambda sym: sym.name,
+ filter(
+ lambda sym: kconfiglib.TYPE_TO_STR[sym.type] == "bool",
+ kconf.syms.values(),
+ ),
+ )
+ )
+
+ by_name = {
+ "multipass": {
+ "isa": "state",
+ "attributes": ["rom_usage", "ram_usage"],
+ "rom_usage": list(),
+ "ram_usage": list(),
+ "param": list(),
+ }
+ }
+ 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, symbols))
+ config_vectors.add(config_vector)
+ by_name["multipass"]["rom_usage"].append(attr["total"]["ROM"])
+ by_name["multipass"]["ram_usage"].append(attr["total"]["RAM"])
+ by_name["multipass"]["param"].append(config_vector)
+ data.append((config_vector, attr["total"]["ROM"], attr["total"]["RAM"]))
+
+ print(
+ "Processing {:d} unique configurations of {:d} total".format(
+ len(config_vectors), len(experiments)
+ )
+ )
+
+ print(
+ "std of all data: {:5.0f} Bytes".format(np.std(list(map(lambda x: x[1], data))))
+ )
+
+ model = AnalyticModel(by_name, symbols, compute_stats=False)
+
+ class DTreeLeaf:
+ def __init__(self, value, stddev):
+ self.value = value
+ self.stddev = stddev
+
+ def __repr__(self):
+ return f"<DTreeLeaf({self.value}, {self.stddev})>"
+
+ def to_json(self):
+ return {"value": self.value, "stddev": self.stddev}
+
+ class DTreeNode:
+ def __init__(self, symbol):
+ self.symbol = symbol
+ self.false_child = None
+ self.true_child = None
+
+ def set_false_child(self, child_node):
+ self.false_child = child_node
+
+ def set_true_child(self, child_node):
+ self.true_child = child_node
+
+ def __repr__(self):
+ return f"<DTreeNode({self.false_child}, {self.true_child})>"
+
+ def to_json(self):
+ ret = {"symbol": self.symbol}
+ if self.false_child:
+ ret["false"] = self.false_child.to_json()
+ else:
+ ret["false"] = None
+ if self.true_child:
+ ret["true"] = self.true_child.to_json()
+ else:
+ ret["true"] = None
+ return ret
+
+ def get_min(this_symbols, this_data, level):
+
+ rom_sizes = list(map(lambda x: x[1], this_data))
+
+ if np.std(rom_sizes) < 100 or len(this_symbols) == 0:
+ return DTreeLeaf(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], enabled)))
+ disabled_std_rom = np.std(list(map(lambda x: x[1], 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 = DTreeNode(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_true_child(get_min(new_symbols, enabled, level + 1))
+ if len(disabled):
+ node.set_false_child(get_min(new_symbols, disabled, level + 1))
+
+ return node
+
+ model = get_min(symbols, data, 0)
+
+ output = {"model": model.to_json(), "symbols": symbols}
+
+ with open("kconfigmodel.json", "w") as f:
+ json.dump(output, f)
+
+
+if __name__ == "__main__":
+ main()