Skip to content

Commit 491ac3f

Browse files
committed
feat(edge_stats): script to print stats about edges
1 parent 8c18da0 commit 491ac3f

1 file changed

Lines changed: 97 additions & 0 deletions

File tree

edge_stats.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#!/usr/bin/env python3
2+
3+
import argparse
4+
import os
5+
import sys
6+
from collections import Counter
7+
8+
from include_analysis import ParseError, load_include_analysis
9+
10+
11+
def main():
12+
parser = argparse.ArgumentParser(description="Output stats about edges in an include analysis")
13+
parser.add_argument(
14+
"include_analysis_output",
15+
type=str,
16+
nargs="?",
17+
help="The include analysis output to use (can be a file path or URL). If not specified, pulls the latest.",
18+
)
19+
parser.add_argument("--top", type=int, default=10, help="Number of top files to show (default: 10).")
20+
parser.add_argument("--verbose", action="store_true", default=False, help="Enable verbose logging.")
21+
args = parser.parse_args()
22+
23+
try:
24+
include_analysis = load_include_analysis(args.include_analysis_output)
25+
except ParseError as e:
26+
message = str(e)
27+
print("error: Could not parse include analysis output file")
28+
if message:
29+
print(message)
30+
return 2
31+
32+
roots = set(include_analysis["roots"])
33+
root_count = len(roots)
34+
35+
# Count total edges
36+
total_edges = sum(len(includes) for includes in include_analysis["includes"].values())
37+
total_edges_ex_gen = sum(
38+
len(includes) for includer, includes in include_analysis["includes"].items() if not includer.startswith("out/")
39+
)
40+
41+
# Count edges directly out of roots and track which files roots include
42+
edges_from_roots = 0
43+
edges_from_roots_ex_gen = 0
44+
root_includes_counter = Counter()
45+
root_includes_counter_ex_gen = Counter()
46+
47+
for root in roots:
48+
includes = include_analysis["includes"].get(root, [])
49+
edges_from_roots += len(includes)
50+
if not root.startswith("out/"):
51+
edges_from_roots_ex_gen += len(includes)
52+
for included in includes:
53+
root_includes_counter[included] += 1
54+
if not root.startswith("out/"):
55+
root_includes_counter_ex_gen[included] += 1
56+
57+
# Count edges directly out of headers (non-roots)
58+
edges_from_headers = 0
59+
edges_from_headers_ex_gen = 0
60+
for includer, includes in include_analysis["includes"].items():
61+
if includer not in roots:
62+
edges_from_headers += len(includes)
63+
if not includer.startswith("out/"):
64+
edges_from_headers_ex_gen += len(includes)
65+
66+
try:
67+
print(f"Total edges: {total_edges:,} ({total_edges_ex_gen:,} excluding generated including headers)")
68+
print(
69+
f"Edges directly out of roots: {edges_from_roots:,} ({edges_from_roots_ex_gen:,} excluding generated including headers)"
70+
)
71+
print(
72+
f"Edges directly out of headers: {edges_from_headers:,} ({edges_from_headers_ex_gen:,} excluding generated including headers)"
73+
)
74+
print()
75+
print(f"Top {args.top} Files Included By Roots:")
76+
77+
for filename, count in root_includes_counter.most_common(args.top):
78+
prevalence = (100.0 * count) / root_count
79+
count_ex_gen = root_includes_counter_ex_gen.get(filename, 0)
80+
print(
81+
f"* {filename} {count:,} ({prevalence:.2f}% prevalence) ({count_ex_gen:,} excluding generated including headers)"
82+
)
83+
84+
sys.stdout.flush()
85+
except BrokenPipeError:
86+
devnull = os.open(os.devnull, os.O_WRONLY)
87+
os.dup2(devnull, sys.stdout.fileno())
88+
sys.exit(1)
89+
90+
return 0
91+
92+
93+
if __name__ == "__main__":
94+
try:
95+
sys.exit(main())
96+
except KeyboardInterrupt:
97+
pass # Don't show the user anything

0 commit comments

Comments
 (0)