Skip to content

Commit

Permalink
Merge pull request #84 from Parsely/engagment_interval_calculator_tests
Browse files Browse the repository at this point in the history
  • Loading branch information
wzieba authored Oct 27, 2023
2 parents 1caf06f + 7d0fc02 commit 2bec4fe
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 28 deletions.
10 changes: 10 additions & 0 deletions parsely/src/main/java/com/parsely/parselyandroid/Clock.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.parsely.parselyandroid

import java.util.Calendar
import java.util.TimeZone
import kotlin.time.Duration.Companion.milliseconds

open class Clock {
open val now
get() = Calendar.getInstance(TimeZone.getTimeZone("UTC")).timeInMillis.milliseconds
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ class EngagementManager {
private TimerTask waitingTimerTask;
private long latestDelayMillis, totalTime;
private Calendar startTime;
private final UpdateEngagementIntervalCalculator intervalCalculator;
private final HeartbeatIntervalCalculator intervalCalculator;

public EngagementManager(
ParselyTracker parselyTracker,
Timer parentTimer,
long intervalMillis,
Map<String, Object> baseEvent,
UpdateEngagementIntervalCalculator intervalCalculator
HeartbeatIntervalCalculator intervalCalculator
) {
this.parselyTracker = parselyTracker;
this.baseEvent = baseEvent;
Expand Down Expand Up @@ -70,7 +70,7 @@ private void scheduleNextExecution(long delay) {
TimerTask task = new TimerTask() {
public void run() {
doEnqueue(scheduledExecutionTime());
latestDelayMillis = intervalCalculator.updateLatestInterval(startTime);
latestDelayMillis = intervalCalculator.calculate(startTime);
scheduleNextExecution(latestDelayMillis);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.parsely.parselyandroid

import java.util.Calendar
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds

internal open class HeartbeatIntervalCalculator(private val clock: Clock) {

open fun calculate(startTime: Calendar): Long {
val startTimeDuration = startTime.time.time.milliseconds
val nowDuration = clock.now

val totalTrackedTime = nowDuration - startTimeDuration
val totalWithOffset = totalTrackedTime + OFFSET_MATCHING_BASE_INTERVAL
val newInterval = totalWithOffset * BACKOFF_PROPORTION
val clampedNewInterval = minOf(MAX_TIME_BETWEEN_HEARTBEATS, newInterval)
return clampedNewInterval.inWholeMilliseconds
}

companion object {
const val BACKOFF_PROPORTION = 0.3
val OFFSET_MATCHING_BASE_INTERVAL = 35.seconds
val MAX_TIME_BETWEEN_HEARTBEATS = 15.minutes
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public class ParselyTracker {
private String lastPageviewUuid = null;
@NonNull
private final EventsBuilder eventsBuilder;
@NonNull final UpdateEngagementIntervalCalculator intervalCalculator = new UpdateEngagementIntervalCalculator();
@NonNull final HeartbeatIntervalCalculator intervalCalculator = new HeartbeatIntervalCalculator(new Clock());

/**
* Create a new ParselyTracker instance.
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ internal class EngagementManagerTest {
}
}

class FakeIntervalCalculator : UpdateEngagementIntervalCalculator() {
override fun updateLatestInterval(startTime: Calendar): Long {
class FakeIntervalCalculator : HeartbeatIntervalCalculator(Clock()) {
override fun calculate(startTime: Calendar): Long {
return DEFAULT_INTERVAL_MILLIS
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.parsely.parselyandroid

import com.parsely.parselyandroid.HeartbeatIntervalCalculator.Companion.MAX_TIME_BETWEEN_HEARTBEATS
import java.util.Calendar
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Test

internal class HeartbeatIntervalCalculatorTest {

private lateinit var sut: HeartbeatIntervalCalculator
private val fakeClock = FakeClock()

@Before
fun setUp() {
sut = HeartbeatIntervalCalculator(fakeClock)
}

@Test
fun `given the same time of start and current time, when calculating interval, return offset times backoff proportion`() {
// given
fakeClock.fakeNow = Duration.ZERO
val startTime = Calendar.getInstance().apply {
timeInMillis = 0
}

// when
val result = sut.calculate(startTime)

// then
// ((currentTime + offset) * backoff) and then in milliseconds
// (0 + 35) * 0.3 * 1000 = 10500
assertThat(result).isEqualTo(10500)
}

@Test
fun `given a time that will cause the interval to surpass the MAX_TIME_BETWEEN_HEARTBEATS, when calculating interval, then return the MAX_TIME_BETWEEN_HEARTBEATS`() {
// given
// "excessiveTime" is a calculated point in time where the resulting interval would
// surpass MAX_TIME_BETWEEN_HEARTBEATS
// (currentTime + offset) * backoff = max
// currentTime = (max / backoff) - offset, so
// (15 minutes / 0.3) - 35 seconds = 2965 seconds. Add 1 second to be over the limit
val excessiveTime = 2965.seconds + 1.seconds
fakeClock.fakeNow = excessiveTime
val startTime = Calendar.getInstance().apply {
timeInMillis = 0
}

// when
val result = sut.calculate(startTime)

// then
assertThat(result).isEqualTo(MAX_TIME_BETWEEN_HEARTBEATS.inWholeMilliseconds)
}

@Test
fun `given a specific time point, when updating latest interval, it correctly calculates the interval`() {
// given
val startTime = Calendar.getInstance().apply {
timeInMillis = 0
}
fakeClock.fakeNow = 2.seconds

// when
val result = sut.calculate(startTime)

// then
// ((currentTime + offset) * backoff) and then in milliseconds
// (2 + 35) * 0.3 * 1000 = 11100
assertThat(result).isEqualTo(11100)
}

class FakeClock : Clock() {
var fakeNow = Duration.ZERO

override val now: Duration
get() = fakeNow
}
}

0 comments on commit 2bec4fe

Please sign in to comment.