Skip to content

Merge develop into feature/continuous-profiling#3573

Merged
ambushwork merged 58 commits into
feature/continuous-profilingfrom
yl/merge-develop-into-feature-branch
Jun 24, 2026
Merged

Merge develop into feature/continuous-profiling#3573
ambushwork merged 58 commits into
feature/continuous-profilingfrom
yl/merge-develop-into-feature-branch

Conversation

@ambushwork

Copy link
Copy Markdown
Member

What does this PR do?

Merge develop into feature/continuous-profiling

Review checklist (to be filled by reviewers)

  • Feature or bugfix MUST have appropriate tests (unit, integration, e2e)
  • Make sure you discussed the feature or bugfix with the maintaining team in an Issue
  • Make sure each commit and the PR mention the Issue number (cf the CONTRIBUTING doc)

gonzalezreal and others added 30 commits February 25, 2026 10:50
Merge develop into feature/heatmaps
…ntity-resolver

PANA-5681: Add `HeatmapIdentifier` infrastructure [1 of 4]
…pdates

PANA-5681: Update RUM and Session Replay schemas for heatmaps [2 of 4]
…maps

PANA-5681: Integrate HeatmapIdentifierRegistry into RUM for heatmaps [3 of 4]
…olors

RUM-8112: Fix gradients not showing in compose
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
…ection-issues

RUM-16551: Fix hidden API reflection noise in Session Replay drawable mappers
Update the CHANGELOG and switch AndroidConfig version from SNAPSHOT to Release.

Co-authored-by: Cursor <[email protected]>
…replay-heatmaps

PANA-5681: Integrate HeatmapIdentifierRegistry into Session Replay for heatmaps [4 of 4]
abrooksv and others added 21 commits June 17, 2026 10:13
RUM-16882: Fix the cronet sample crashing when loading 404.png
…util

Remove the unused KotlinDSLUtils from the Gradle build
The analysis:detekt-custom job runs custom Detekt rules by invoking
./gradlew customDetektRules, which sequentially executes 31 independent
JavaExec tasks — one JVM per module. Because the tasks share no state and
declare no inter-dependencies, they are safe to run concurrently.

Adding --parallel --max-workers=4 reduces step 6 of the job from ~155s to
~69s (2.2× speedup) on a local benchmark of all 31 modules.

Each worker JVM is capped at maxHeapSize=2g to bound heap usage. The job's
Kubernetes memory limit is increased to 32Gi (matching datadog-android) to
accommodate 4 concurrent JVMs: 4 × ~3.25Gi (heap + metaspace) + 4Gi
Gradle daemon ≈ 18Gi, well within the new limit.

# Conflicts:
#	ci/pipelines/default-pipeline.yml
assembleLibrariesRelease is the dominant step in analysis:detekt-custom,
taking ~8m 17s (56% of the job) in CI. Adding --parallel enables Gradle
to compile independent library modules concurrently within the same daemon
process (in-process workers sharing the 4Gi heap) rather than sequentially.

Unlike customDetektRules --parallel which spawns separate JVMs per task,
--parallel on assembleLibrariesRelease uses Gradle's built-in worker
infrastructure — no additional memory per concurrent task. The module
dependency graph has a natural fan-out: dd-sdk-android-internal and
dd-sdk-android-core must compile first, then 25+ feature and integration
modules can compile in parallel.
Move KUBERNETES_MEMORY_LIMIT from 13Gi to 32Gi in the root .gitlab-ci.yml,
matching datadog-android which uses the same limit on the same runner pool.

The higher limit is needed for jobs that run parallel JVM workers
(analysis:detekt-custom, test:debug). Setting it globally is simpler than
per-job overrides and has no scheduling cost since KUBERNETES_MEMORY_REQUEST
remains at 8Gi — the scheduler still only needs 8Gi free on the node.
Actual memory consumed per job is unchanged for jobs that don't use the
extra headroom.

Removes the now-redundant KUBERNETES_MEMORY_LIMIT: 32Gi override from
analysis:detekt-custom.
…--parallel

test:debug ran 5 separate ./gradlew --no-daemon invocations sequentially.
Each call pays the full Gradle startup and project configuration overhead
(~10-15s in CI × 4 extra calls = ~40-60s wasted). Consolidating into a
single invocation eliminates that redundancy.

--parallel --max-workers=4 lets Gradle run up to 4 modules' test tasks
concurrently. Each module forks its own test JVM (~768MB); 4 concurrent
test JVMs + 3Gi Gradle JVM ≈ 6Gi total — well within the 32Gi limit now
set globally.

Note: unlike assembleLibrariesRelease --parallel (in-process workers,
shared heap), test tasks fork separate JVMs. --max-workers=4 acts as the
memory safety valve, the same role it plays for customDetektRules.
…--parallel

test:kover ran 4 separate ./gradlew --no-daemon invocations for coverage
generation, paying ~33s of Gradle startup overhead across 3 extra calls.
Consolidating into a single invocation using the >- YAML folded scalar
eliminates that overhead (same fix applied to test:debug).

--parallel --max-workers=4 lets Gradle run up to 4 modules' koverXmlReport
tasks concurrently. koverXmlReportRelease forks a test JVM per module for
instrumented coverage tracking (~1.5-2GB each); 4 workers stay well within
the 32Gi global limit set in the previous commit.

test:kover is the current critical path of the test stage (~17m 55s avg)
now that test:debug has been reduced to ~7m 57s.
…analysis-optimization

RUM-14050: Reduce CI pipeline time via parallelism and Gradle consolidation
Fix regression of missing tinted backgrounds
…rossplatform-prep

[PANA-5681] Preparation for Heatmaps cross-platform integration
@ambushwork
ambushwork force-pushed the yl/merge-develop-into-feature-branch branch from a12149e to 463955c Compare June 24, 2026 14:49
@codecov-commenter

codecov-commenter commented Jun 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 81.46912% with 111 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.79%. Comparing base (20fd1c7) to head (463955c).

Files with missing lines Patch % Lines
...l/recorder/mapper/AndroidMDrawableToColorMapper.kt 31.43% 20 Missing and 4 partials ⚠️
.../android/rum/internal/domain/scope/RumViewScope.kt 46.15% 13 Missing and 1 partial ⚠️
...oid/rum/internal/heatmaps/HeatmapActionResolver.kt 79.07% 7 Missing and 2 partials ⚠️
...onreplay/compose/internal/utils/ReflectionUtils.kt 25.00% 6 Missing and 3 partials ⚠️
...eplay/compose/internal/utils/BackgroundResolver.kt 91.01% 4 Missing and 4 partials ⚠️
...android/heatmaps/CrossPlatformHeatmapActionData.kt 0.00% 7 Missing ⚠️
...lay/internal/recorder/HeatmapIdentifierResolver.kt 91.57% 6 Missing and 1 partial ⚠️
...dog/android/internal/heatmaps/HeatmapIdentifier.kt 71.43% 6 Missing ⚠️
...ndroid/rum/internal/domain/scope/RumActionScope.kt 64.71% 4 Missing and 2 partials ⚠️
...y/compose/internal/reflection/ComposeReflection.kt 37.50% 0 Missing and 5 partials ⚠️
... and 10 more
Additional details and impacted files
@@                       Coverage Diff                        @@
##           feature/continuous-profiling    #3573      +/-   ##
================================================================
+ Coverage                         72.51%   72.79%   +0.28%     
================================================================
  Files                               987      999      +12     
  Lines                             36619    37015     +396     
  Branches                           6098     6191      +93     
================================================================
+ Hits                              26553    26945     +392     
+ Misses                             8403     8343      -60     
- Partials                           1663     1727      +64     
Files with missing lines Coverage Δ
...ava/com/datadog/android/heatmaps/HeatmapViewKey.kt 100.00% <100.00%> (ø)
...oid/internal/heatmaps/HeatmapIdentifierRegistry.kt 100.00% <100.00%> (ø)
...m/datadog/android/internal/utils/TapTargetUtils.kt 100.00% <100.00%> (ø)
...rum/src/main/kotlin/com/datadog/android/rum/Rum.kt 86.42% <100.00%> (+0.34%) ⬆️
...otlin/com/datadog/android/rum/_RumInternalProxy.kt 68.00% <100.00%> (+2.78%) ⬆️
...lin/com/datadog/android/rum/internal/RumFeature.kt 92.87% <100.00%> (+0.02%) ⬆️
...d/rum/internal/domain/scope/RumApplicationScope.kt 94.57% <100.00%> (+0.92%) ⬆️
...g/android/rum/internal/domain/scope/RumRawEvent.kt 100.00% <100.00%> (ø)
...droid/rum/internal/domain/scope/RumSessionScope.kt 91.67% <100.00%> (+0.05%) ⬆️
...d/rum/internal/domain/scope/RumViewManagerScope.kt 93.25% <100.00%> (+0.11%) ⬆️
... and 35 more

... and 34 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ambushwork
ambushwork marked this pull request as ready for review June 24, 2026 15:04
@ambushwork
ambushwork requested review from a team as code owners June 24, 2026 15:04

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 463955cfcf

ℹ️ 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".

Comment on lines +123 to +126
lastPublishedScreenName = context.viewUrl
lastPublishedEntries.clear()
lastPublishedEntries.putAll(context.entries)
registry.setHeatmapIdentifiers(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Retry publishing after RUM becomes available

When Session Replay records its first heatmap snapshot before RUM is registered, LazyHeatmapIdentifierRegistry.setHeatmapIdentifiers() is a no-op because getFeature(RUM) returns null. These lines still mark that snapshot as already published, so later traversals of the same unchanged hierarchy see hierarchyChanged == false and never retry after RUM registers; heatmap permanent IDs stay absent until the UI hierarchy changes. This affects apps that enable/start Session Replay before registering RUM or delay RUM initialization.

Useful? React with 👍 / 👎.

Comment on lines +112 to +114
if (context.entries.isEmpty()) {
lastPublishedScreenName = null
lastPublishedEntries.clear()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Clear the registry when a traversal has no targets

If a previous traversal published identifiers and a later traversal on the same RUM view has no heatmap entries, this branch only clears the resolver's local cache and never calls registry.setHeatmapIdentifiers(emptyMap(), context.viewUrl). The shared HeatmapIdentifierStore therefore keeps serving the old snapshot for that view URL, so subsequent RUM lookups can attach stale permanent IDs after the tracked targets are removed or dropped from Session Replay.

Useful? React with 👍 / 👎.

}
val resolvedColor = resolveGradientFillColor(drawable)
val colorFilter = resolveGradientColorFilter(drawable)
if (resolvedColor == null || colorFilter == null) return null

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve solid GradientDrawable colors without filters

For a plain XML <shape> / GradientDrawable with a solid fill, drawable.color is non-null but drawable.colorFilter is normally null. Returning null here makes ViewWireframeMapper skip the shape style, so those backgrounds produce no shape wireframe on API 29+; the mirrored N+ branch does the same on API 24-28. The previous path returned the fill color when no filter was present, so only filtered/tinted drawables should need the fallback behavior.

Useful? React with 👍 / 👎.

internal val systemRequirementsConfiguration: SystemRequirementsConfiguration,
internal val internalCallback: SessionReplayInternalCallback
internal val internalCallback: SessionReplayInternalCallback,
internal val heatmapsEnabled: Boolean

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve SessionReplayConfiguration binary ABI

Because SessionReplayConfiguration is a public data class, adding this primary-constructor property changes the generated JVM signatures for copy, copy$default, and component methods. Apps or libraries compiled against the previous SDK that call those generated methods can hit NoSuchMethodError after a dependency-only upgrade to this version; the builder flag should be stored without changing the data-class constructor ABI or with compatibility overloads.

Useful? React with 👍 / 👎.

heatmapContext = heatmapContext
)

heatmapContext?.publish()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Aggregate heatmap identifiers across all roots

When more than one decor root is captured, WindowsOnDrawListener calls produce() once per root, and this publish replaces the registry snapshot each time. That leaves identifiers only for the last root in the z-ordered list, so taps on views in another visible window such as an Activity behind a Dialog/Popup are recorded without a matching heatmap permanent ID even though their wireframes are included in the same Session Replay snapshot.

Useful? React with 👍 / 👎.

Comment on lines +139 to +143
val resolvedColor = resolveGradientFillColor(drawable)
if (resolvedColor == null || drawable.colorFilter == null) return null
val colorAlpha = (resolvedColor ushr ALPHA_SHIFT_ANDROID) and MAX_ALPHA_VALUE
val fillAlpha = (colorAlpha * drawable.alpha) / MAX_ALPHA_VALUE
return if (fillAlpha == 0) null else mergeColorAndAlpha(resolvedColor, fillAlpha)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Apply GradientDrawable color filters on API 24-28

On API 24-28 the default drawable mapper enters this N+ branch for GradientDrawables. If the drawable has an explicit colorFilter, the branch only checks that the filter exists and then serializes resolvedColor, so tinted/filtered shape backgrounds are replayed with their original fill color rather than the color actually drawn on screen. The old reflection path read the PorterDuffColorFilter color for these API levels.

Useful? React with 👍 / 👎.

@ambushwork
ambushwork merged commit c5f9cf4 into feature/continuous-profiling Jun 24, 2026
27 checks passed
@ambushwork
ambushwork deleted the yl/merge-develop-into-feature-branch branch June 24, 2026 16:04
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.

8 participants