Repository navigation
Android 17 method traces rejected as profiling_invalid_sampled_profile: new ART header keys unsupported by android_trace_log #6517
Description
Summary
Android method-trace profiles recorded on Android 17 (API 37) are rejected at ingestion with outcome invalid / reason profiling_invalid_sampled_profile. The same app on Android 16 (API 36) is accepted.
Android 17's ART adds five new lines to the *version header section of method traces. Relay parses sampled_profile with android_trace_log 0.3.0, which fails on any header key it doesn't know. The new lines aren't publicly documented; they're defined only in ART's source.
| Android 17, same app | Profile |
|---|---|
| ❌ As recorded | e32415d4d98a43fbb8db7866cf56d51c → profiling_invalid_sampled_profile (not found) |
| ✅ Unknown header lines stripped on the client | 03d14df40eec40538c7b67de18d8fcb6 → accepted |
(org sentry-sdks, project sentry-react-native / 5428561, 2026-10-15.) The transactions themselves are accepted; only the profile is dropped. On 2026-10-15 alone, 33 Android 17 profiles from this project were rejected this way.
Where it fails
relay-profiling/src/android/legacy.rs#L138-L140(transaction profiles) andrelay-profiling/src/android/chunk.rs#L100-L102(Android trace chunks):match android_trace_log::parse(&profile_bytes) { Ok(profile) => profile, Err(_) => return Err(ProfileError::InvalidSampledProfile), }
android_trace_log0.3.0 (Cargo.toml#L87). In itssrc/lib.rs, the*versionheader loop only accepts either a new*sectionor one of ten known keys (data-file-overflow,clock,elapsed-time-usec,num-method-calls,clock-call-overhead-nsec,vm,pid,alloc-count,alloc-size,gc-count). Any other line errors out:let (inp, line) = alt(( map(parse_section, Line::NewSection), map(parse_header_line, Line::Info), ))(input)?;
- Android 17 writes these lines after
pid=in every method trace, and the first one aborts the parse:*version 3 data-file-overflow=false clock=dual elapsed-time-usec=9407646 num-method-calls=4341 clock-call-overhead-nsec=273 vm=art pid=4471 +is_streaming=false +is_precise_trace=true +end_time=1642106300035 +end_tsc=7762889077040 +compilation-filter=run-from-apk *threads
Where the new lines come from
They're written unconditionally by TraceWriter::CreateSummary in ART's runtime/trace.cc on android17-release. The same function on android16-release stops at pid=.
os << "is_streaming=" << (trace_output_mode_ == TraceOutputMode::kStreaming ? "true" : "false") << "\n";
os << "is_precise_trace=" << (flags & Trace::TraceFlag::kTraceLowOverhead ? "false" : "true") << "\n";
os << "end_time=" << end_time << "\n"; // NanoTime()
os << "end_tsc=" << end_time_tsc << "\n"; // TimestampCounter
os << "compilation-filter=" << compiler_filter << "\n"; // primary APK optimization statusNone of this is documented outside the source. The Android 17 release notes and behavior-change pages don't mention it, and the trace header format has never been formally specified.
The same function also has two older conditional variations (already in Android 16) that android_trace_log doesn't handle either:
elapsed-time-nsecinstead ofelapsed-time-usecfor the V2 trace format;num-method-callsomitted in streaming mode.
Debug.startMethodTracingSampling, which the SDKs use, produces the V1 non-streaming variant, so today only the Android 17 keys are hit. Still, it argues for tolerating unknown keys in general rather than special-casing these five.
The header is the only incompatibility
- A port of
android_trace_log's grammar parses 15/15 Android 17 traces completely once unknown header keys are skipped: binary v3,event_size=14,clock=dual, 44–74 threads, 115–1230 methods, 657–12,791 events, all consumed, 0 trailing bytes. Threads, methods, theSLOWbinary header and the event records are unchanged. - Verified end to end (table above): stripping only these five lines on the client before upload makes Android 17 profiles ingest. Reference PR, not to be merged: [DO NOT MERGE] Strip Android 17 trace header keys unknown to Relay sentry-react-native#6892.
Impact
- Every legacy
Debug.startMethodTracingSamplingtransaction profile from an Android 17 device. That covers the React Native SDK (defaultplatformProfilers: true) and, by the same mechanism, sentry-android's transaction profiler. - Probably also Android trace profile chunks from the legacy profiler, since
chunk.rsuses the same parser. sentry-android's continuous profiling on API 35+ normally uses Perfetto, which limits this part. - Already-shipped SDK versions are affected, so a Relay-side fix covers everyone without a client update.
- It's easy to miss: in Stats this outcome shows as "Invalid · Internal" (see the note at the end).
Suggested fix
Skip header key=value lines whose key is unknown, before or inside android_trace_log::parse, at both call sites (legacy.rs, chunk.rs).
- Unknown keys only: malformed known keys (e.g.
clock=foo) keep failing exactly as today. - No regressions: traces without unknown keys stay byte-identical, so current parsing is unaffected.
- Offsets are safe:
data_offsetis read relative to theSLOWmagic, not the file start, so shortening the text header doesn't move the binary section. - Where to fix: upstream
chayleaf/android_trace_logwas last pushed 2023-09-03, so this likely needs a Relay-side pre-filter or a fork/vendored copy rather than an upstream bump. - Test fixture: we can provide Android 17 trace fixtures (15 captured).
Note: Stats shows this as "Internal"
static/app/views/organizationStats/getReasonGroupName.ts falls back to internal for every invalid reason it doesn't group, which covers all profiling_* reasons. The real reason was only visible through stats_v2?groupBy=reason. That probably deserves a small follow-up in getsentry/sentry.
Source references
Relay (pinned to 75ace9c9)
relay-profiling/src/android/legacy.rs#L138-L140: transaction profile parse →InvalidSampledProfilerelay-profiling/src/android/chunk.rs#L100-L102: Android trace chunk parse →InvalidSampledProfilerelay-profiling/src/outcomes.rs:InvalidSampledProfile→profiling_invalid_sampled_profileCargo.toml#L87:android_trace_log = { version = "0.3", … }
android_trace_log 0.3.0 (crate, upstream repo, last push 2023-09-03)
src/lib.rsparse_header_line: the ten accepted header keyssrc/lib.rsparse_android_trace,*versionloop:alt((parse_section, parse_header_line))?, which errors on any other linesrc/lib.rsparse_num: hex (0x…) or decimal;SLOWheader withdata_offsetrelative to the magic
ART (AOSP)
runtime/trace.cc@android17-release:TraceWriter::CreateSummary, which writes the five new lines afterpid=runtime/trace.cc@android16-release: same function, header ends atpid=runtime/trace.h@android17-release
Android docs (no mention of the header change; the format isn't specified)
- Android 17 features and changes list
- Behavior changes: all apps (Android 17)
android.os.Debugreference- Generate trace logs by instrumenting your app
- dmtracedump
Sentry
static/app/views/organizationStats/getReasonGroupName.ts: invalid-reason fallback tointernal- [DO NOT MERGE] Strip Android 17 trace header keys unknown to Relay sentry-react-native#6892: client-side strip experiment (reference, do not merge)
- fix(android): Include CPU and memory measurements in Android profiles sentry-react-native#6891: Android profile CPU/memory measurements, which could only be verified on API ≤ 36 because of this
Activity
linear-code commentedon Oct 15, 2026
RELAY-287
antonis commentedon Oct 16, 2026
Heads up that I've opened a PR for this 🙇 #6523