Skip to content

Conversation

fuzzybinary
Copy link
Member

What and why?

Allow Session Replay tree traversal to capture images from Session Replay.

This works by using RawImage's toByteData(format: ImageByteFormat.rawRgba) method, which gives a raw, RGBA image with premultiplied alpha. This is then copied directly to the native layer for processing.

On Android, we transfer the data using a ByteBuffer, and then use that buffer to create a Bitmap for compression and hashing. Because Android's raw bitmap format is also RGBA premultiplied (despite what the constant says), we can simply pass in the wrapped buffer to create the required Bitmap

On iOS, we create Data / NSData which copies the data directly from the buffer pointer. Doing this relatively safely requires we bypass the objective_c package, as using address in Dart requires calling an FFI leaf function (one that can be guaranteed not to call back into Dart) directly.

Because of the memory constraints of the copy, we are limiting copies to 640,000 pixels (an 800x800 image), which would be a raw size of 2.4 MB.

Any images larger than that are captured as "Large Image", shown below.
image

This still needs to support caching which images we've sent between sessions.

Review checklist

  • This pull request has appropriate unit and / or integration tests
  • This pull request references a Github or JIRA issue

@fuzzybinary fuzzybinary requested a review from a team as a code owner August 25, 2025 20:02
Current support for Android image processing is very slow, but confined to a background thread on Android.  However, because we halt collection until individual images are processed, this will negatively affect replays with a large number of images.  We'll need to look for an async method of processing images or speed it up significantly on Android

refs: RUM-11125

# Conflicts:
#	packages/datadog_session_replay/example/lib/app.dart
#	packages/datadog_session_replay/example/lib/routes.dart
#	packages/datadog_session_replay/example/lib/screens/main_screen.dart
@fuzzybinary fuzzybinary force-pushed the jward/RUM-11125-image-capture branch from 1b893f4 to 7e8c260 Compare August 25, 2025 20:23
@Valpertui Valpertui requested a review from a team August 26, 2025 13:58
@fuzzybinary fuzzybinary force-pushed the jward/RUM-11125-image-capture branch from 5a35f05 to efdc738 Compare August 26, 2025 18:39
val resourceWriter: ResourceWriter,
val bitmapHandler: BitmapHandler = DefaultBitmapHandler(internalLogger)
) : ResourceResolver {
private val resourceKeyMap: MutableMap<Int, ResourceResolver.ResourceEntry> = mutableMapOf()
Copy link
Member

@jonathanmos jonathanmos Aug 27, 2025

Choose a reason for hiding this comment

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

It looks like items are added to resourceKeyMap, but I don't see where they are cleared from it. Also, should we limit the size of the map? Same questions about knownResources.

Copy link
Member Author

Choose a reason for hiding this comment

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

Right now we're not clearing them, except at the end of the session. I'd have to think about under what circumstances we wanted to clear them. What criteria does the Android SDK use? I didn't see it.

Copy link
Member

@jonathanmos jonathanmos Aug 28, 2025

Choose a reason for hiding this comment

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

For the resources map (which I think is equivalent to resourceKeyMap) we're using an lrucache to keep it implicitly bounded. For the DS knownResources I think it's unbounded but deleted every 30 days when the data is considered to have expired.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'll probably do that as a separate task. Created RUM-11500 to track

mariedm
mariedm previously approved these changes Aug 27, 2025
Copy link
Member

@mariedm mariedm left a comment

Choose a reason for hiding this comment

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

Only a few nits, feel free to disregard them. LGMT! 🚀

internal var mimeType: String
internal var context: Context

internal init(
Copy link
Member

Choose a reason for hiding this comment

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

nit: I don't think this init is needed; since EnrichedResource is a struct, you get memberwise initializer for free.

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't know why this is needed, but it is apparently?

jonathanmos
jonathanmos previously approved these changes Aug 28, 2025
@fuzzybinary fuzzybinary dismissed stale reviews from jonathanmos and mariedm via b50bab8 August 28, 2025 18:18
@fuzzybinary fuzzybinary force-pushed the jward/RUM-11125-image-capture branch from b50bab8 to 5f4505f Compare August 28, 2025 18:42
@fuzzybinary fuzzybinary force-pushed the jward/RUM-11125-image-capture branch from 5f4505f to 3d2e603 Compare August 28, 2025 21:07
@fuzzybinary fuzzybinary merged commit cecf56c into develop Sep 2, 2025
10 checks passed
@fuzzybinary fuzzybinary deleted the jward/RUM-11125-image-capture branch September 2, 2025 17:41
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.

3 participants