Skip to content

fix(tracing): Fix native frames measurements dropped for idle transactions#5813

Merged
antonis merged 7 commits intomainfrom
antonis/idle-native-frames
Mar 16, 2026
Merged

fix(tracing): Fix native frames measurements dropped for idle transactions#5813
antonis merged 7 commits intomainfrom
antonis/idle-native-frames

Conversation

@antonis
Copy link
Contributor

@antonis antonis commented Mar 13, 2026

📢 Type of change

  • Bugfix

📜 Description

The NativeFrames integration was silently dropping transaction-level frame measurements (frames_total, frames_slow, frames_frozen) for idle transactions with the warning:

[NativeFrames] Frames were collected within larger than margin of error delay for spanId. Dropping the inaccurate values.

This affected all idle transactions (navigation-based performance monitoring), causing slow/frozen frame metrics to be missing from the Performance pages.

Three issues fixed in the child span end frames fallback path of processEvent:

  1. Race condition: Child span end frames were stored in the map only after two async awaits completed (_spanToNativeFramesAtStartMap.get() and fetchNativeFrames()). If processEvent ran before those awaits finished, the fallback data was unavailable. Now stores a Promise immediately before any awaits, matching the pattern used by the root span end frames path.

  2. Timestamp mismatch: Used timestampInSeconds() (wall-clock time) for the child span end timestamp, but compared it against event.timestamp which is the span's actual end time. For idle transactions where event.timestamp is trimmed to the last child span's end time, the wall-clock time at spanEnd event firing could differ. Now uses spanToJSON(span).timestamp — the same timestamp source the idle proxy trims to.

  3. Concurrent transaction interference: The fallback was a single global variable (_lastChildSpanEndFrames) overwritten by any transaction's child spans. Now scoped per root span using AsyncExpiringMap keyed by root span ID, with a 60s TTL to survive idle timeouts + processing delays.

Bonus: child spans now reuse the same fetchNativeFrames() promise for both the fallback map and span attributes, eliminating a redundant native bridge call per child span.

💡 Motivation and Context

Reported by a customer on SDK v6.15.1 seeing 100% of idle transactions missing slow/frozen frame measurements. The issue exists on both iOS and Android and affects all SDK versions (v6 and v7/v8) since the processEvent fallback logic has not changed.

Related: #4723

💚 How did you test it?

  • Added test: falls back to last child span end frames when root span end timestamp does not match event timestamp (idle transaction trim) — simulates idle transaction trimming by shifting event.timestamp back to child span end time and advancing mock time for root span end, verifying the fallback path produces correct measurements.
  • All existing NativeFrames tests pass (10/10).
  • Build compiles successfully.

📝 Checklist

  • I added tests to verify changes
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled
  • I updated the docs if needed.
  • I updated the wizard if needed.
  • All tests passing
  • No breaking changes

🔮 Next steps

…tions

The NativeFrames integration was dropping transaction-level frame measurements
(frames_total, frames_slow, frames_frozen) for all idle transactions because
the fallback child span end frames were either unavailable or had mismatched
timestamps when processEvent ran.

Three issues fixed:
- Race condition: child span end frames stored after async awaits, so
  processEvent could run before data was available. Now stores a Promise
  immediately before any awaits.
- Timestamp mismatch: used wall-clock time (timestampInSeconds) instead of
  actual span timestamp (spanToJSON), causing isClose check to fail when
  comparing against the trimmed event.timestamp.
- Concurrent transaction interference: single global variable overwritten by
  any transaction's child spans. Now scoped per root span using AsyncExpiringMap.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Contributor

github-actions bot commented Mar 13, 2026

Semver Impact of This PR

None (no version bump detected)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


  • fix(tracing): Fix native frames measurements dropped for idle transactions by antonis in #5813
  • feat(core): Support SENTRY_ENVIRONMENT in bare React Native builds by antonis in #5823
  • chore(deps): bump tar to ^7.5.11 by antonis in #5824
  • chore(deps): bump actions/create-github-app-token from 2.2.1 to 3.0.0 by dependabot in #5822
  • chore(deps): bump dorny/paths-filter from 3.0.2 to 4.0.1 by dependabot in #5820
  • chore(deps): bump reactivecircus/android-emulator-runner from 2.35.0 to 2.37.0 by dependabot in #5818
  • chore(deps): bump getsentry/craft/.github/workflows/changelog-preview.yml from 2.23.2 to 2.24.1 by dependabot in #5821
  • chore(deps): bump getsentry/craft from 2.23.2 to 2.24.1 by dependabot in #5819
  • chore(deps): bump undici from 6.23.0 to 6.24.1 by dependabot in #5817
  • chore(deps): bump flatted from 3.3.1 to 3.4.1 by dependabot in #5816
  • Ref: remove yarn from stub update by lucas-zimerman in #5811
  • Ref(CI): Unify stub update with android update by lucas-zimerman in #5807

🤖 This preview updates automatically when you update the PR.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@antonis
Copy link
Contributor Author

antonis commented Mar 13, 2026

@sentry review

@antonis
Copy link
Contributor Author

antonis commented Mar 13, 2026

@claude review

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@antonis antonis marked this pull request as ready for review March 13, 2026 14:53
childEndFramesPromise
.then(frames => ({ timestamp: spanTimestamp, nativeFrames: frames }))
.then(undefined, error => {
debug.log(`[${INTEGRATION_NAME}] Error while fetching child span end frames.`, error);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Won't this and line 195 generated duplicated error logs?

  1. Line 138: "Error while fetching child span end frames."                                                   
  2. Line 195: "Error while capturing end frames for span ${spanId}."                                          

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch 👍 Removed with 88cae02

@lucas-zimerman
Copy link
Collaborator

Q: Concurrent transaction interference is there a test to validate this case?

antonis and others added 2 commits March 16, 2026 10:05
The error is already logged by the catch block at line 191, so the
separate log in the promise chain was redundant.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Verifies that child end frames are scoped per root span ID and don't
leak between overlapping transactions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@antonis
Copy link
Contributor Author

antonis commented Mar 16, 2026

Q: Concurrent transaction interference is there a test to validate this case?

Good point. Added a test with 2d53119

@antonis antonis added the ready-to-merge Triggers the full CI test suite label Mar 16, 2026
@antonis antonis requested a review from lucas-zimerman March 16, 2026 14:39
@github-actions
Copy link
Contributor

Android (legacy) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 466.72 ms 517.12 ms 50.40 ms
Size 43.75 MiB 48.32 MiB 4.57 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
4a17c8f+dirty 406.62 ms 400.58 ms -6.04 ms
df1f7df+dirty 442.64 ms 427.16 ms -15.48 ms
a483f9f+dirty 396.82 ms 453.28 ms 56.46 ms
60cd796+dirty 445.84 ms 492.45 ms 46.61 ms
5c16cdc+dirty 423.48 ms 452.35 ms 28.88 ms
80e4616+dirty 411.58 ms 462.12 ms 50.54 ms
55b77fc+dirty 411.87 ms 417.16 ms 5.29 ms
bca62c0+dirty 414.36 ms 451.06 ms 36.70 ms
0b64753+dirty 448.67 ms 474.61 ms 25.94 ms
4e6d7d7+dirty 480.73 ms 515.73 ms 35.00 ms

App size

Revision Plain With Sentry Diff
4a17c8f+dirty 43.75 MiB 47.99 MiB 4.24 MiB
df1f7df+dirty 43.75 MiB 48.08 MiB 4.33 MiB
a483f9f+dirty 43.75 MiB 48.41 MiB 4.66 MiB
60cd796+dirty 43.75 MiB 48.07 MiB 4.32 MiB
5c16cdc+dirty 17.75 MiB 19.68 MiB 1.94 MiB
80e4616+dirty 43.75 MiB 48.55 MiB 4.80 MiB
55b77fc+dirty 43.75 MiB 47.99 MiB 4.24 MiB
bca62c0+dirty 43.75 MiB 48.41 MiB 4.66 MiB
0b64753+dirty 17.75 MiB 19.70 MiB 1.95 MiB
4e6d7d7+dirty 43.75 MiB 48.40 MiB 4.64 MiB

@github-actions
Copy link
Contributor

iOS (legacy) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 1197.98 ms 1196.87 ms -1.11 ms
Size 3.38 MiB 4.72 MiB 1.34 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
ea3e26e+dirty 1229.13 ms 1228.46 ms -0.67 ms
80e4616+dirty 1221.32 ms 1225.64 ms 4.32 ms
818a608+dirty 1205.76 ms 1208.00 ms 2.24 ms
77061ed+dirty 1233.16 ms 1234.88 ms 1.71 ms
bef3709+dirty 1222.07 ms 1220.24 ms -1.83 ms
a206511+dirty 1185.00 ms 1186.35 ms 1.35 ms
74979ac+dirty 1210.49 ms 1213.31 ms 2.82 ms
a2bb688+dirty 1223.53 ms 1232.90 ms 9.37 ms
8a868fe+dirty 1221.50 ms 1230.78 ms 9.28 ms
d590428+dirty 1211.77 ms 1220.51 ms 8.75 ms

App size

Revision Plain With Sentry Diff
ea3e26e+dirty 3.41 MiB 4.58 MiB 1.17 MiB
80e4616+dirty 3.38 MiB 4.60 MiB 1.22 MiB
818a608+dirty 2.63 MiB 3.91 MiB 1.28 MiB
77061ed+dirty 2.63 MiB 3.98 MiB 1.34 MiB
bef3709+dirty 3.38 MiB 4.78 MiB 1.40 MiB
a206511+dirty 3.41 MiB 4.67 MiB 1.25 MiB
74979ac+dirty 3.38 MiB 4.60 MiB 1.22 MiB
a2bb688+dirty 2.63 MiB 3.99 MiB 1.36 MiB
8a868fe+dirty 3.38 MiB 4.60 MiB 1.22 MiB
d590428+dirty 3.38 MiB 4.78 MiB 1.39 MiB

@github-actions
Copy link
Contributor

iOS (new) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 1221.02 ms 1222.40 ms 1.37 ms
Size 3.38 MiB 4.72 MiB 1.34 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
ea3e26e+dirty 1216.61 ms 1214.15 ms -2.47 ms
80e4616+dirty 1206.90 ms 1205.94 ms -0.96 ms
818a608+dirty 1218.84 ms 1223.18 ms 4.34 ms
77061ed+dirty 1210.77 ms 1218.45 ms 7.68 ms
bef3709+dirty 1217.79 ms 1225.33 ms 7.54 ms
a206511+dirty 1225.02 ms 1223.74 ms -1.28 ms
74979ac+dirty 1212.33 ms 1212.54 ms 0.21 ms
a2bb688+dirty 1244.82 ms 1238.60 ms -6.22 ms
8a868fe+dirty 1206.85 ms 1215.04 ms 8.19 ms
d590428+dirty 1221.23 ms 1225.27 ms 4.03 ms

App size

Revision Plain With Sentry Diff
ea3e26e+dirty 3.41 MiB 4.58 MiB 1.17 MiB
80e4616+dirty 3.38 MiB 4.60 MiB 1.22 MiB
818a608+dirty 3.19 MiB 4.48 MiB 1.29 MiB
77061ed+dirty 3.19 MiB 4.54 MiB 1.36 MiB
bef3709+dirty 3.38 MiB 4.78 MiB 1.40 MiB
a206511+dirty 3.41 MiB 4.67 MiB 1.25 MiB
74979ac+dirty 3.38 MiB 4.60 MiB 1.22 MiB
a2bb688+dirty 3.19 MiB 4.56 MiB 1.37 MiB
8a868fe+dirty 3.38 MiB 4.60 MiB 1.22 MiB
d590428+dirty 3.38 MiB 4.78 MiB 1.39 MiB

Copy link
Collaborator

@lucas-zimerman lucas-zimerman left a comment

Choose a reason for hiding this comment

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

LGTM!

@github-actions
Copy link
Contributor

Android (new) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 439.76 ms 479.78 ms 40.02 ms
Size 43.94 MiB 49.18 MiB 5.24 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
70250df+dirty 418.08 ms 480.84 ms 62.76 ms
8d89cc9+dirty 357.69 ms 415.79 ms 58.10 ms
1853710+dirty 360.67 ms 396.28 ms 35.61 ms
55b77fc+dirty 410.46 ms 414.11 ms 3.65 ms
69602ce+dirty 375.37 ms 405.28 ms 29.91 ms
c1573b3+dirty 355.65 ms 448.82 ms 93.17 ms
90afdd3+dirty 367.79 ms 404.84 ms 37.05 ms
955f2eb+dirty 388.13 ms 433.56 ms 45.44 ms
80e4616+dirty 427.31 ms 461.15 ms 33.84 ms
276d348+dirty 356.30 ms 405.27 ms 48.97 ms

App size

Revision Plain With Sentry Diff
70250df+dirty 43.94 MiB 48.91 MiB 4.97 MiB
8d89cc9+dirty 7.15 MiB 8.41 MiB 1.26 MiB
1853710+dirty 7.15 MiB 8.41 MiB 1.26 MiB
55b77fc+dirty 43.94 MiB 48.82 MiB 4.88 MiB
69602ce+dirty 7.15 MiB 8.41 MiB 1.26 MiB
c1573b3+dirty 7.15 MiB 8.42 MiB 1.27 MiB
90afdd3+dirty 7.15 MiB 8.43 MiB 1.28 MiB
955f2eb+dirty 7.15 MiB 8.42 MiB 1.27 MiB
80e4616+dirty 43.94 MiB 49.38 MiB 5.44 MiB
276d348+dirty 7.15 MiB 8.42 MiB 1.26 MiB

@antonis antonis merged commit 45df559 into main Mar 16, 2026
158 of 173 checks passed
@antonis antonis deleted the antonis/idle-native-frames branch March 16, 2026 16:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-merge Triggers the full CI test suite

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants