summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Friesel <daniel.friesel@uos.de>2020-09-11 11:55:46 +0200
committerDaniel Friesel <daniel.friesel@uos.de>2020-09-11 11:55:46 +0200
commit5af6a1a0f32ca7c75c731b0e84cf9b234cdb757d (patch)
treec3eeaf01f4b1af466311c6e796b59079072cdbee
parentb2325314cc3a689a7bbc0e557aa41b47652eb6cb (diff)
add analyze-kconfig script
-rwxr-xr-xbin/analyze-kconfig.py95
-rw-r--r--lib/model.py48
2 files changed, 127 insertions, 16 deletions
diff --git a/bin/analyze-kconfig.py b/bin/analyze-kconfig.py
new file mode 100755
index 0000000..2f2e973
--- /dev/null
+++ b/bin/analyze-kconfig.py
@@ -0,0 +1,95 @@
+#!/usr/bin/env python3
+
+"""analyze-kconfig - Generate a model for KConfig selections
+
+analyze-kconfig builds a model determining system attributes
+(e.g. ROM or RAM usage) based on KConfig configuration variables.
+Only boolean variables are supported at the moment.
+"""
+
+import argparse
+import json
+import kconfiglib
+import logging
+
+from dfatool.loader import KConfigAttributes
+from dfatool.model import KConfigModel
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ formatter_class=argparse.RawDescriptionHelpFormatter, description=__doc__
+ )
+ parser.add_argument(
+ "--export-tree", type=str, help="Export decision tree model to file"
+ )
+ parser.add_argument(
+ "--config", type=str, help="Show model results for symbols in .config file"
+ )
+ parser.add_argument(
+ "--attribute", choices=["rom", "ram"], default="rom", help="Model attribute"
+ )
+ parser.add_argument("kconfig_path", type=str, help="Path to Kconfig file")
+ parser.add_argument("experiment_root", type=str, help="Path to experiment results")
+
+ args = parser.parse_args()
+
+ data = KConfigAttributes(args.kconfig_path, args.experiment_root)
+ model = KConfigModel(data, args.attribute)
+
+ if args.export_tree:
+ with open(args.export_tree, "w") as f:
+ json.dump(model.get_tree(), f)
+
+ if args.config:
+ kconf = kconfiglib.Kconfig(args.kconfig_path)
+ kconf.load_config(args.config)
+ print(f"Model result for .config: {model.value_for_config(kconf)}")
+
+ for symbol in model.symbols:
+ kconf2 = kconfiglib.Kconfig(args.kconfig_path)
+ kconf2.load_config(args.config)
+ kconf_sym = kconf2.syms[symbol]
+ if kconf_sym.tri_value == 0 and 2 in kconf_sym.assignable:
+ kconf_sym.set_value(2)
+ elif kconf_sym.tri_value == 2 and 0 in kconf_sym.assignable:
+ kconf_sym.set_value(0)
+ else:
+ continue
+
+ # specific to multipass:
+ # Do not suggest changes which affect the application
+ skip = False
+ num_changes = 0
+ changed_symbols = list()
+ for i, csymbol in enumerate(model.symbols):
+ if kconf.syms[csymbol].tri_value != kconf2.syms[csymbol].tri_value:
+ num_changes += 1
+ changed_symbols.append(csymbol)
+ if (
+ csymbol.startswith("app_")
+ and kconf.syms[csymbol].tri_value
+ != kconf2.syms[csymbol].tri_value
+ ):
+ skip = True
+ break
+ if skip:
+ continue
+
+ model_diff = model.value_for_config(kconf2) - model.value_for_config(kconf)
+ if kconf_sym.choice:
+ print(
+ f"Setting {kconf_sym.choice.name} to {kconf_sym.name} changes {num_changes:2d} symbols, model change: {model_diff:+5.0f}"
+ )
+ else:
+ print(
+ f"Setting {symbol} to {kconf_sym.str_value} changes {num_changes:2d} symbols, model change: {model_diff:+5.0f}"
+ )
+ for changed_symbol in changed_symbols:
+ print(
+ f" {changed_symbol:30s} -> {kconf2.syms[changed_symbol].str_value}"
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/lib/model.py b/lib/model.py
index a953c46..f7f8e5c 100644
--- a/lib/model.py
+++ b/lib/model.py
@@ -1158,6 +1158,8 @@ class PTAModel:
class KConfigModel:
+ """Model for a specific system attribute such as ROM or RAM usage"""
+
class Leaf:
def __init__(self, value, stddev):
self.value = value
@@ -1206,20 +1208,36 @@ class KConfigModel:
ret["y"] = None
return ret
- def __init__(self, kconfig_benchmark):
+ def __init__(self, kconfig_benchmark, attribute):
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)
+ if callable(attribute):
+ self.attribute = "custom"
+ self.attr_function = lambda x: attribute(x[1])
+ elif attribute == "rom":
+ self.attribute = "rom"
+ self.attr_function = lambda x: x[1]["total"]["ROM"]
+ elif attribute == "ram":
+ self.attribute = "ram"
+ self.attr_function = lambda x: x[1]["total"]["RAM"]
+ else:
+ raise ValueError("attribute must be a a function, 'rom', or 'ram'")
+ self.model = self.build_tree(self.symbols, self.data, 0)
+
+ def value_for_config(self, kconf):
+ return self.model.model(kconf)
- # with open("kconfigmodel.json", "w") as f:
- # json.dump(output, f)
+ def to_json(self):
+ output = {
+ "model": model.to_json(),
+ "symbols": self.symbols,
+ "attribute": self.attribute,
+ }
+ return output
- def get_min(self, this_symbols, this_data, level):
+ def build_tree(self, this_symbols, this_data, level):
- rom_sizes = list(map(lambda x: x[1]["total"]["ROM"], this_data))
+ rom_sizes = list(map(self.attr_function, this_data))
if np.std(rom_sizes) < 100 or len(this_symbols) == 0:
return self.Leaf(np.mean(rom_sizes), np.std(rom_sizes))
@@ -1229,10 +1247,8 @@ class KConfigModel:
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))
- )
+ enabled_std_rom = np.std(list(map(self.attr_function, enabled)))
+ disabled_std_rom = np.std(list(map(self.attr_function, disabled)))
children = [enabled_std_rom, disabled_std_rom]
if np.any(np.isnan(children)):
@@ -1260,12 +1276,12 @@ class KConfigModel:
disabled,
)
)
- print(
+ logger.debug(
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))
+ node.set_child_y(self.build_tree(new_symbols, enabled, level + 1))
if len(disabled):
- node.set_child_n(self.get_min(new_symbols, disabled, level + 1))
+ node.set_child_n(self.build_tree(new_symbols, disabled, level + 1))
return node