Skip to content

Commit b931bc3

Browse files
committed
gh-154090: Preserve modes across replay outputs
1 parent 7c13579 commit b931bc3

5 files changed

Lines changed: 54 additions & 8 deletions

File tree

InternalDocs/profiling_binary_format.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -533,11 +533,10 @@ one write() call (or feeds through the compression stream).
533533

534534
## Future Considerations
535535

536-
The format reserves space for future extensions. The 12 reserved bytes in
537-
the header could hold additional metadata. The 16-byte checksum field in
538-
the footer is currently unused. The version field allows incompatible
539-
changes with graceful rejection. New compression types could be added
540-
(compression_type > 1).
536+
The Python-version field retains one reserved byte. The 16-byte checksum
537+
field in the footer is currently unused. The version field allows
538+
incompatible changes with graceful rejection. New compression types could
539+
be added (compression_type > 1).
541540

542541
Any changes that alter the meaning of existing fields or the parsing logic
543542
should increment the version number to prevent older readers from

Lib/profiling/sampling/binary_reader.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ def convert_binary_to_format(input_file, output_file, output_format,
120120
elif output_format == 'gecko':
121121
collector = GeckoCollector(interval)
122122
elif output_format == "jsonl":
123-
collector = JsonlCollector(interval)
123+
collector = JsonlCollector(interval, mode=info.get("mode"))
124124
else:
125125
raise ValueError(f"Unknown output format: {output_format}")
126126

Lib/profiling/sampling/cli.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -761,6 +761,7 @@ def _replay_with_reader(args, reader):
761761

762762
collector = _create_collector(
763763
args.format, interval, skip_idle=False,
764+
mode=info.get("mode"),
764765
diff_baseline=args.diff_baseline
765766
)
766767

Lib/test/test_profiling/test_sampling_profiler/test_binary_format.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1598,8 +1598,10 @@ def test_timestamp_preservation_with_rle(self):
15981598
class TestBinaryReplayToJsonl(BinaryFormatTestBase):
15991599
"""Tests for binary -> JSONL replay via convert_binary_to_format."""
16001600

1601-
def _replay_to_jsonl(self, samples, interval=1000):
1602-
bin_path = self.create_binary_file(samples, interval=interval)
1601+
def _replay_to_jsonl(self, samples, interval=1000, mode=None):
1602+
bin_path = self.create_binary_file(
1603+
samples, interval=interval, mode=mode
1604+
)
16031605
with tempfile.NamedTemporaryFile(suffix=".jsonl", delete=False) as f:
16041606
jsonl_path = f.name
16051607
self.temp_files.append(jsonl_path)
@@ -1629,6 +1631,16 @@ def test_binary_replay_to_jsonl_basic(self):
16291631
self.assertEqual(len(frame_defs), 1)
16301632
self.assertEqual(frame_defs[0]["line"], 99)
16311633

1634+
def test_binary_replay_to_jsonl_preserves_mode(self):
1635+
frame = make_frame("hot.py", 99, "hot_func")
1636+
records = self._replay_to_jsonl(
1637+
[[make_interpreter(0, [make_thread(1, [frame])])]],
1638+
mode=PROFILING_MODE_CPU,
1639+
)
1640+
1641+
meta = next(record for record in records if record["type"] == "meta")
1642+
self.assertEqual(meta["mode"], "cpu")
1643+
16321644
def test_binary_replay_to_jsonl_rle_weight_propagation(self):
16331645
"""RLE-batched identical samples land as a single agg entry with the right total."""
16341646
frame = make_frame("rle.py", 42, "rle_func")

Lib/test/test_profiling/test_sampling_profiler/test_cli.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
_create_collector,
3030
_generate_output_filename,
3131
_handle_output,
32+
_replay_with_reader,
3233
main,
3334
)
3435
from profiling.sampling.constants import (
@@ -964,6 +965,39 @@ def test_cli_replay_reader_errors_exit_cleanly(self):
964965
"Error: Unsupported format version 2",
965966
)
966967

968+
def test_cli_replay_propagates_recorded_mode(self):
969+
reader = mock.MagicMock()
970+
reader.get_info.return_value = {
971+
"sample_interval_us": 1000,
972+
"sample_count": 0,
973+
"compression_type": 0,
974+
"mode": PROFILING_MODE_CPU,
975+
}
976+
reader.replay_samples.return_value = 0
977+
collector = mock.MagicMock()
978+
collector.export.return_value = True
979+
args = SimpleNamespace(
980+
format="diff_flamegraph",
981+
input_file="current.bin",
982+
diff_baseline="baseline.bin",
983+
outfile="diff.html",
984+
browser=False,
985+
)
986+
987+
with mock.patch(
988+
"profiling.sampling.cli._create_collector",
989+
return_value=collector,
990+
) as create_collector:
991+
_replay_with_reader(args, reader)
992+
993+
create_collector.assert_called_once_with(
994+
"diff_flamegraph",
995+
1000,
996+
skip_idle=False,
997+
mode=PROFILING_MODE_CPU,
998+
diff_baseline="baseline.bin",
999+
)
1000+
9671001
def test_cli_jsonl_format_mutually_exclusive_with_pstats(self):
9681002
"""--jsonl and --pstats cannot be combined (mutually exclusive group)."""
9691003
with (

0 commit comments

Comments
 (0)