diff options
author | Mark Shannon <mark@hotpy.org> | 2022-02-09 12:30:26 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-02-09 12:30:26 (GMT) |
commit | f71a69aa9209cf67cc1060051b147d6afa379bba (patch) | |
tree | e5561ed03edb72403fbe09f69281182c8652439d /Tools | |
parent | 77bab59c8a1f04922bb975cc4f11e5323d1d379d (diff) | |
download | cpython-f71a69aa9209cf67cc1060051b147d6afa379bba.zip cpython-f71a69aa9209cf67cc1060051b147d6afa379bba.tar.gz cpython-f71a69aa9209cf67cc1060051b147d6afa379bba.tar.bz2 |
bpo-46072: Output stats as markdown with collapsible sections. (GH-31228)
Diffstat (limited to 'Tools')
-rw-r--r-- | Tools/scripts/summarize_stats.py | 256 |
1 files changed, 186 insertions, 70 deletions
diff --git a/Tools/scripts/summarize_stats.py b/Tools/scripts/summarize_stats.py index 1271c19..da0bab2 100644 --- a/Tools/scripts/summarize_stats.py +++ b/Tools/scripts/summarize_stats.py @@ -5,6 +5,7 @@ default stats folders. import collections import os.path import opcode +from datetime import date if os.name == "nt": DEFAULT_DIR = "c:\\temp\\py_stats\\" @@ -24,37 +25,53 @@ for name in opcode.opname[1:]: TOTAL = "specialization.deferred", "specialization.hit", "specialization.miss", "execution_count" -def print_specialization_stats(name, family_stats): +def print_specialization_stats(name, family_stats, defines): if "specializable" not in family_stats: return total = sum(family_stats.get(kind, 0) for kind in TOTAL) if total == 0: return - print(name+":") - for key in sorted(family_stats): - if key.startswith("specialization.failure_kinds"): - continue - if key.startswith("specialization."): + with Section(name, 3, f"specialization stats for {name} family"): + rows = [] + for key in sorted(family_stats): + if key.startswith("specialization.failure_kinds"): + continue + if key.startswith("specialization."): + label = key[len("specialization."):] + elif key == "execution_count": + label = "unquickened" + else: + label = key + if key not in ("specialization.success", "specialization.failure", "specializable"): + rows.append((f"{label:>12}", f"{family_stats[key]:>12}", f"{100*family_stats[key]/total:0.1f}%")) + emit_table(("Kind", "Count", "Ratio"), rows) + print_title("Specialization attempts", 4) + total_attempts = 0 + for key in ("specialization.success", "specialization.failure"): + total_attempts += family_stats.get(key, 0) + rows = [] + for key in ("specialization.success", "specialization.failure"): label = key[len("specialization."):] - elif key == "execution_count": - label = "unquickened" - if key not in ("specialization.success", "specialization.failure"): - print(f"{label:>12}:{family_stats[key]:>12} {100*family_stats[key]/total:0.1f}%") - for key in ("specialization.success", "specialization.failure"): - label = key[len("specialization."):] - print(f" {label}:{family_stats.get(key, 0):>12}") - total_failures = family_stats.get("specialization.failure", 0) - failure_kinds = [ 0 ] * 30 - for key in family_stats: - if not key.startswith("specialization.failure_kind"): - continue - _, index = key[:-1].split("[") - index = int(index) - failure_kinds[index] = family_stats[key] - for index, value in enumerate(failure_kinds): - if not value: - continue - print(f" kind {index:>2}: {value:>8} {100*value/total_failures:0.1f}%") + label = label[0].upper() + label[1:] + val = family_stats.get(key, 0) + rows.append((label, val, f"{100*val/total_attempts:0.1f}%")) + emit_table(("", "Count", "Ratio"), rows) + total_failures = family_stats.get("specialization.failure", 0) + failure_kinds = [ 0 ] * 30 + for key in family_stats: + if not key.startswith("specialization.failure_kind"): + continue + _, index = key[:-1].split("[") + index = int(index) + failure_kinds[index] = family_stats[key] + failures = [(value, index) for (index, value) in enumerate(failure_kinds)] + failures.sort(reverse=True) + rows = [] + for value, index in failures: + if not value: + continue + rows.append((kind_to_text(index, defines, name), value, f"{100*value/total_failures:0.1f}%")) + emit_table(("Failure kind", "Count", "Ratio"), rows) def gather_stats(): stats = collections.Counter() @@ -76,6 +93,31 @@ def extract_opcode_stats(stats): opcode_stats[int(n)][rest.strip(".")] = value return opcode_stats +def parse_kinds(spec_src): + defines = collections.defaultdict(list) + for line in spec_src: + line = line.strip() + if not line.startswith("#define SPEC_FAIL_"): + continue + line = line[len("#define SPEC_FAIL_"):] + name, val = line.split() + defines[int(val.strip())].append(name.strip()) + return defines + +def pretty(defname): + return defname.replace("_", " ").lower() + +def kind_to_text(kind, defines, opname): + if kind < 7: + return pretty(defines[kind][0]) + if opname.endswith("ATTR"): + opname = "ATTR" + if opname.endswith("SUBSCR"): + opname = "SUBSCR" + for name in defines[kind]: + if name.startswith(opname): + return pretty(name[len(opname)+1:]) + return "kind " + str(kind) def categorized_counts(opcode_stats): basic = 0 @@ -104,57 +146,131 @@ def categorized_counts(opcode_stats): basic += count return basic, not_specialized, specialized +def print_title(name, level=2): + print("#"*level, name) + print() + +class Section: + + def __init__(self, title, level=2, summary=None): + self.title = title + self.level = level + if summary is None: + self.summary = title.lower() + else: + self.summary = summary + + def __enter__(self): + print_title(self.title, self.level) + print("<details>") + print("<summary>", self.summary, "</summary>") + print() + return self + + def __exit__(*args): + print() + print("</details>") + print() + +def emit_table(header, rows): + width = len(header) + print("|", " | ".join(header), "|") + print("|", " | ".join(["---"]*width), "|") + for row in rows: + if width is not None and len(row) != width: + raise ValueError("Wrong number of elements in row '" + str(rows) + "'") + print("|", " | ".join(str(i) for i in row), "|") + print() + +def emit_execution_counts(opcode_stats, total): + with Section("Execution counts", summary="execution counts for all instructions"): + counts = [] + for i, opcode_stat in enumerate(opcode_stats): + if "execution_count" in opcode_stat: + count = opcode_stat['execution_count'] + miss = 0 + if "specializable" not in opcode_stat: + miss = opcode_stat.get("specialization.miss") + counts.append((count, opname[i], miss)) + counts.sort(reverse=True) + cumulative = 0 + rows = [] + for (count, name, miss) in counts: + cumulative += count + if miss: + miss = f"{100*miss/count:0.1f}%" + else: + miss = "" + rows.append((name, count, f"{100*count/total:0.1f}%", + f"{100*cumulative/total:0.1f}%", miss)) + emit_table( + ("Name", "Count", "Self", "Cumulative", "Miss ratio"), + rows + ) + + +def emit_specialization_stats(opcode_stats): + spec_path = os.path.join(os.path.dirname(__file__), "../../Python/specialize.c") + with open(spec_path) as spec_src: + defines = parse_kinds(spec_src) + with Section("Specialization stats", summary="specialization stats by family"): + for i, opcode_stat in enumerate(opcode_stats): + name = opname[i] + print_specialization_stats(name, opcode_stat, defines) + +def emit_specialization_overview(opcode_stats, total): + basic, not_specialized, specialized = categorized_counts(opcode_stats) + with Section("Specialization effectiveness"): + emit_table(("Instructions", "Count", "Ratio"), ( + ("Basic", basic, f"{basic*100/total:0.1f}%"), + ("Not specialized", not_specialized, f"{not_specialized*100/total:0.1f}%"), + ("Specialized", specialized, f"{specialized*100/total:0.1f}%"), + )) + +def emit_call_stats(stats): + with Section("Call stats", summary="Inlined calls and frame stats"): + total = 0 + for key, value in stats.items(): + if "Calls to" in key: + total += value + rows = [] + for key, value in stats.items(): + if "Calls to" in key: + rows.append((key, value, f"{100*value/total:0.1f}%")) + for key, value in stats.items(): + if key.startswith("Frame"): + rows.append((key, value, f"{100*value/total:0.1f}%")) + emit_table(("", "Count", "Ratio"), rows) + +def emit_object_stats(stats): + with Section("Object stats", summary="allocations, frees and dict materializatons"): + total = stats.get("Object new values") + rows = [] + for key, value in stats.items(): + if key.startswith("Object"): + if "materialize" in key: + materialize = f"{100*value/total:0.1f}%" + else: + materialize = "" + label = key[6:].strip() + label = label[0].upper() + label[1:] + rows.append((label, value, materialize)) + emit_table(("", "Count", "Ratio"), rows) + def main(): stats = gather_stats() opcode_stats = extract_opcode_stats(stats) - print("Execution counts:") - counts = [] total = 0 for i, opcode_stat in enumerate(opcode_stats): if "execution_count" in opcode_stat: - count = opcode_stat['execution_count'] - miss = 0 - if "specializable" not in opcode_stat: - miss = opcode_stat.get("specialization.miss") - counts.append((count, opname[i], miss)) - total += count - counts.sort(reverse=True) - cummulative = 0 - for (count, name, miss) in counts: - cummulative += count - print(f"{name}: {count} {100*count/total:0.1f}% {100*cummulative/total:0.1f}%") - if miss: - print(f" Misses: {miss} {100*miss/count:0.1f}%") - print("Specialization stats:") - for i, opcode_stat in enumerate(opcode_stats): - name = opname[i] - print_specialization_stats(name, opcode_stat) - basic, not_specialized, specialized = categorized_counts(opcode_stats) - print("Specialization effectiveness:") - print(f" Base instructions {basic} {basic*100/total:0.1f}%") - print(f" Not specialized {not_specialized} {not_specialized*100/total:0.1f}%") - print(f" Specialized {specialized} {specialized*100/total:0.1f}%") - print("Call stats:") - total = 0 - for key, value in stats.items(): - if "Calls to" in key: - total += value - for key, value in stats.items(): - if "Calls to" in key: - print(f" {key}: {value} {100*value/total:0.1f}%") - for key, value in stats.items(): - if key.startswith("Frame"): - print(f" {key}: {value} {100*value/total:0.1f}%") - print("Object stats:") - total = stats.get("Object new values") - for key, value in stats.items(): - if key.startswith("Object"): - if "materialize" in key: - print(f" {key}: {value} {100*value/total:0.1f}%") - else: - print(f" {key}: {value}") - total = 0 - + total += opcode_stat['execution_count'] + emit_execution_counts(opcode_stats, total) + emit_specialization_stats(opcode_stats) + emit_specialization_overview(opcode_stats, total) + emit_call_stats(stats) + emit_object_stats(stats) + print("---") + print("Stats gathered on:", date.today()) if __name__ == "__main__": main() |