Skip to content

Commit bdc7c35

Browse files
authored
Arm backend: Fix cycle timestamp conversion in ETDump Chrome traces (pytorch#24792)
### Summary Require a positive finite clock frequency for cycle timestamps. Convert start times and duration from cycles to microseconds when generating Chrome traces. ### Test plan Add Buck dependencies and visibility for the conversion test. Signed-off-by: Zingo Andersen <Zingo.Andersen@arm.com>
1 parent fa342fb commit bdc7c35

5 files changed

Lines changed: 156 additions & 7 deletions

File tree

‎backends/arm/scripts/BUCK‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Copyright 2026 Arm Limited and/or its affiliates.
2+
#
3+
# This source code is licensed under the BSD-style license found in the
4+
# LICENSE file in the root directory of this source tree.
5+
6+
load("@fbcode_macros//build_defs:build_file_migration.bzl", "fbcode_target")
7+
load("@fbsource//xplat/executorch/build:runtime_wrapper.bzl", "runtime")
8+
9+
oncall("executorch")
10+
11+
fbcode_target(
12+
_kind = runtime.python_library,
13+
name = "etdump_to_chrome_trace",
14+
srcs = ["etdump_to_chrome_trace.py"],
15+
deps = ["//executorch/devtools:lib"],
16+
)

‎backends/arm/scripts/etdump_to_chrome_trace.py‎

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import argparse
2323
import json
24+
import math
2425

2526
from executorch.devtools import Inspector
2627
from executorch.devtools.inspector import TimeScale
@@ -36,13 +37,26 @@ def main() -> None:
3637
default="ns",
3738
choices=[ts.value for ts in TimeScale],
3839
)
40+
parser.add_argument(
41+
"--clock_frequency_hz",
42+
type=float,
43+
help="Clock frequency used for timestamps when --source_time_scale=cycles.",
44+
)
3945
args = parser.parse_args()
4046

47+
cycle_timestamps = args.source_time_scale == "cycles"
48+
if cycle_timestamps and (
49+
args.clock_frequency_hz is None
50+
or not math.isfinite(args.clock_frequency_hz)
51+
or args.clock_frequency_hz <= 0
52+
):
53+
parser.error("Cycle timestamps require a positive finite --clock_frequency_hz")
54+
4155
inspector = Inspector(
4256
etdump_path=args.etdump_path,
4357
etrecord=args.etrecord_path,
4458
source_time_scale=TimeScale(args.source_time_scale),
45-
target_time_scale=TimeScale.US,
59+
target_time_scale=TimeScale.CYCLES if cycle_timestamps else TimeScale.US,
4660
)
4761

4862
trace_events = []
@@ -53,9 +67,11 @@ def main() -> None:
5367
"us": 1.0,
5468
"ms": 1000.0,
5569
"s": 1000_000.0,
56-
"cycles": 1.0,
70+
"cycles": 1_000_000.0 / args.clock_frequency_hz if cycle_timestamps else 1.0,
5771
}[args.source_time_scale]
5872

73+
duration_to_us = source_to_us if cycle_timestamps else 1.0
74+
5975
for block_idx, event_block in enumerate(inspector.event_blocks):
6076
tid_name = event_block.name
6177

@@ -73,19 +89,19 @@ def main() -> None:
7389
if event.perf_data is None or event.start_time is None:
7490
continue
7591

76-
durations_us = event.perf_data.raw
92+
durations = event.perf_data.raw
7793
start_times = event.start_time
7894

79-
for iter_idx, (start_time, duration_us) in enumerate(
80-
zip(start_times, durations_us)
95+
for iter_idx, (start_time, duration) in enumerate(
96+
zip(start_times, durations)
8197
):
8298
trace_events.append(
8399
{
84100
"name": event.name,
85101
"cat": event_block.name,
86102
"ph": "X",
87103
"ts": float(start_time) * source_to_us,
88-
"dur": float(duration_us),
104+
"dur": float(duration) * duration_to_us,
89105
"pid": 1,
90106
"tid": block_idx,
91107
"args": {
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Copyright 2026 Arm Limited and/or its affiliates.
2+
#
3+
# This source code is licensed under the BSD-style license found in the
4+
# LICENSE file in the root directory of this source tree.
5+
6+
import json
7+
import struct
8+
import sys
9+
10+
import pytest
11+
12+
from executorch.backends.arm.scripts import etdump_to_chrome_trace
13+
from executorch.devtools.etdump import schema_flatcc as flatcc
14+
from executorch.devtools.etdump.serialize import serialize_to_etdump_flatcc
15+
16+
17+
@pytest.fixture
18+
def etdump_path(tmp_path):
19+
event = flatcc.ProfileEvent(
20+
name="op",
21+
chain_index=0,
22+
instruction_id=0,
23+
delegate_debug_id_int=-1,
24+
delegate_debug_id_str="",
25+
delegate_debug_metadata=b"",
26+
start_time=1000,
27+
end_time=1500,
28+
)
29+
dump = flatcc.ETDumpFlatCC(
30+
version=0,
31+
run_data=[
32+
flatcc.RunData(
33+
name="Execute",
34+
bundled_input_index=-1,
35+
allocators=[],
36+
events=[
37+
flatcc.Event(
38+
profile_event=event, allocation_event=None, debug_event=None
39+
)
40+
],
41+
)
42+
],
43+
)
44+
path = tmp_path / "etdump.bin"
45+
data = serialize_to_etdump_flatcc(dump)
46+
path.write_bytes(struct.pack("<I", len(data)) + data)
47+
return path
48+
49+
50+
@pytest.mark.parametrize(
51+
"scale,frequency,timestamp,duration",
52+
[
53+
("cycles", "500000000", 2.0, 1.0),
54+
("cycles", "1000000000", 1.0, 0.5),
55+
("ns", None, 1.0, 0.5),
56+
("us", None, 1000.0, 500.0),
57+
("ms", None, 1000000.0, 500000.0),
58+
("s", None, 1000000000.0, 500000000.0),
59+
],
60+
)
61+
def test_trace_uses_microseconds(
62+
monkeypatch, tmp_path, etdump_path, scale, frequency, timestamp, duration
63+
):
64+
output = tmp_path / "trace.json"
65+
command = [
66+
"etdump_to_chrome_trace",
67+
"--etdump_path",
68+
str(etdump_path),
69+
"--output",
70+
str(output),
71+
"--source_time_scale",
72+
scale,
73+
]
74+
if frequency is not None:
75+
command += ["--clock_frequency_hz", frequency]
76+
monkeypatch.setattr(sys, "argv", command)
77+
78+
etdump_to_chrome_trace.main()
79+
80+
events = json.loads(output.read_text())["traceEvents"]
81+
event = next(event for event in events if event["ph"] == "X")
82+
assert event["ts"] == pytest.approx(timestamp)
83+
assert event["dur"] == pytest.approx(duration)
84+
85+
86+
@pytest.mark.parametrize("frequency", [None, "0", "-1", "nan", "inf"])
87+
def test_cycle_trace_requires_valid_frequency(monkeypatch, tmp_path, frequency, capsys):
88+
command = [
89+
"etdump_to_chrome_trace",
90+
"--etdump_path",
91+
str(tmp_path / "missing.bin"),
92+
"--output",
93+
str(tmp_path / "trace.json"),
94+
"--source_time_scale",
95+
"cycles",
96+
]
97+
if frequency is not None:
98+
command += ["--clock_frequency_hz", frequency]
99+
monkeypatch.setattr(sys, "argv", command)
100+
101+
with pytest.raises(SystemExit) as error:
102+
etdump_to_chrome_trace.main()
103+
104+
assert error.value.code == 2
105+
assert "positive finite --clock_frequency_hz" in capsys.readouterr().err

‎backends/arm/test/targets.bzl‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ def define_arm_tests():
128128
if runtime.is_oss:
129129
test_files += [
130130
"misc/test_docgen_op_support.py",
131+
"misc/test_etdump_to_chrome_trace.py",
131132
"misc/test_public_api_manifest.py",
132133
"misc/test_validate_public_api_manifest.py",
133134
]
@@ -168,7 +169,11 @@ def define_arm_tests():
168169
]),
169170
deps = ([] if runtime.is_oss or not _ENABLE_VGF else [
170171
"//executorch/backends/arm/test:model_converter_resource",
171-
]) + [
172+
]) + ([
173+
"//executorch/backends/arm/scripts:etdump_to_chrome_trace",
174+
"//executorch/devtools/etdump:schema_flatcc",
175+
"//executorch/devtools/etdump:serialize",
176+
] if test_name == "etdump_to_chrome_trace" else []) + [
172177
"//executorch/backends/arm/test:arm_tester" if runtime.is_oss else "//executorch/backends/arm/test/tester/fb:arm_tester_fb",
173178
"//executorch/backends/arm/test:conftest",
174179
"//executorch/backends/arm/test:mxfp_test_common",

‎devtools/etdump/BUCK‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
# Copyright 2026 Arm Limited and/or its affiliates.
2+
#
3+
# This source code is licensed under the BSD-style license found in the
4+
# LICENSE file in the root directory of this source tree.
5+
16
load("@fbcode_macros//build_defs:build_file_migration.bzl", "fbcode_target", "non_fbcode_target")
27
load(":targets.bzl", "define_common_targets")
38

@@ -19,6 +24,7 @@ fbcode_target(_kind = runtime.python_library,
1924
"schema_flatcc.py",
2025
],
2126
visibility = [
27+
"//executorch/backends/arm/test:etdump_to_chrome_trace",
2228
"//executorch/devtools/...",
2329
],
2430
deps = [
@@ -36,6 +42,7 @@ fbcode_target(_kind = runtime.python_library,
3642
"//executorch/schema:scalar_type.fbs": "scalar_type.fbs",
3743
},
3844
visibility = [
45+
"//executorch/backends/arm/test:etdump_to_chrome_trace",
3946
"//executorch/devtools/...",
4047
"//executorch/runtime/test/...",
4148
],

0 commit comments

Comments
 (0)