diff options
Diffstat (limited to 'bin/analyze-trace.py')
-rwxr-xr-x | bin/analyze-trace.py | 129 |
1 files changed, 110 insertions, 19 deletions
diff --git a/bin/analyze-trace.py b/bin/analyze-trace.py index 9aa72d8..d7f45e7 100755 --- a/bin/analyze-trace.py +++ b/bin/analyze-trace.py @@ -39,25 +39,48 @@ def learn_pta(observations, annotation, delta=dict(), delta_param=dict()): prev = "__init__" prev_non_kernel = prev meta_observations = list() + n_seen = dict() + + total_latency_us = 0 if annotation.kernels: + # ggf. als dict of tuples, für den Fall dass Schleifen verschieden iterieren können? for i in range(prev_i, annotation.kernels[0].offset): this = observations[i]["name"] + " @ " + observations[i]["place"] + if this in n_seen: + if n_seen[this] == 1: + logging.debug( + f"Loop found in {annotation.start.name} {annotation.end.param}: {this} ⟳" + ) + n_seen[this] += 1 + else: + n_seen[this] = 1 + if not prev in delta: delta[prev] = set() delta[prev].add(this) + + # annotation.start.param may be incomplete, for instance in cases + # where DPUs are allocated before the input file is loadeed (and + # thus before the problem size is known). + # Hence, we must use annotation.end.param whenever we deal + # with possibly problem size-dependent behaviour. if not (prev, this) in delta_param: delta_param[(prev, this)] = set() delta_param[(prev, this)].add( - dfatool.utils.param_dict_to_str(annotation.start.param) + dfatool.utils.param_dict_to_str(annotation.end.param) ) + prev = this prev_i = i + 1 + + total_latency_us += observations[i]["attribute"].get("latency_us", 0) + meta_observations.append( { "name": f"__trace__ {this}", - "param": annotation.start.param, + "param": annotation.end.param, "attribute": dict( filter( lambda kv: not kv[0].startswith("e_"), @@ -76,17 +99,30 @@ def learn_pta(observations, annotation, delta=dict(), delta_param=dict()): if not prev in delta: delta[prev] = set() delta[prev].add(this) + if not (prev, this) in delta_param: delta_param[(prev, this)] = set() delta_param[(prev, this)].add( - dfatool.utils.param_dict_to_str(annotation.start.param) + dfatool.utils.param_dict_to_str(annotation.end.param) ) + + # The last iteration (next block) contains a single kernel, + # so we do not increase total_latency_us here. + # However, this means that we will only ever get one latency + # value for each set of kernels with a common problem size, + # despite potentially having far more data at our fingertips. + # We could provide one total_latency_us for each kernel + # (by combining start latency + kernel latency + teardown latency), + # but for that we first need to distinguish between kernel + # components and teardown components in the following block. + prev = this prev_i = i + 1 + meta_observations.append( { "name": f"__trace__ {this}", - "param": annotation.start.param, + "param": annotation.end.param, "attribute": dict( filter( lambda kv: not kv[0].startswith("e_"), @@ -101,19 +137,33 @@ def learn_pta(observations, annotation, delta=dict(), delta_param=dict()): for i in range(prev_i, annotation.end.offset): this = observations[i]["name"] + " @ " + observations[i]["place"] + if this in n_seen: + if n_seen[this] == 1: + logging.debug( + f"Loop found in {annotation.start.name} {annotation.end.param}: {this} ⟳" + ) + n_seen[this] += 1 + else: + n_seen[this] = 1 + if not prev in delta: delta[prev] = set() delta[prev].add(this) + if not (prev, this) in delta_param: delta_param[(prev, this)] = set() delta_param[(prev, this)].add( - dfatool.utils.param_dict_to_str(annotation.start.param) + dfatool.utils.param_dict_to_str(annotation.end.param) ) + + total_latency_us += observations[i]["attribute"].get("latency_us", 0) + prev = this + meta_observations.append( { "name": f"__trace__ {this}", - "param": annotation.start.param, + "param": annotation.end.param, "attribute": dict( filter( lambda kv: not kv[0].startswith("e_"), @@ -129,9 +179,32 @@ def learn_pta(observations, annotation, delta=dict(), delta_param=dict()): if not (prev, "__end__") in delta_param: delta_param[(prev, "__end__")] = set() delta_param[(prev, "__end__")].add( - dfatool.utils.param_dict_to_str(annotation.start.param) + dfatool.utils.param_dict_to_str(annotation.end.param) + ) + + for transition, count in n_seen.items(): + meta_observations.append( + { + "name": f"__loop__ {transition}", + "param": annotation.end.param, + "attribute": {"n_iterations": count}, + } + ) + + if total_latency_us: + meta_observations.append( + { + "name": annotation.start.name, + "param": annotation.end.param, + "attribute": {"latency_us": total_latency_us}, + } + ) + + is_loop = dict( + map(lambda kv: (kv[0], True), filter(lambda kv: kv[1] > 1, n_seen.items())) ) - return delta, delta_param, meta_observations + + return delta, delta_param, meta_observations, is_loop def join_annotations(ref, base, new): @@ -172,18 +245,20 @@ def main(): delta_by_name = dict() delta_param_by_name = dict() + is_loop = dict() for annotation in annotations: am_tt_param_names = sorted(annotation.start.param.keys()) if annotation.name not in delta_by_name: delta_by_name[annotation.name] = dict() delta_param_by_name[annotation.name] = dict() - _, _, meta_obs = learn_pta( + _, _, meta_obs, _is_loop = learn_pta( observations, annotation, delta_by_name[annotation.name], delta_param_by_name[annotation.name], ) observations += meta_obs + is_loop.update(_is_loop) def format_guard(guard): return "∧".join(map(lambda kv: f"{kv[0]}={kv[1]}", guard)) @@ -216,7 +291,13 @@ def main(): i_to_transition[i] = t_to am = AnalyticModel(am_tt_by_name, am_tt_param_names, force_tree=True) model, info = am.get_fitted() - flat_model = info(name, t_from).flatten() + if type(info(name, t_from)) is df.SplitFunction: + flat_model = info(name, t_from).flatten() + else: + flat_model = list() + logging.warning( + f"Model for {name} {t_from} is {info(name, t_from)}, expected SplitFunction" + ) for prefix, output in flat_model: transition_name = i_to_transition[int(output)] @@ -229,9 +310,14 @@ def main(): delta_param_sets.append(delta_params) to_names.append(t_to) n_confs = len(delta_params) - print( - f"{name} {t_from} → {t_to} ({' ∨ '.join(map(format_guard, transition_guard.get(t_to, list()))) or '⊤'})" - ) + if is_loop.get(t_from, False) and is_loop.get(t_to, False): + print(f"{name} {t_from} → {t_to} ⟳") + elif is_loop.get(t_from, False): + print(f"{name} {t_from} → {t_to} →") + else: + print( + f"{name} {t_from} → {t_to} ({' ∨ '.join(map(format_guard, transition_guard.get(t_to, list()))) or '⊤'})" + ) for i in range(len(delta_param_sets)): for j in range(i + 1, len(delta_param_sets)): @@ -239,12 +325,17 @@ def main(): intersection = delta_param_sets[i].intersection( delta_param_sets[j] ) - logging.error( - f"Outbound transitions of <{t_from}> are not deterministic: <{to_names[i]}> and <{to_names[j]}> are both taken for {intersection}" - ) - raise RuntimeError( - f"Outbound transitions of <{t_from}> are not deterministic" - ) + if is_loop.get(t_from, False): + logging.debug( + f"Loop transition <{t_from}>: <{to_names[i]}> and <{to_names[j]}> are both taken for {intersection}" + ) + else: + logging.error( + f"Outbound transitions of <{t_from}> are not deterministic: <{to_names[i]}> and <{to_names[j]}> are both taken for {intersection}" + ) + raise RuntimeError( + f"Outbound transitions of <{t_from}> are not deterministic" + ) print("") |