Skip to content

feat(profiling): Add Perfetto trace format support#5659

Open
markushi wants to merge 15 commits intomasterfrom
feat/markushi/perfetto-profiling-support
Open

feat(profiling): Add Perfetto trace format support#5659
markushi wants to merge 15 commits intomasterfrom
feat/markushi/perfetto-profiling-support

Conversation

@markushi
Copy link
Copy Markdown
Member

@markushi markushi commented Feb 25, 2026

This PR adds the ability to ingest binary Perfetto traces (.pftrace) and convert them into the existing Sample v2 JSON format used by Sentry's profiling pipeline. This targets Android
profiling, where Perfetto is the native tracing format.

Key Changes

  1. New feature flag organizations:continuous-profiling-perfetto — gates all Perfetto processing.
  2. Compound item format via meta_length — profile chunk items can now carry [JSON metadata][binary blob], split at meta_length. This is how Perfetto traces arrive: metadata describes the
    profile, the binary blob is the raw .pftrace.
  3. Perfetto → Sample v2 conversion — new relay-profiling/src/perfetto/ module (proto definitions + conversion logic) parses binary Perfetto traces and produces the existing Sample v2
    JSON format.
  4. Raw profile passthrough to Kafka — after expansion, the original Perfetto binary is preserved alongside the expanded JSON. Two new fields on ProfileChunkKafkaMessage: raw_profile and
    raw_profile_content_type.
  5. New fields on existing structs:
    - Frame.package — library/container path for native/Java frames
    - ProfileMetadata.content_type — carries "perfetto" through the pipeline
    - DebugImage::native_image() constructor — creates debug images from Perfetto mapping data

markushi and others added 11 commits February 25, 2026 09:16
Add support for ingesting binary Perfetto traces (.pftrace) as profile
chunks. The SDK sends an envelope with a ProfileChunk metadata item
paired with a ProfileChunkData item containing the raw Perfetto protobuf.

Relay decodes the Perfetto trace, extracts CPU profiling samples
(PerfSample and StreamingProfilePacket), converts them to the internal
Sample v2 format, and forwards both the expanded JSON and the original
binary blob to Kafka for downstream processing.

Key changes:
- New `perfetto` module in relay-profiling for protobuf decoding and
  conversion to Sample v2
- New `ProfileChunkData` envelope item type for binary profile payloads
- Pairing logic to associate ProfileChunk metadata with ProfileChunkData
- Raw profile blob preserved through to Kafka for further processing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
# Conflicts:
#	relay-server/src/envelope/item.rs
@markushi
Copy link
Copy Markdown
Member Author

@sentry review

markushi and others added 2 commits March 23, 2026 09:01
…fer main thread

Separate the shared `strings` intern table into distinct `function_names`,
`mapping_paths`, and `build_ids` tables matching the Perfetto spec where
each InternedData field has its own ID namespace. Also infer the main
thread name when tid equals pid and no explicit name is provided.

Co-Authored-By: Claude <noreply@anthropic.com>
@markushi markushi marked this pull request as ready for review March 23, 2026 08:57
@markushi markushi requested a review from a team as a code owner March 23, 2026 08:57
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: First timestamp delta skipped in StreamingProfilePacket processing
    • Removed the 'i > 0' guard so that timestamp_delta_us[0] is now correctly applied to the first sample's timestamp.

Create PR

Or push these changes by commenting:

@cursor push ab5f3ec796
Preview (ab5f3ec796)
diff --git a/relay-profiling/src/perfetto/mod.rs b/relay-profiling/src/perfetto/mod.rs
--- a/relay-profiling/src/perfetto/mod.rs
+++ b/relay-profiling/src/perfetto/mod.rs
@@ -229,9 +229,7 @@
             Some(Data::StreamingProfilePacket(spp)) => {
                 let mut ts = packet.timestamp.unwrap_or(0);
                 for (i, &cs_iid) in spp.callstack_iid.iter().enumerate() {
-                    if i > 0
-                        && let Some(&delta) = spp.timestamp_delta_us.get(i)
-                    {
+                    if let Some(&delta) = spp.timestamp_delta_us.get(i) {
                         // `delta` is i64 (can be negative for out-of-order samples).
                         // Casting to u64 wraps negative values, which is correct because
                         // `wrapping_add` of a wrapped negative value subtracts as expected.

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

markushi and others added 2 commits March 23, 2026 10:27
Replace `get().is_none()` with `!contains_key()` to satisfy clippy
and add a CHANGELOG entry for the Perfetto interning changes.

Co-Authored-By: Claude <noreply@anthropic.com>
The first delta in timestamp_delta_us was skipped due to an i > 0
guard, but per the Perfetto spec the first sample's timestamp should
be TracePacket.timestamp + timestamp_delta_us[0]. Update tests to use
non-zero first deltas to verify the fix.

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

// `wrapping_add` of a wrapped negative value subtracts as expected.
ts = ts.wrapping_add((delta * 1000) as u64);
}
raw_samples.push((ts, 0, cs_iid, seq_id));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Streaming profile samples all assigned thread ID zero

Medium Severity

All StreamingProfilePacket samples are pushed with tid = 0, losing the actual thread identity. Perfetto associates each StreamingProfilePacket with a thread via the trusted_packet_sequence_idTrackDescriptorThreadDescriptor chain, but this code never resolves that mapping. Every streaming sample ends up with thread_id: "0", which collapses multi-thread profiles into one thread and produces incorrect output for the profiling UI.

Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant