summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xbin/analyze-log.py13
-rwxr-xr-xbin/workload.py66
-rw-r--r--lib/behaviour.py77
-rw-r--r--lib/cli.py23
-rw-r--r--lib/model.py32
-rw-r--r--lib/parameters.py4
-rw-r--r--lib/utils.py14
7 files changed, 167 insertions, 62 deletions
diff --git a/bin/analyze-log.py b/bin/analyze-log.py
index 0b66bdf..50b5648 100755
--- a/bin/analyze-log.py
+++ b/bin/analyze-log.py
@@ -301,7 +301,7 @@ def main():
if args.export_dot:
dfatool.cli.export_dot(model, args.export_dot)
- if args.export_dref:
+ if args.export_dref or args.export_pseudo_dref:
dref = model.to_dref(
static_quality,
lut_quality,
@@ -321,9 +321,14 @@ def main():
mutual_information[param]
)
- dfatool.cli.export_dataref(
- args.export_dref, dref, precision=args.dref_precision
- )
+ if args.export_pseudo_dref:
+ dfatool.cli.export_pseudo_dref(
+ args.export_pseudo_dref, dref, precision=args.dref_precision
+ )
+ if args.export_dref:
+ dfatool.cli.export_dataref(
+ args.export_dref, dref, precision=args.dref_precision
+ )
if args.export_json:
with open(args.export_json, "w") as f:
diff --git a/bin/workload.py b/bin/workload.py
index ee2df0d..72b66bb 100755
--- a/bin/workload.py
+++ b/bin/workload.py
@@ -6,6 +6,7 @@ import logging
import sys
import dfatool.cli
import dfatool.utils
+from dfatool.behaviour import EventSequenceModel
from dfatool.model import AnalyticModel
@@ -39,6 +40,11 @@ def main():
type=str,
help="Path to model file (.json or .json.xz)",
)
+ parser.add_argument(
+ "--use-lut",
+ action="store_true",
+ help="Use LUT rather than performance model for prediction",
+ )
parser.add_argument("event", nargs="+", type=str)
args = parser.parse_args()
@@ -61,58 +67,18 @@ def main():
if args.info:
for i in range(len(models)):
print(f"""{args.models[i]}: {" ".join(models[i].parameters)}""")
+ _, param_info = models[i].get_fitted()
for name in models[i].names:
for attr in models[i].attributes(name):
- print(f" {name}.{attr}")
-
- aggregate = args.aggregate_init
- for event in args.event:
-
- event_normalizer = lambda p: p
- if "/" in event:
- v1, v2 = event.split("/")
- if dfatool.utils.is_numeric(v1):
- event = v2.strip()
- event_normalizer = lambda p: dfatool.utils.soft_cast_float(v1) / p
- elif dfatool.utils.is_numeric(v2):
- event = v1.strip()
- event_normalizer = lambda p: p / dfatool.utils.soft_cast_float(v2)
- else:
- raise RuntimeError(f"Cannot parse '{event}'")
-
- nn, param = event.split("(")
- name, action = nn.split(".")
- param_model = None
- ref_model = None
- for model in models:
- if name in model.names and action in model.attributes(name):
- ref_model = model
- param_model, param_info = model.get_fitted()
- break
- assert param_model is not None
- param = param.removesuffix(")")
- if param == "":
- param = dict()
- else:
- param = dfatool.utils.parse_conf_str(param)
-
- param_list = dfatool.utils.param_dict_to_list(param, ref_model.parameters)
-
- if not param_info(name, action).is_predictable(param_list):
- logging.warning(
- f"Cannot predict {name}.{action}({param}), falling back to static model"
- )
-
- event_output = event_normalizer(
- param_model(
- name,
- action,
- param=param_list,
- )
- )
-
- if args.aggregate == "sum":
- aggregate += event_output
+ print(f" {name}.{attr} {param_info(name, attr)}")
+
+ workload = EventSequenceModel(models)
+ aggregate = workload.eval_strs(
+ args.event,
+ aggregate=args.aggregate,
+ aggregate_init=args.aggregate_init,
+ use_lut=args.use_lut,
+ )
if args.normalize_output:
sf = dfatool.cli.parse_shift_function(
diff --git a/lib/behaviour.py b/lib/behaviour.py
new file mode 100644
index 0000000..402ddc7
--- /dev/null
+++ b/lib/behaviour.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python3
+
+import logging
+from . import utils
+
+logger = logging.getLogger(__name__)
+
+
+class EventSequenceModel:
+ def __init__(self, models):
+ self.models = models
+
+ def _event_normalizer(self, event):
+ event_normalizer = lambda p: p
+ if "/" in event:
+ v1, v2 = event.split("/")
+ if utils.is_numeric(v1):
+ event = v2.strip()
+ event_normalizer = lambda p: utils.soft_cast_float(v1) / p
+ elif utils.is_numeric(v2):
+ event = v1.strip()
+ event_normalizer = lambda p: p / utils.soft_cast_float(v2)
+ else:
+ raise RuntimeError(f"Cannot parse '{event}'")
+ return event, event_normalizer
+
+ def eval_strs(self, events, aggregate="sum", aggregate_init=0, use_lut=False):
+ for event in events:
+ event, event_normalizer = self._event_normalizer(event)
+ nn, param = event.split("(")
+ name, action = nn.split(".")
+ param_model = None
+ ref_model = None
+
+ for model in self.models:
+ if name in model.names and action in model.attributes(name):
+ ref_model = model
+ if use_lut:
+ param_model = model.get_param_lut(allow_none=True)
+ else:
+ param_model, param_info = model.get_fitted()
+ break
+
+ if param_model is None:
+ raise RuntimeError(f"Did not find a model for {name}.{action}")
+
+ param = param.removesuffix(")")
+ if param == "":
+ param = dict()
+ else:
+ param = utils.parse_conf_str(param)
+
+ param_list = utils.param_dict_to_list(param, ref_model.parameters)
+
+ if not use_lut and not param_info(name, action).is_predictable(param_list):
+ logging.warning(
+ f"Cannot predict {name}.{action}({param}), falling back to static model"
+ )
+
+ try:
+ event_output = event_normalizer(
+ param_model(
+ name,
+ action,
+ param=param_list,
+ )
+ )
+ except KeyError:
+ logging.error(f"Cannot predict {name}.{action}({param}) from LUT model")
+ raise
+
+ if aggregate == "sum":
+ aggregate_init += event_output
+ else:
+ raise RuntimeError(f"Unknown aggregate type: {aggregate}")
+
+ return aggregate_init
diff --git a/lib/cli.py b/lib/cli.py
index 75f6890..b2358ec 100644
--- a/lib/cli.py
+++ b/lib/cli.py
@@ -331,6 +331,23 @@ def model_quality_table(
print(buf)
+def export_pseudo_dref(dref_file, dref, precision=None):
+ with open(dref_file, "w") as f:
+ for k, v in sorted(os.environ.items(), key=lambda kv: kv[0]):
+ if k.startswith("DFATOOL_"):
+ print(f"% {k}='{v}'", file=f)
+ for arg in sys.argv:
+ print(f"% {arg}", file=f)
+ for k, v in sorted(dref.items()):
+ k = k.replace("/", "I").replace("-", "").replace(" ", "")
+ if type(v) is tuple:
+ v = v[0]
+ if type(v) in (float, np.float64) and precision is not None:
+ print("\\def\\" + k + "{" + f"{v:.{precision}f}" + "}", file=f)
+ else:
+ print("\\def\\" + k + "{" + str(v) + "}", file=f)
+
+
def export_dataref(dref_file, dref, precision=None):
with open(dref_file, "w") as f:
for k, v in sorted(os.environ.items(), key=lambda kv: kv[0]):
@@ -493,6 +510,12 @@ def add_standard_arguments(parser):
help="Export tree-based model to {PREFIX}{name}-{attribute}.dot",
)
parser.add_argument(
+ "--export-pseudo-dref",
+ metavar="FILE",
+ type=str,
+ help="Export model and model quality to LaTeX def file (sort of like dataref)",
+ )
+ parser.add_argument(
"--export-dref",
metavar="FILE",
type=str,
diff --git a/lib/model.py b/lib/model.py
index 0026249..dbe05aa 100644
--- a/lib/model.py
+++ b/lib/model.py
@@ -20,6 +20,7 @@ from .utils import (
by_name_to_by_param,
by_param_to_by_name,
regression_measures,
+ param_eq_or_none,
)
logger = logging.getLogger(__name__)
@@ -85,6 +86,7 @@ class AnalyticModel:
compute_stats=True,
force_tree=False,
max_std=None,
+ by_param=None,
from_json=None,
):
"""
@@ -154,9 +156,18 @@ class AnalyticModel:
for name, name_data in from_json["name"].items():
self.attr_by_name[name] = dict()
for attr, attr_data in name_data.items():
- self.attr_by_name[name][attr] = ModelAttribute.from_json(
- name, attr, attr_data
- )
+ if by_param:
+ self.attr_by_name[name][attr] = ModelAttribute.from_json(
+ name,
+ attr,
+ attr_data,
+ data_values=by_name[name][attr],
+ param_values=by_name[name]["param"],
+ )
+ else:
+ self.attr_by_name[name][attr] = ModelAttribute.from_json(
+ name, attr, attr_data
+ )
self.fit_done = True
return
@@ -255,7 +266,7 @@ class AnalyticModel:
return static_model_getter
- def get_param_lut(self, use_mean=False, fallback=False):
+ def get_param_lut(self, use_mean=False, fallback=False, allow_none=False):
"""
Get parameter-look-up-table model function: name, attribute, parameter values -> model value.
@@ -285,7 +296,16 @@ class AnalyticModel:
try:
return lut_model[name][key][param]
except KeyError:
- if fallback:
+ if allow_none:
+ keys = filter(
+ lambda p: param_eq_or_none(param, p),
+ lut_model[name][key].keys(),
+ )
+ values = list(map(lambda p: lut_model[name][key][p], keys))
+ if not values:
+ raise
+ return np.mean(values)
+ elif fallback:
return static_model[name][key]
raise
params = kwargs["params"]
@@ -684,7 +704,7 @@ class AnalyticModel:
for (nk, pk), v in data["byParam"]:
by_param[(nk, tuple(pk))] = v
by_name = by_param_to_by_name(by_param)
- return cls(by_name, data["parameters"], from_json=data)
+ return cls(by_name, data["parameters"], by_param=by_param, from_json=data)
else:
assert data["parameters"] == parameters
return cls(by_name, parameters, from_json=data)
diff --git a/lib/parameters.py b/lib/parameters.py
index b648c4c..acb044c 100644
--- a/lib/parameters.py
+++ b/lib/parameters.py
@@ -731,11 +731,11 @@ class ModelAttribute:
return self.mutual_information_cache
@classmethod
- def from_json(cls, name, attr, data):
+ def from_json(cls, name, attr, data, data_values=None, param_values=None):
param_names = data["paramNames"]
arg_count = data["argCount"]
- self = cls(name, attr, None, None, param_names, arg_count)
+ self = cls(name, attr, data_values, param_values, param_names, arg_count)
self.model_function = df.ModelFunction.from_json(data["modelFunction"])
self.mean = self.model_function.value
diff --git a/lib/utils.py b/lib/utils.py
index 208db44..ae0e3f7 100644
--- a/lib/utils.py
+++ b/lib/utils.py
@@ -48,6 +48,8 @@ def running_mean(x: np.ndarray, N: int) -> np.ndarray:
def human_readable(value, unit):
+ if value is None:
+ return value
for prefix, factor in (
("p", 1e-12),
("n", 1e-9),
@@ -207,6 +209,18 @@ def param_slice_eq(a, b, index):
return False
+def param_eq_or_none(a, b):
+ """
+ Check if by_param keys a and b are identical, allowing a None in a to match any key in b.
+ """
+ set_keys = tuple(filter(lambda i: a[i] is not None, range(len(a))))
+ a_not_none = tuple(map(lambda i: a[i], set_keys))
+ b_not_none = tuple(map(lambda i: b[i], set_keys))
+ if a_not_none == b_not_none:
+ return True
+ return False
+
+
def match_parameter_values(input_param: dict, match_param: dict):
"""
Check whether one of the paramaters in `input_param` has the same value in `match_param`.