Skip to content

Commit 7cbd883

Browse files
committed
Add ModuleEventAllocMonitor diff table generation. Add running of Module Event Alloc Monitor to profiling.
1 parent 21c47aa commit 7cbd883

3 files changed

Lines changed: 1627 additions & 0 deletions

File tree

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
#! /usr/bin/env python3
2+
3+
import json
4+
import os
5+
import sys
6+
7+
METRIC_SPECS = [
8+
("TotalMemoryGrowth", "bytes"),
9+
("AvgRetained", "bytes"),
10+
("AvgDataProductSize", "bytes"),
11+
("AvgTempSize", "bytes"),
12+
("AvgNTemp", "count"),
13+
]
14+
15+
16+
def build_viewer_html(template_path, embedded_data, source_label):
17+
with open(template_path, encoding="utf-8") as template_file:
18+
content = template_file.read()
19+
20+
embedded_json = json.dumps(embedded_data).replace("</", "<\\/")
21+
22+
autoload_snippet = """
23+
<script>
24+
(function () {
25+
var embeddedData = %s;
26+
if (typeof loadFromObject === "function") {
27+
loadFromObject(embeddedData, %s);
28+
}
29+
})();
30+
</script>
31+
""" % (embedded_json, json.dumps(source_label))
32+
33+
if "</body>" in content:
34+
content = content.replace("</body>", autoload_snippet + "</body>", 1)
35+
return content
36+
37+
38+
def module_key(module):
39+
return "%s|%s" % (module.get("label", ""), module.get("type", ""))
40+
41+
42+
def diff_value(ib_value, pr_value):
43+
if isinstance(ib_value, (int, float)) and isinstance(pr_value, (int, float)):
44+
return pr_value - ib_value
45+
if isinstance(pr_value, (int, float)):
46+
return pr_value
47+
if isinstance(ib_value, (int, float)):
48+
return -ib_value
49+
return "N/A"
50+
51+
52+
def diff_from(metric_names, ib_data, pr_data, result):
53+
for metric in metric_names:
54+
ib_key = "%s IB" % metric
55+
pr_key = "%s PR" % metric
56+
ib_value = ib_data.get(metric, "N/A")
57+
pr_value = pr_data.get(metric, "N/A")
58+
result[ib_key] = ib_value
59+
result[pr_key] = pr_value
60+
result[metric + " diff"] = diff_value(ib_value, pr_value)
61+
62+
63+
def merge_report(report_map, report, key_hint):
64+
module = {
65+
"label": report.get("label", key_hint),
66+
"type": report.get("type", ""),
67+
}
68+
for metric_name, _unit in METRIC_SPECS:
69+
value = report.get(metric_name, "N/A")
70+
if isinstance(value, (int, float)):
71+
module[metric_name] = value
72+
73+
key = module_key(module)
74+
existing = report_map.get(key)
75+
if not existing:
76+
report_map[key] = module
77+
return
78+
79+
for metric_name, _unit in METRIC_SPECS:
80+
if isinstance(module.get(metric_name), (int, float)):
81+
existing[metric_name] = existing.get(metric_name, 0) + module[metric_name]
82+
83+
84+
def load_reports(path):
85+
with open(path, encoding="utf-8") as handle:
86+
data = json.load(handle)
87+
88+
memory_reports = data.get("memoryReports")
89+
if not isinstance(memory_reports, dict):
90+
raise ValueError('Expected top-level key "memoryReports" in %s' % path)
91+
92+
modules = {}
93+
for key_hint, report in memory_reports.items():
94+
if not isinstance(report, dict):
95+
continue
96+
merge_report(modules, report, key_hint)
97+
return modules
98+
99+
100+
def compute_total(modules, label):
101+
total = {"type": "Process", "label": label}
102+
for metric_name, _unit in METRIC_SPECS:
103+
total[metric_name] = sum(
104+
module.get(metric_name, 0)
105+
for module in modules.values()
106+
if isinstance(module.get(metric_name), (int, float))
107+
)
108+
return total
109+
110+
111+
def build_results(ib_modules, pr_modules, process_label):
112+
metric_names = [metric_name for metric_name, _unit in METRIC_SPECS]
113+
results = {
114+
"resources": [{"name": metric_name, "unit": unit} for metric_name, unit in METRIC_SPECS],
115+
"total": {"type": "Process", "label": process_label},
116+
"modules": [],
117+
}
118+
119+
diff_from(
120+
metric_names,
121+
compute_total(ib_modules, process_label),
122+
compute_total(pr_modules, process_label),
123+
results["total"],
124+
)
125+
126+
keys = sorted(set(ib_modules.keys()) | set(pr_modules.keys()))
127+
for key in keys:
128+
ib_module = ib_modules.get(key, {})
129+
pr_module = pr_modules.get(key, {})
130+
result = {
131+
"label": pr_module.get("label", ib_module.get("label", "")),
132+
"type": pr_module.get("type", ib_module.get("type", "")),
133+
}
134+
diff_from(metric_names, ib_module, pr_module, result)
135+
results["modules"].append(result)
136+
137+
return results
138+
139+
140+
def output_paths(pr_file):
141+
pr_realpath = os.path.realpath(pr_file)
142+
output_prefix = os.path.join(
143+
os.path.dirname(pr_realpath), "diff-" + os.path.basename(pr_realpath)
144+
)
145+
return output_prefix, output_prefix + ".html"
146+
147+
148+
def main(argv):
149+
if len(argv) != 3:
150+
print(
151+
"""Usage: moduleEventAllocMonitor-circles-diff.py IB_FILE PR_FILE
152+
Diff the memoryReports section of two moduleEventAllocMonitor JSON files and write JSON and HTML outputs next to PR_FILE."""
153+
)
154+
return 1
155+
156+
ib_file = argv[1]
157+
pr_file = argv[2]
158+
159+
try:
160+
ib_modules = load_reports(ib_file)
161+
pr_modules = load_reports(pr_file)
162+
except (OSError, ValueError, json.JSONDecodeError) as err:
163+
print("Error:", err)
164+
return 1
165+
166+
process_label = os.path.basename(os.path.realpath(pr_file))
167+
results = build_results(ib_modules, pr_modules, process_label)
168+
169+
dump_file, summary_file = output_paths(pr_file)
170+
with open(dump_file, "w", encoding="utf-8") as handle:
171+
json.dump(results, handle, indent=2)
172+
173+
template_file = os.path.join(
174+
os.path.dirname(os.path.realpath(__file__)), "module_event_alloc_monitor_viewer.html"
175+
)
176+
summary_html = build_viewer_html(
177+
template_path=template_file,
178+
embedded_data=results,
179+
source_label="embedded moduleEventAllocMonitor diff data",
180+
)
181+
with open(summary_file, "w", encoding="utf-8") as handle:
182+
handle.write(summary_html)
183+
184+
return 0
185+
186+
187+
if __name__ == "__main__":
188+
sys.exit(main(sys.argv))

0 commit comments

Comments
 (0)