PANA-8612: Add snapshot capture orchestration - #3722
jonathanmos wants to merge 4 commits into
Conversation
|
✅ All CI checks and tests passed. Datadog automation helped this PR pass. 🎉 All green!🧪 All tests passed 🔄 Datadog retried 1 test - 1 passed on retry 🎯 Code Coverage (details) 🔗 Commit SHA: 357bf07 | Docs | View more details | Give us feedback! |
001809f to
1ca510f
Compare
99626d9 to
1dd5cfc
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1dd5cfca85
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
1dd5cfc to
50400d0
Compare
e59a111 to
6d73790
Compare
06dc1f8 to
783469c
Compare
783469c to
902b1be
Compare
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 902b1bee2a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ab03b99b6a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 11c8119e7b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
11c8119 to
13a4a1d
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 13a4a1d192
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…position pipeline
13a4a1d to
a21f246
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a21f246719
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
a21f246 to
357bf07
Compare
| * | ||
| * Disabled by default. | ||
| * | ||
| * Heatmap recording ([setHeatmapsEnabled]) is not supported by this pipeline yet; combining |
There was a problem hiding this comment.
I think we can either remove this new part or add a todo here in case we forget it here in the future.
|
|
||
| import com.datadog.android.internal.time.TimeProvider | ||
|
|
||
| internal class TimeProviderCaptureTimeProvider( |
There was a problem hiding this comment.
This class name seems weird to me, should it be simpler?
| internal class TimeProviderCaptureTimeProvider( | ||
| private val timeProvider: TimeProvider | ||
| ) : CaptureTimeProvider { | ||
| override fun elapsedRealtimeNanos(): Long = timeProvider.getDeviceElapsedTimeNanos() |
There was a problem hiding this comment.
I am not sure I understand the meaning of this wrapper class, why can't we just use the timeProvier and call getDeviceElapsedTimeNanos directly?
| * Deadline/cancellation are checked on both sides of the adapter call, and only the time spent | ||
| * actively executing [block] is charged to the recording time bank. | ||
| */ | ||
| fun <T> runMainThreadCaptureUnit( |
There was a problem hiding this comment.
Maybe annotate this with @MainThread?
| private val sdkCore: FeatureSdkCore | ||
| ) { | ||
| fun notifySkippedFrame() { | ||
| sdkCore.getFeature(Feature.RUM_FEATURE_NAME)?.sendEvent(SKIPPED_FRAME_EVENT) |
There was a problem hiding this comment.
By curiosity how RUM will consume this event?
| internalLogger.log( | ||
| InternalLogger.Level.WARN, | ||
| InternalLogger.Target.TELEMETRY, | ||
| { "Unable to $operation composition onDrawListener on viewTreeObserver" }, |
There was a problem hiding this comment.
in which exact case the IllegalStateException will throw, is this actionable as a telemetry?
| flushPositionBufferThresholdInNs = flushPositionBufferThresholdInNs | ||
| ) | ||
|
|
||
| @MainThread |
There was a problem hiding this comment.
this is annotated with @MainThread to keep isActive thread-safe?
| return try { | ||
| super.dispatchTouchEvent(event) | ||
| } catch (e: NullPointerException) { | ||
| logOrRethrowWrappedCallbackException(e) |
There was a problem hiding this comment.
do we have to log the exception here since we've null check above and we can return earlier?
hamorillo
left a comment
There was a problem hiding this comment.
I did a first high-level pass through the production files and didn't notice any major architectural issues. I couldn't review everything in depth, so I relied heavily on AI assistance for parts of this.
I think the PR is too big, which makes a deep review difficult.
I'd suggest splitting this kind of work into several PRs going forward — for example, the file reorganization in CapturedTree, CapturedTreeValidator, and CapturedTreeWireMapper is unrelated to the orchestration logic and could have been its own PR. Touch interception (CompositionWindowTouchCallback/CompositionWindowTouchInterceptor) is also fully independent of the snapshot orchestration and could have been split out on its own, leaving the orchestrator/queue work as a tighter, more focused PR.
There are some files adding code that is currently never used in production, only in tests (example: DefaultCapturedIdentityFactory). I wonder if those are also susceptible to being split.
I'm not saying I won't approve this PR once the comments are solved, just sharing some thoughts.
| private fun Long.toDelayMillis(): Long { | ||
| val delayMs = TimeUnit.NANOSECONDS.toMillis(this) | ||
| return if (delayMs < 1L) 1L else delayMs | ||
| } |
There was a problem hiding this comment.
nit:
| private fun Long.toDelayMillis(): Long { | |
| val delayMs = TimeUnit.NANOSECONDS.toMillis(this) | |
| return if (delayMs < 1L) 1L else delayMs | |
| } | |
| private fun Long.toDelayMillis(): Long = | |
| TimeUnit.NANOSECONDS.toMillis(this).coerceAtLeast(1L) |
|
|
||
| internal class DefaultCapturedIdentityFactory( | ||
| override val scope: RumViewIdentityScope, | ||
| private val replayIdGenerator: CapturedReplayIdGenerator = AutoIncrementingCapturedReplayIdGenerator(), |
There was a problem hiding this comment.
💡 The file-split changed this constructor's default from a shared companion-object generator to a fresh per-instance one:
// before
private val replayIdGenerator: CapturedReplayIdGenerator = SHARED_REPLAY_ID_GENERATOR// now
private val replayIdGenerator: CapturedReplayIdGenerator = AutoIncrementingCapturedReplayIdGenerator()Each factory instance now generates IDs starting from 0 instead of sharing one counter. No impact today since nothing constructs this factory yet, but if that ever changes without one shared generator being injected across instances, wireframe IDs will collide across windows/views. Intentional, or should the shared default be restored?
|
|
||
| when (val mapping = wireMapper.mapFullSnapshot(capture.snapshot)) { | ||
| is CaptureWireMappingResult.Success -> { | ||
| if (capture.generation.tryAccept()) { |
There was a problem hiding this comment.
❓ What happen is tryAccept return false? Should we handle that case?
What does this PR do?
Second PR in the new composition pipeline series, stacked on PANA-8500.
Adds the orchestration layer:
SnapshotCaptureOrchestratorserializes snapshot "generations"end-to-end (traversal, async processing, expiry, handoff), coalescing overlapping requests into one
follow-up and accumulating a
CaptureChangesetof what changed (accepted, not yet consumed bytraversal). A new
DefaultCompositionPipelineFactoryassembles it and its collaborators, leavingDefaultRecorderProviderwith pipeline selection only.Also in scope:
descoped in PANA-8500). Both now log WARN/
Target.USER, andsetCompositionTreeRecordingEnabled'sKDoc documents them.
dropping and expiring the oldest capture on saturation, and now expires captures it refuses so no
generation stays active holding a snapshot.
ActiveWindowSourceandCompositionViewOnDrawInterceptorare reachable from every window's drawcallbacks, so their mutable state is guarded rather than assumed main-thread confined.
Motivation
The recorder and processing pieces already existed; this adds the piece that drives a capture
end-to-end. The real view-traversal producer is PANA-8613, so the producer here is
NoOpCapturedSnapshotProducerandcompositionTreeRecordingEnabledstays disabled by defaultbehind
@ExperimentalSessionReplayApi.Additional Notes
CompositionCaptureConcurrencyTestcovers the same-structure races by hammering and the orderingones by forced interleaving; each test was verified to fail with its guard reverted.
Tuning belongs to PANA-8617.
Review checklist (to be filled by reviewers)