-
Notifications
You must be signed in to change notification settings - Fork 61
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
RUM-6093:Add TimeBank in session replay recorder for dynamic optimisa…
…tion
- Loading branch information
1 parent
679de59
commit fe0410a
Showing
6 changed files
with
247 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
50 changes: 50 additions & 0 deletions
50
.../src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/RecordingTimeBank.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
/* | ||
* 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 | ||
|
||
import java.util.concurrent.TimeUnit | ||
import kotlin.math.min | ||
|
||
/** | ||
* Implementation of [TimeBank], it maintains a time balance which can be consumed for session replay recording. | ||
* Every time the session replay recording task tries to execute, [updateAndCheck] is called to increase the time | ||
* balance according to the time passed since last call, and check if the balance is positive to execute the task. | ||
*/ | ||
internal class RecordingTimeBank( | ||
private val maxTimeBalancePerSecondInMs: Long = DEFAULT_MAX_TIME_BALANCE_PER_SEC_IN_MS | ||
) : TimeBank { | ||
|
||
@Volatile | ||
private var recordingTimeBalanceInNano = TimeUnit.MILLISECONDS.toNanos(maxTimeBalancePerSecondInMs) | ||
|
||
@Volatile | ||
private var lastStartTime: Long = 0 | ||
|
||
override fun consume(executionTime: Long) { | ||
recordingTimeBalanceInNano -= executionTime | ||
} | ||
|
||
override fun updateAndCheck(timestamp: Long): Boolean { | ||
increaseTimeBank(timestamp) | ||
lastStartTime = timestamp | ||
return recordingTimeBalanceInNano >= 0 | ||
} | ||
|
||
private fun increaseTimeBank(timestamp: Long) { | ||
val timePassedSinceLastExecution = timestamp - lastStartTime | ||
// The factor of balance increasing by time. if increasing 100ms balance in the bank takes 1000ms, then the | ||
// factor will be 100ms/1000ms = 0.1f | ||
val balanceFactor = maxTimeBalancePerSecondInMs.toFloat() / TimeUnit.SECONDS.toMillis(1) | ||
recordingTimeBalanceInNano += (timePassedSinceLastExecution * balanceFactor).toLong() | ||
recordingTimeBalanceInNano = | ||
min(TimeUnit.MILLISECONDS.toNanos(maxTimeBalancePerSecondInMs), recordingTimeBalanceInNano) | ||
} | ||
|
||
companion object { | ||
private const val DEFAULT_MAX_TIME_BALANCE_PER_SEC_IN_MS = 100L | ||
} | ||
} |
26 changes: 26 additions & 0 deletions
26
...on-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/TimeBank.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
/* | ||
* 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 | ||
|
||
import com.datadog.tools.annotation.NoOpImplementation | ||
|
||
@NoOpImplementation | ||
internal interface TimeBank { | ||
|
||
/** | ||
* Called to consume execution time from the bank. | ||
*/ | ||
fun consume(executionTime: Long) | ||
|
||
/** | ||
* Called to update time bank balance and check if the given timestamp | ||
* is allowed according to the current time balance. | ||
* | ||
* @return true if the given timestamp is allowed by time bank to execute a task, false otherwise. | ||
*/ | ||
fun updateAndCheck(timestamp: Long): Boolean | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
133 changes: 133 additions & 0 deletions
133
...eplay/src/test/kotlin/com/datadog/android/sessionreplay/recorder/RecordingTimeBankTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
/* | ||
* 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.recorder | ||
|
||
import com.datadog.android.sessionreplay.forge.ForgeConfigurator | ||
import com.datadog.android.sessionreplay.internal.recorder.RecordingTimeBank | ||
import com.datadog.android.sessionreplay.internal.recorder.TimeBank | ||
import fr.xgouchet.elmyr.Forge | ||
import fr.xgouchet.elmyr.junit5.ForgeConfiguration | ||
import fr.xgouchet.elmyr.junit5.ForgeExtension | ||
import org.assertj.core.api.Assertions.assertThat | ||
import org.junit.jupiter.api.BeforeEach | ||
import org.junit.jupiter.api.Test | ||
import org.junit.jupiter.api.extension.ExtendWith | ||
import org.junit.jupiter.api.extension.Extensions | ||
import org.mockito.junit.jupiter.MockitoExtension | ||
import org.mockito.junit.jupiter.MockitoSettings | ||
import org.mockito.quality.Strictness | ||
import java.util.concurrent.TimeUnit | ||
|
||
@Extensions( | ||
ExtendWith(MockitoExtension::class), | ||
ExtendWith(ForgeExtension::class) | ||
) | ||
@MockitoSettings(strictness = Strictness.LENIENT) | ||
@ForgeConfiguration(ForgeConfigurator::class) | ||
class RecordingTimeBankTest { | ||
|
||
private lateinit var recordingTimeBank: TimeBank | ||
|
||
@BeforeEach | ||
fun `set up`() { | ||
recordingTimeBank = RecordingTimeBank(TEST_MAX_BALANCE_IN_MS) | ||
} | ||
|
||
@Test | ||
fun `M allow the first execution W check`(forge: Forge) { | ||
// Given | ||
val timestamp = forge.aLong(min = 0) | ||
|
||
// When | ||
val actual = recordingTimeBank.updateAndCheck(timestamp) | ||
|
||
// Then | ||
assertThat(actual).isEqualTo(true) | ||
} | ||
|
||
@Test | ||
fun `M skip the next execution W previous consume out the balance`(forge: Forge) { | ||
// Given | ||
val firstTimestamp = forge.aLong(min = 0) | ||
val firstExecutionTime = forge.aLong( | ||
min = TimeUnit.MILLISECONDS.toNanos(TEST_MAX_BALANCE_IN_MS), | ||
max = TimeUnit.MILLISECONDS.toNanos(TEST_MAX_BALANCE_IN_MS) * 100 | ||
) | ||
val interval = forge.aLong(min = 0, max = firstExecutionTime) | ||
val secondTimestamp = forge.aLong( | ||
min = firstTimestamp + firstExecutionTime, | ||
max = firstTimestamp + firstExecutionTime + interval | ||
) | ||
|
||
// When | ||
recordingTimeBank.updateAndCheck(firstTimestamp) | ||
recordingTimeBank.consume(firstExecutionTime) | ||
val actual = recordingTimeBank.updateAndCheck(secondTimestamp) | ||
|
||
// Then | ||
assertThat(actual).isEqualTo(false) | ||
} | ||
|
||
@Test | ||
fun `M allow the next execution W balance is recovery`(forge: Forge) { | ||
// Given | ||
val firstTimestamp = forge.aLong(min = 0) | ||
val firstExecutionTime = forge.aLong( | ||
min = TimeUnit.MILLISECONDS.toNanos(TEST_MAX_BALANCE_IN_MS), | ||
max = TimeUnit.MILLISECONDS.toNanos(TEST_MAX_BALANCE_IN_MS) * 100 | ||
) | ||
val interval = forge.aLong(min = 0, max = firstExecutionTime) | ||
val secondTimestamp = forge.aLong( | ||
min = firstTimestamp + firstExecutionTime, | ||
max = firstTimestamp + firstExecutionTime + interval | ||
) | ||
|
||
val thirdTimestamp = | ||
forge.aLong( | ||
min = secondTimestamp + firstExecutionTime / | ||
((TimeUnit.SECONDS.toMillis(1) / TEST_MAX_BALANCE_IN_MS)) | ||
) | ||
// When | ||
recordingTimeBank.updateAndCheck(firstTimestamp) | ||
recordingTimeBank.consume(firstExecutionTime) | ||
recordingTimeBank.updateAndCheck(secondTimestamp) | ||
val actual = recordingTimeBank.updateAndCheck(thirdTimestamp) | ||
|
||
// Then | ||
assertThat(actual).isEqualTo(true) | ||
// | ||
} | ||
|
||
@Test | ||
fun `M allow everything W set max balance to 1000ms per sec`(forge: Forge) { | ||
recordingTimeBank = RecordingTimeBank(1000) | ||
|
||
// Given | ||
val firstTimestamp = forge.aLong(min = 0) | ||
val firstExecutionTime = forge.aLong( | ||
min = TimeUnit.MILLISECONDS.toNanos(TEST_MAX_BALANCE_IN_MS), | ||
max = TimeUnit.MILLISECONDS.toNanos(TEST_MAX_BALANCE_IN_MS) * 100 | ||
) | ||
val interval = forge.aLong(min = 0, max = firstExecutionTime) | ||
val secondTimestamp = forge.aLong( | ||
min = firstTimestamp + firstExecutionTime, | ||
max = firstTimestamp + firstExecutionTime + interval | ||
) | ||
|
||
// When | ||
recordingTimeBank.updateAndCheck(firstTimestamp) | ||
recordingTimeBank.consume(firstExecutionTime) | ||
val actual = recordingTimeBank.updateAndCheck(secondTimestamp) | ||
|
||
// Then | ||
assertThat(actual).isEqualTo(true) | ||
} | ||
|
||
companion object { | ||
private const val TEST_MAX_BALANCE_IN_MS = 100L | ||
} | ||
} |