-
Notifications
You must be signed in to change notification settings - Fork 6
Fix heartbeats interval calculations, add unit tests for the calculator, rewrite to Kotlin #84
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
19d5a32
036d99b
8b1c72d
5325aaa
3a67a1d
79e4153
ecc4e19
9823815
f273ee1
424a168
cb5f40f
d151d73
cda6762
626d162
46c4053
0e6bec8
210bf32
7d0fc02
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 |
|---|---|---|
| @@ -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 | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
👍 |
||
| } | ||
| } | ||
This file was deleted.
| 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 | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Praise (❤️): Thanks for adding these comments and making it clear how these numbers are calculated. 🙇 |
||
| 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 | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Praise (❤️): Nice! |
||
|
|
||
| override val now: Duration | ||
| get() = fakeNow | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
x.seconds/minutes/hoursapproach, much more readable. Also, kudos for fixing the bugs of unwanted rounding (f273ee1)! 🥇Extra commit for .java > .kt renamesoption while committing this change. This will make it easier for anyone reviewing this change later-on as it will show an actual diffing of files instead of showing the Java file being deleted and a new Kotlin file being added.FYI: For more context on check this discussion I had with an external contributor a while back. Let me know your thoughts. 🙏
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for sharing about
Extra commitoption, TIL!