summaryrefslogtreecommitdiffstats
path: root/Tools
diff options
context:
space:
mode:
authorMark Shannon <mark@hotpy.org>2022-02-09 12:30:26 (GMT)
committerGitHub <noreply@github.com>2022-02-09 12:30:26 (GMT)
commitf71a69aa9209cf67cc1060051b147d6afa379bba (patch)
treee5561ed03edb72403fbe09f69281182c8652439d /Tools
parent77bab59c8a1f04922bb975cc4f11e5323d1d379d (diff)
downloadcpython-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.py256
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()