Skip to content

Commit

Permalink
Merge pull request #1534 from DataDog/jmoskovich/replay-1727/base64-c…
Browse files Browse the repository at this point in the history
…aching

REPLAY-1727: Base64 Caching Mechanism
  • Loading branch information
jonathanmos authored Jul 19, 2023
2 parents 4be13fb + 257722b commit f8cc3e6
Show file tree
Hide file tree
Showing 15 changed files with 640 additions and 49 deletions.
1 change: 1 addition & 0 deletions detekt_custom.yml
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,7 @@ datadog:
- "java.lang.Object.constructor()"
- "java.lang.Runtime.availableProcessors()"
- "java.lang.Runtime.getRuntime()"
- "java.lang.Runtime.maxMemory()"
- "java.lang.System.currentTimeMillis()"
- "java.lang.System.getProperty(kotlin.String)"
- "java.lang.System.identityHashCode(kotlin.Any)"
Expand Down
2 changes: 1 addition & 1 deletion features/dd-sdk-android-session-replay/api/apiSurface
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ abstract class com.datadog.android.sessionreplay.internal.recorder.mapper.BaseWi
protected fun android.graphics.drawable.Drawable.resolveShapeStyleAndBorder(Float): Pair<com.datadog.android.sessionreplay.model.MobileSegment.ShapeStyle?, com.datadog.android.sessionreplay.model.MobileSegment.ShapeBorder?>?
protected fun resolveChildDrawableUniqueIdentifier(android.view.View): Long?
protected fun getWebPMimeType(): String?
protected fun handleBitmap(android.util.DisplayMetrics, android.graphics.drawable.Drawable, com.datadog.android.sessionreplay.model.MobileSegment.Wireframe.ImageWireframe)
protected fun handleBitmap(android.content.Context, android.util.DisplayMetrics, android.graphics.drawable.Drawable, com.datadog.android.sessionreplay.model.MobileSegment.Wireframe.ImageWireframe)
override fun startProcessingImage()
override fun finishProcessingImage()
companion object
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public final class com/datadog/android/sessionreplay/internal/recorder/SystemInf
}

public final class com/datadog/android/sessionreplay/internal/recorder/base64/Base64Serializer {
public synthetic fun <init> (Ljava/util/concurrent/ExecutorService;Lcom/datadog/android/sessionreplay/internal/utils/DrawableUtils;Lcom/datadog/android/sessionreplay/internal/utils/Base64Utils;Lcom/datadog/android/sessionreplay/internal/recorder/base64/ImageCompression;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Ljava/util/concurrent/ExecutorService;Lcom/datadog/android/sessionreplay/internal/utils/DrawableUtils;Lcom/datadog/android/sessionreplay/internal/utils/Base64Utils;Lcom/datadog/android/sessionreplay/internal/recorder/base64/ImageCompression;Lcom/datadog/android/sessionreplay/internal/recorder/base64/Cache;Lcom/datadog/android/api/InternalLogger;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
}

public abstract interface class com/datadog/android/sessionreplay/internal/recorder/base64/ImageCompression {
Expand All @@ -113,7 +113,7 @@ public abstract class com/datadog/android/sessionreplay/internal/recorder/mapper
protected final fun colorAndAlphaAsStringHexa (II)Ljava/lang/String;
public fun finishProcessingImage ()V
protected final fun getWebPMimeType ()Ljava/lang/String;
protected final fun handleBitmap (Landroid/util/DisplayMetrics;Landroid/graphics/drawable/Drawable;Lcom/datadog/android/sessionreplay/model/MobileSegment$Wireframe$ImageWireframe;)V
protected final fun handleBitmap (Landroid/content/Context;Landroid/util/DisplayMetrics;Landroid/graphics/drawable/Drawable;Lcom/datadog/android/sessionreplay/model/MobileSegment$Wireframe$ImageWireframe;)V
protected final fun resolveChildDrawableUniqueIdentifier (Landroid/view/View;)Ljava/lang/Long;
protected final fun resolveShapeStyleAndBorder (Landroid/graphics/drawable/Drawable;F)Lkotlin/Pair;
protected final fun resolveViewGlobalBounds (Landroid/view/View;F)Lcom/datadog/android/sessionreplay/internal/recorder/GlobalBounds;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import android.widget.Button
import android.widget.CheckBox
import android.widget.CheckedTextView
import android.widget.EditText
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.NumberPicker
import android.widget.RadioButton
Expand All @@ -24,6 +25,7 @@ import com.datadog.android.sessionreplay.internal.recorder.mapper.ButtonMapper
import com.datadog.android.sessionreplay.internal.recorder.mapper.CheckBoxMapper
import com.datadog.android.sessionreplay.internal.recorder.mapper.CheckedTextViewMapper
import com.datadog.android.sessionreplay.internal.recorder.mapper.EditTextViewMapper
import com.datadog.android.sessionreplay.internal.recorder.mapper.ImageButtonMapper
import com.datadog.android.sessionreplay.internal.recorder.mapper.MapperTypeWrapper
import com.datadog.android.sessionreplay.internal.recorder.mapper.MaskCheckBoxMapper
import com.datadog.android.sessionreplay.internal.recorder.mapper.MaskCheckedTextViewMapper
Expand Down Expand Up @@ -77,6 +79,7 @@ enum class SessionReplayPrivacy {
internal fun mappers(): List<MapperTypeWrapper> {
val viewWireframeMapper = ViewWireframeMapper()
val unsupportedViewMapper = UnsupportedViewMapper()
val imageButtonMapper = ImageButtonMapper()
val imageMapper: ViewScreenshotWireframeMapper
val textMapper: TextViewMapper
val buttonMapper: ButtonMapper
Expand Down Expand Up @@ -131,6 +134,7 @@ enum class SessionReplayPrivacy {
MapperTypeWrapper(CheckBox::class.java, checkBoxMapper.toGenericMapper()),
MapperTypeWrapper(CheckedTextView::class.java, checkedTextViewMapper.toGenericMapper()),
MapperTypeWrapper(Button::class.java, buttonMapper.toGenericMapper()),
MapperTypeWrapper(ImageButton::class.java, imageButtonMapper.toGenericMapper()),
MapperTypeWrapper(EditText::class.java, editTextViewMapper.toGenericMapper()),
MapperTypeWrapper(TextView::class.java, textMapper.toGenericMapper()),
MapperTypeWrapper(ImageView::class.java, imageMapper.toGenericMapper()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@

package com.datadog.android.sessionreplay.internal.async

import android.graphics.drawable.Drawable
import androidx.annotation.MainThread
import androidx.annotation.VisibleForTesting
import androidx.annotation.WorkerThread
import com.datadog.android.sessionreplay.internal.processor.RecordedDataProcessor
import com.datadog.android.sessionreplay.internal.processor.RumContextDataHandler
import com.datadog.android.sessionreplay.internal.recorder.SystemInformation
import com.datadog.android.sessionreplay.internal.recorder.base64.Base64LRUCache
import com.datadog.android.sessionreplay.internal.recorder.base64.Cache
import com.datadog.android.sessionreplay.internal.utils.TimeProvider
import com.datadog.android.sessionreplay.model.MobileSegment
import java.lang.ClassCastException
Expand All @@ -34,15 +37,18 @@ internal class RecordedDataQueueHandler {
private var processor: RecordedDataProcessor
private var rumContextDataHandler: RumContextDataHandler
private var timeProvider: TimeProvider
private var cache: Cache<Drawable, String>

internal constructor(
processor: RecordedDataProcessor,
rumContextDataHandler: RumContextDataHandler,
timeProvider: TimeProvider
timeProvider: TimeProvider,
cache: Cache<Drawable, String> = Base64LRUCache
) : this(
processor = processor,
rumContextDataHandler = rumContextDataHandler,
timeProvider = timeProvider,
cache = cache,

/**
* TODO: RUMM-0000 consider change to LoggingThreadPoolExecutor once V2 is merged.
Expand All @@ -64,12 +70,14 @@ internal class RecordedDataQueueHandler {
processor: RecordedDataProcessor,
rumContextDataHandler: RumContextDataHandler,
timeProvider: TimeProvider,
executorService: ExecutorService
executorService: ExecutorService,
cache: Cache<Drawable, String>
) {
this.processor = processor
this.rumContextDataHandler = rumContextDataHandler
this.executorService = executorService
this.timeProvider = timeProvider
this.cache = cache
}

// region internal
Expand Down Expand Up @@ -99,6 +107,11 @@ internal class RecordedDataQueueHandler {
val rumContextData = rumContextDataHandler.createRumContextData()
?: return null

// if the view changed then clear the drawable cache
if (rumContextData.prevRumContext != rumContextData.newRumContext) {
cache.clear()
}

val item = SnapshotRecordedDataQueueItem(
rumContextData = rumContextData,
systemInformation = systemInformation
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2016-Present Datadog, Inc.
*/

package com.datadog.android.sessionreplay.internal.recorder.base64

import android.content.ComponentCallbacks2
import android.content.res.Configuration
import android.graphics.drawable.AnimationDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.DrawableContainer
import android.graphics.drawable.LayerDrawable
import android.util.LruCache
import androidx.annotation.VisibleForTesting

internal object Base64LRUCache : Cache<Drawable, String>, ComponentCallbacks2 {
@Suppress("MagicNumber")
val MAX_CACHE_MEMORY_SIZE_BYTES = 4 * 1024 * 1024 // 4MB
@Suppress("MagicNumber")
private val ON_LOW_MEMORY_SIZE_BYTES = MAX_CACHE_MEMORY_SIZE_BYTES / 2 // 50% size
@Suppress("MagicNumber")
private val ON_MODERATE_MEMORY_SIZE_BYTES = (MAX_CACHE_MEMORY_SIZE_BYTES / 4) * 3 // 75% size

private var cache: LruCache<String, ByteArray> = object :
LruCache<String, ByteArray>(MAX_CACHE_MEMORY_SIZE_BYTES) {
override fun sizeOf(key: String?, value: ByteArray): Int {
return value.size
}
}

override fun onTrimMemory(level: Int) {
when (level) {
ComponentCallbacks2.TRIM_MEMORY_BACKGROUND -> {
cache.evictAll()
}

ComponentCallbacks2.TRIM_MEMORY_COMPLETE -> {
cache.evictAll()
}

ComponentCallbacks2.TRIM_MEMORY_MODERATE -> {
cache.trimToSize(ON_MODERATE_MEMORY_SIZE_BYTES)
}

ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL -> {
cache.evictAll()
}

ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW -> {
cache.trimToSize(ON_LOW_MEMORY_SIZE_BYTES)
}

ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE -> {
cache.trimToSize(ON_MODERATE_MEMORY_SIZE_BYTES)
}

ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN -> {
cache.evictAll()
}

else -> {
cache.evictAll()
}
}
}

override fun onConfigurationChanged(newConfig: Configuration) {}

override fun onLowMemory() {
cache.evictAll()
}

@VisibleForTesting
internal fun setBackingCache(cache: LruCache<String, ByteArray>) {
this.cache = cache
}

@Synchronized
override fun put(element: Drawable, value: String) {
val key = generateKey(element)
val byteArray = value.toByteArray(Charsets.UTF_8)
cache.put(key, byteArray)
}

@Synchronized
override fun get(element: Drawable): String? =
cache.get(generateKey(element))?.let {
String(it)
}

@Synchronized
override fun size(): Int = cache.size()

@Synchronized
override fun clear() {
cache.evictAll()
}

private fun generateKey(drawable: Drawable): String =
generatePrefix(drawable) + System.identityHashCode(drawable)

private fun generatePrefix(drawable: Drawable): String {
return when (drawable) {
is DrawableContainer -> getPrefixForDrawableContainer(drawable)
is LayerDrawable -> getPrefixForLayerDrawable(drawable)
else -> ""
}
}

private fun getPrefixForDrawableContainer(drawable: DrawableContainer): String {
if (drawable !is AnimationDrawable) {
return drawable.state.joinToString(separator = "", postfix = "-")
}

return ""
}

private fun getPrefixForLayerDrawable(drawable: LayerDrawable): String {
return if (drawable.numberOfLayers > 1) {
val sb = StringBuilder()
for (index in 0 until drawable.numberOfLayers) {
val layer = drawable.getDrawable(index)
val layerHash = System.identityHashCode(layer).toString()
sb.append(layerHash)
sb.append("-")
}
"$sb"
} else {
""
}
}
}
Loading

0 comments on commit f8cc3e6

Please sign in to comment.