Skip to content

Commit

Permalink
Merge 2cddcc4 into bee240b
Browse files Browse the repository at this point in the history
  • Loading branch information
romtsn authored Mar 26, 2024
2 parents bee240b + 2cddcc4 commit baefbb0
Show file tree
Hide file tree
Showing 11 changed files with 247 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ final class ManifestMetadataReader {

static final String ENABLE_APP_START_PROFILING = "io.sentry.profiling.enable-app-start";

static final String REPLAYS_SESSION_SAMPLE_RATE = "io.sentry.replays.session-sample-rate";

static final String REPLAYS_ERROR_SAMPLE_RATE = "io.sentry.replays.error-sample-rate";

/** ManifestMetadataReader ctor */
private ManifestMetadataReader() {}

Expand Down Expand Up @@ -371,6 +375,21 @@ static void applyMetadata(
options.setEnableAppStartProfiling(
readBool(
metadata, logger, ENABLE_APP_START_PROFILING, options.isEnableAppStartProfiling()));

if (options.get_experimental().getReplayOptions().getSessionSampleRate() == null) {
final Double sessionSampleRate =
readDouble(metadata, logger, REPLAYS_SESSION_SAMPLE_RATE);
if (sessionSampleRate != -1) {
options.get_experimental().getReplayOptions().setSessionSampleRate(sessionSampleRate);
}
}

if (options.get_experimental().getReplayOptions().getErrorSampleRate() == null) {
final Double errorSampleRate = readDouble(metadata, logger, REPLAYS_ERROR_SAMPLE_RATE);
if (errorSampleRate != -1) {
options.get_experimental().getReplayOptions().setErrorSampleRate(errorSampleRate);
}
}
}

options
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1370,4 +1370,46 @@ class ManifestMetadataReaderTest {
// Assert
assertFalse(fixture.options.isEnableAppStartProfiling)
}

@Test
fun `applyMetadata reads replays errorSampleRate from metadata`() {
// Arrange
val expectedSampleRate = 0.99f

val bundle = bundleOf(ManifestMetadataReader.REPLAYS_ERROR_SAMPLE_RATE to expectedSampleRate)
val context = fixture.getContext(metaData = bundle)

// Act
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)

// Assert
assertEquals(expectedSampleRate.toDouble(), fixture.options._experimental.replayOptions.errorSampleRate)
}

@Test
fun `applyMetadata does not override replays errorSampleRate from options`() {
// Arrange
val expectedSampleRate = 0.99f
fixture.options._experimental.replayOptions.errorSampleRate = expectedSampleRate.toDouble()
val bundle = bundleOf(ManifestMetadataReader.REPLAYS_ERROR_SAMPLE_RATE to 0.1f)
val context = fixture.getContext(metaData = bundle)

// Act
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)

// Assert
assertEquals(expectedSampleRate.toDouble(), fixture.options._experimental.replayOptions.errorSampleRate)
}

@Test
fun `applyMetadata without specifying replays errorSampleRate, stays null`() {
// Arrange
val context = fixture.getContext()

// Act
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)

// Assert
assertNull(fixture.options._experimental.replayOptions.errorSampleRate)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,7 @@ internal class ReplayCache(
private val encoderCreator: (File) -> SimpleVideoEncoder = { videoFile ->
SimpleVideoEncoder(
options,
MuxerConfig(
file = videoFile,
recorderConfig = recorderConfig,
frameRate = recorderConfig.frameRate.toFloat(),
bitrate = 20 * 1000
)
MuxerConfig(file = videoFile, recorderConfig = recorderConfig)
).also { it.start() }
}
) : Closeable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,7 @@ package io.sentry.android.replay

import android.content.Context
import android.graphics.Bitmap
import android.graphics.Point
import android.graphics.Rect
import android.os.Build
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import android.view.WindowManager
import io.sentry.DateUtils
import io.sentry.Hint
import io.sentry.IHub
Expand All @@ -33,7 +28,6 @@ import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicReference
import kotlin.LazyThreadSafetyMode.NONE
import kotlin.math.roundToInt

class ReplayIntegration(
private val context: Context,
Expand All @@ -59,28 +53,11 @@ class ReplayIntegration(
private val saver =
Executors.newSingleThreadScheduledExecutor(ReplayExecutorServiceThreadFactory())

private val screenBounds by lazy(NONE) {
// PixelCopy takes screenshots including system bars, so we have to get the real size here
val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
if (VERSION.SDK_INT >= VERSION_CODES.R) {
wm.currentWindowMetrics.bounds
} else {
val screenBounds = Point()
@Suppress("DEPRECATION")
wm.defaultDisplay.getRealSize(screenBounds)
Rect(0, 0, screenBounds.x, screenBounds.y)
}
}

private val aspectRatio by lazy(NONE) {
screenBounds.bottom.toFloat() / screenBounds.right.toFloat()
}

private val recorderConfig by lazy(NONE) {
ScreenshotRecorderConfig(
recordingWidth = (720 / aspectRatio).roundToInt(),
recordingHeight = 720,
scaleFactor = 720f / screenBounds.bottom
ScreenshotRecorderConfig.from(
context,
targetHeight = 720,
options._experimental.replayOptions
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,34 @@
package io.sentry.android.replay

import android.annotation.TargetApi
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Bitmap.Config.ARGB_8888
import android.graphics.Canvas
import android.graphics.Matrix
import android.graphics.Paint
import android.graphics.Point
import android.graphics.Rect
import android.graphics.RectF
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import android.os.Handler
import android.os.HandlerThread
import android.os.Looper
import android.view.PixelCopy
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
import android.view.WindowManager
import io.sentry.SentryLevel.DEBUG
import io.sentry.SentryLevel.INFO
import io.sentry.SentryOptions
import io.sentry.SentryReplayOptions
import io.sentry.android.replay.viewhierarchy.ViewHierarchyNode
import java.lang.ref.WeakReference
import java.util.WeakHashMap
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.math.roundToInt
import kotlin.system.measureTimeMillis

@TargetApi(26)
Expand Down Expand Up @@ -217,8 +224,33 @@ internal data class ScreenshotRecorderConfig(
val recordingWidth: Int,
val recordingHeight: Int,
val scaleFactor: Float,
val frameRate: Int = 2
)
val frameRate: Int,
val bitRate: Int
) {
companion object {
fun from(context: Context, targetHeight: Int, sentryReplayOptions: SentryReplayOptions): ScreenshotRecorderConfig {
// PixelCopy takes screenshots including system bars, so we have to get the real size here
val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
val screenBounds = if (VERSION.SDK_INT >= VERSION_CODES.R) {
wm.currentWindowMetrics.bounds
} else {
val screenBounds = Point()
@Suppress("DEPRECATION")
wm.defaultDisplay.getRealSize(screenBounds)
Rect(0, 0, screenBounds.x, screenBounds.y)
}
val aspectRatio = screenBounds.bottom.toFloat() / screenBounds.right.toFloat()

return ScreenshotRecorderConfig(
recordingWidth = (targetHeight / aspectRatio).roundToInt(),
recordingHeight = targetHeight,
scaleFactor = targetHeight.toFloat() / screenBounds.bottom,
frameRate = sentryReplayOptions.frameRate,
bitRate = sentryReplayOptions.bitRate
)
}
}
}

interface ScreenshotRecorderCallback {
fun onScreenshotRecorded(bitmap: Bitmap)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ internal class SimpleVideoEncoder(
MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface
)
format.setInteger(MediaFormat.KEY_BIT_RATE, muxerConfig.bitrate)
format.setFloat(MediaFormat.KEY_FRAME_RATE, muxerConfig.frameRate)
format.setInteger(MediaFormat.KEY_BIT_RATE, muxerConfig.recorderConfig.bitRate)
format.setFloat(MediaFormat.KEY_FRAME_RATE, muxerConfig.recorderConfig.frameRate.toFloat())
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10)

format
Expand All @@ -79,7 +79,7 @@ internal class SimpleVideoEncoder(
}

private val bufferInfo: MediaCodec.BufferInfo = MediaCodec.BufferInfo()
private val frameMuxer = muxerConfig.frameMuxer
private val frameMuxer = SimpleMp4FrameMuxer(muxerConfig.file.absolutePath, muxerConfig.recorderConfig.frameRate.toFloat())
val duration get() = frameMuxer.getVideoTime()

private var surface: Surface? = null
Expand Down Expand Up @@ -187,8 +187,5 @@ internal class SimpleVideoEncoder(
internal data class MuxerConfig(
val file: File,
val recorderConfig: ScreenshotRecorderConfig,
val bitrate: Int = 20_000,
val frameRate: Float = recorderConfig.frameRate.toFloat(),
val mimeType: String = MediaFormat.MIMETYPE_VIDEO_AVC,
val frameMuxer: SimpleFrameMuxer = SimpleMp4FrameMuxer(file.absolutePath, frameRate)
val mimeType: String = MediaFormat.MIMETYPE_VIDEO_AVC
)
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class ReplayCacheTest {
frameRate: Int,
framesToEncode: Int = 0
): ReplayCache {
val recorderConfig = ScreenshotRecorderConfig(100, 200, 1f, frameRate)
val recorderConfig = ScreenshotRecorderConfig(100, 200, 1f, frameRate = frameRate, bitRate = 20_000)
options.run {
cacheDirPath = dir?.newFolder()?.absolutePath
}
Expand All @@ -45,9 +45,7 @@ class ReplayCacheTest {
options,
MuxerConfig(
file = videoFile,
recorderConfig = recorderConfig,
frameRate = recorderConfig.frameRate.toFloat(),
bitrate = 20 * 1000
recorderConfig = recorderConfig
),
onClose = {
encodeFrame(framesToEncode, frameRate, size = 0, flags = MediaCodec.BUFFER_FLAG_END_OF_STREAM)
Expand Down
20 changes: 20 additions & 0 deletions sentry/api/sentry.api
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,12 @@ public abstract interface class io/sentry/EventProcessor {
public fun process (Lio/sentry/protocol/SentryTransaction;Lio/sentry/Hint;)Lio/sentry/protocol/SentryTransaction;
}

public final class io/sentry/ExperimentalOptions {
public fun <init> ()V
public fun getReplayOptions ()Lio/sentry/SentryReplayOptions;
public fun setReplayOptions (Lio/sentry/SentryReplayOptions;)V
}

public final class io/sentry/ExternalOptions {
public fun <init> ()V
public fun addBundleId (Ljava/lang/String;)V
Expand Down Expand Up @@ -2315,6 +2321,7 @@ public class io/sentry/SentryOptions {
public fun getTransportFactory ()Lio/sentry/ITransportFactory;
public fun getTransportGate ()Lio/sentry/transport/ITransportGate;
public final fun getViewHierarchyExporters ()Ljava/util/List;
public fun get_experimental ()Lio/sentry/ExperimentalOptions;
public fun isAttachServerName ()Z
public fun isAttachStacktrace ()Z
public fun isAttachThreads ()Z
Expand Down Expand Up @@ -2533,6 +2540,19 @@ public final class io/sentry/SentryReplayEvent$ReplayType$Deserializer : io/sent
public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object;
}

public final class io/sentry/SentryReplayOptions {
public fun <init> ()V
public fun <init> (Ljava/lang/Double;Ljava/lang/Double;)V
public fun getBitRate ()I
public fun getErrorReplayDuration ()J
public fun getErrorSampleRate ()Ljava/lang/Double;
public fun getFrameRate ()I
public fun getSessionSampleRate ()Ljava/lang/Double;
public fun getSessionSegmentDuration ()J
public fun setErrorSampleRate (Ljava/lang/Double;)V
public fun setSessionSampleRate (Ljava/lang/Double;)V
}

public final class io/sentry/SentrySpanStorage {
public fun get (Ljava/lang/String;)Lio/sentry/ISpan;
public static fun getInstance ()Lio/sentry/SentrySpanStorage;
Expand Down
16 changes: 16 additions & 0 deletions sentry/src/main/java/io/sentry/ExperimentalOptions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.sentry;

import org.jetbrains.annotations.NotNull;

public final class ExperimentalOptions {
private @NotNull SentryReplayOptions replayOptions = new SentryReplayOptions();

@NotNull
public SentryReplayOptions getReplayOptions() {
return replayOptions;
}

public void setReplayOptions(final @NotNull SentryReplayOptions replayOptions) {
this.replayOptions = replayOptions;
}
}
7 changes: 7 additions & 0 deletions sentry/src/main/java/io/sentry/SentryOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,8 @@ public class SentryOptions {
*/
private int profilingTracesHz = 101;

private final @NotNull ExperimentalOptions _experimental = new ExperimentalOptions();

/**
* Adds an event processor
*
Expand Down Expand Up @@ -2274,6 +2276,11 @@ public void setSessionFlushTimeoutMillis(final long sessionFlushTimeoutMillis) {
this.sessionFlushTimeoutMillis = sessionFlushTimeoutMillis;
}

@NotNull
public ExperimentalOptions get_experimental() {
return _experimental;
}

/** The BeforeSend callback */
public interface BeforeSendCallback {

Expand Down
Loading

0 comments on commit baefbb0

Please sign in to comment.