diff options
author | Daniel Friesel <daniel.friesel@uos.de> | 2020-09-11 11:55:46 +0200 |
---|---|---|
committer | Daniel Friesel <daniel.friesel@uos.de> | 2020-09-11 11:55:46 +0200 |
commit | 5af6a1a0f32ca7c75c731b0e84cf9b234cdb757d (patch) | |
tree | c3eeaf01f4b1af466311c6e796b59079072cdbee | |
parent | b2325314cc3a689a7bbc0e557aa41b47652eb6cb (diff) |
add analyze-kconfig script
-rwxr-xr-x | bin/analyze-kconfig.py | 95 | ||||
-rw-r--r-- | lib/model.py | 48 |
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 |