Skip to content

Commit a7ca466

Browse files
committed
ci: workflow to surface heavy Chromium includes
1 parent 949e4a7 commit a7ca466

2 files changed

Lines changed: 225 additions & 0 deletions

File tree

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
name: Heavy Chromium Includes
2+
3+
on:
4+
workflow_dispatch:
5+
schedule:
6+
- cron: '0 18 * * *'
7+
8+
permissions: {}
9+
10+
jobs:
11+
heavy_chromium_includes:
12+
name: Heavy Chromium Includes
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Checkout
16+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
17+
with:
18+
repository: dsanders11/chromium-include-cleanup
19+
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
20+
with:
21+
python-version: '3.12'
22+
cache: 'pip'
23+
- name: Install Dependencies
24+
run: pip install -r requirements.txt
25+
- name: Download Include Analysis Output
26+
run: |
27+
curl https://commondatastorage.googleapis.com/chromium-browser-clang/include-analysis.js > include-analysis.js
28+
- name: Find Heavy Include edges
29+
run: |
30+
python extract_include_analysis_edges.py --filter-generated-files --filter-third-party --weight-threshold 75000000 > heavy-includes.csv
31+
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
32+
with:
33+
name: heavy-includes
34+
path: heavy-includes.csv
35+
- run: npm install @actions/cache
36+
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
37+
with:
38+
script: |
39+
const fs = require('node:fs');
40+
const cache = require('@actions/cache');
41+
42+
const data = fs.readFileSync('./heavy-includes.csv', 'utf8').trim();
43+
44+
const includes = await Promise.all(data.split('\n').map(async (line, idx) => {
45+
const [filename, include, added_size, prevalence, expanded_size, centrality] = line.trim().split(',');
46+
const addedSize = parseInt(added_size);
47+
48+
// Check if this is known from a previous run
49+
const cacheKey = `heavy-chromium-include-${filename}-${include}`;
50+
const cacheHit =
51+
(await cache.restoreCache(['/dev/null'], cacheKey, undefined, {
52+
lookupOnly: true,
53+
})) !== undefined;
54+
55+
if (!cacheHit) {
56+
// Create a cache entry (only the name matters) to keep track of
57+
// includes we've seen from previous runs to mark them as stale
58+
await cache.saveCache(['/dev/null'], cacheKey);
59+
}
60+
61+
return [
62+
`${filename} --> ${include.replace(/</g, '&lt;').replace(/>/g, '&gt;')}`,
63+
addedSize,
64+
parseFloat(prevalenceData[idx].trim().split(',')[4]),
65+
parseInt(expandedSizeData[idx].trim().split(',')[4]),
66+
parseFloat(centralityData[idx].trim().split(',')[4]),
67+
cacheHit,
68+
];
69+
}));
70+
71+
const addTable = (includes) => {
72+
core.summary.addTable([
73+
[
74+
{ data: 'Include Edge', header: true },
75+
{ data: 'Added Size', header: true },
76+
{ data: 'Prevalence', header: true },
77+
{ data: 'Expanded Size', header: true },
78+
{ data: 'Centrality', header: true },
79+
],
80+
// Sort by added size, then convert it back to string or it won't render
81+
...includes
82+
.sort((a, b) => b[1] - a[1])
83+
.map(([edge, added_size, prevalence, expandedSize, centrality]) => [
84+
edge,
85+
added_size.toLocaleString(),
86+
`${prevalence.toFixed(2)}%`,
87+
expandedSize.toLocaleString(),
88+
centrality.toFixed(5),
89+
]),
90+
]);
91+
}
92+
93+
core.summary.addHeading('🔗 Heavy Chromium Includes');
94+
core.summary.addRaw(`Found ${includes.length} unused includes over 75 MB`);
95+
96+
const newlySeen = unusedIncludes.filter(([, , , , , cacheHit]) => !cacheHit)
97+
if (newlySeen.length > 0) {
98+
core.summary.addHeading('Not Seen Before', '2');
99+
addTable(newlySeen);
100+
core.summary.addHeading('All Includes', '2');
101+
} else {
102+
core.summary.addBreak();
103+
}
104+
105+
addTable(unusedIncludes);
106+
107+
await core.summary.write();

extract_include_analysis_edges.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
#!/usr/bin/env python3
2+
3+
import argparse
4+
import csv
5+
import logging
6+
import os
7+
import re
8+
import sys
9+
import urllib.request
10+
from datetime import datetime
11+
12+
from include_analysis import IncludeAnalysisOutput, ParseError, parse_raw_include_analysis_output
13+
from suggest_include_changes import filter_filenames
14+
from utils import (
15+
get_include_analysis_edge_expanded_sizes,
16+
get_include_analysis_edge_prevalence,
17+
get_include_analysis_edges_centrality,
18+
get_latest_include_analysis,
19+
)
20+
21+
22+
def extract_include_analysis_edges(
23+
include_analysis: IncludeAnalysisOutput,
24+
weight_threshold=None,
25+
filter_generated_files=False,
26+
filter_mojom_headers=False,
27+
filter_third_party=False,
28+
):
29+
filenames = filter_filenames(
30+
include_analysis["files"],
31+
filter_generated_files=filter_generated_files,
32+
filter_mojom_headers=filter_mojom_headers,
33+
filter_third_party=filter_third_party,
34+
)
35+
36+
expanded_size_edge_weights = get_include_analysis_edge_expanded_sizes(include_analysis)
37+
prevalence_edge_weights = get_include_analysis_edge_prevalence(include_analysis)
38+
centrality_edge_weights = get_include_analysis_edges_centrality(include_analysis)
39+
40+
for file in filenames:
41+
for [include, size] in include_analysis["esizes"][file].items():
42+
if weight_threshold and float(size) < weight_threshold:
43+
continue
44+
45+
prevalence = expanded_size_edge_weights[file][include]
46+
expanded_size = expanded_size_edge_weights[file][include]
47+
centrality = centrality_edge_weights[file][include]
48+
49+
yield file, include, size, prevalence, expanded_size, centrality
50+
51+
52+
def main():
53+
parser = argparse.ArgumentParser(description="Extract include edges from include analysis, with filtering")
54+
parser.add_argument(
55+
"include_analysis_output",
56+
type=argparse.FileType("r"),
57+
nargs="?",
58+
help="The include analysis output to use.",
59+
)
60+
parser.add_argument(
61+
"--weight-threshold", type=float, help="Filter out changes with a weight value below the threshold."
62+
)
63+
parser.add_argument(
64+
"--filter-third-party", action="store_true", help="Filter out third_party/ (excluding blink) and v8."
65+
)
66+
parser.add_argument("--filter-generated-files", action="store_true", help="Filter out generated files.")
67+
parser.add_argument("--filter-mojom-headers", action="store_true", help="Filter out mojom headers.")
68+
group = parser.add_mutually_exclusive_group()
69+
group.add_argument("--quiet", action="store_true", default=False, help="Only log warnings and errors.")
70+
group.add_argument("--verbose", action="store_true", default=False, help="Enable verbose logging.")
71+
args = parser.parse_args()
72+
73+
logging.basicConfig(
74+
format="%(asctime)s - %(levelname)s - %(message)s",
75+
level=logging.DEBUG if args.verbose else logging.WARNING if args.quiet else logging.INFO,
76+
)
77+
78+
# If the user specified an include analysis output file, use that instead of fetching it
79+
if args.include_analysis_output:
80+
raw_include_analysis = args.include_analysis_output.read()
81+
else:
82+
raw_include_analysis = get_latest_include_analysis()
83+
84+
try:
85+
include_analysis = parse_raw_include_analysis_output(raw_include_analysis)
86+
except ParseError as e:
87+
message = str(e)
88+
print("error: Could not parse include analysis output file")
89+
if message:
90+
print(message)
91+
return 2
92+
93+
csv_writer = csv.writer(sys.stdout)
94+
95+
try:
96+
for row in extract_include_analysis_edges(
97+
include_analysis,
98+
weight_threshold=args.weight_threshold,
99+
filter_generated_files=args.filter_generated_files,
100+
filter_mojom_headers=args.filter_mojom_headers,
101+
filter_third_party=args.filter_third_party,
102+
):
103+
csv_writer.writerow(row)
104+
105+
sys.stdout.flush()
106+
except BrokenPipeError:
107+
devnull = os.open(os.devnull, os.O_WRONLY)
108+
os.dup2(devnull, sys.stdout.fileno())
109+
sys.exit(1)
110+
111+
return 0
112+
113+
114+
if __name__ == "__main__":
115+
try:
116+
sys.exit(main())
117+
except KeyboardInterrupt:
118+
pass # Don't show the user anything

0 commit comments

Comments
 (0)