Skip to content

Instantly share code, notes, and snippets.

@AniketSK
Last active September 2, 2019 11:57
Show Gist options
  • Save AniketSK/6a10d942fdcea2d5db4e0e7725538cfb to your computer and use it in GitHub Desktop.
Save AniketSK/6a10d942fdcea2d5db4e0e7725538cfb to your computer and use it in GitHub Desktop.
A demonstration of how a speed calculation could be done from the OnProgressListener of https://github.com/MindorksOpenSource/PRDownloader Here's how it works: 1. To make an accurate estimate of the speed, you need a certain number of progress updates, you can choose what number this is. 2. If we haven't reached that number of observations, retu…
package com.downloader
import org.junit.Test
import java.util.*
import java.util.concurrent.TimeUnit
class OnProgressSpeedListenerTest {
@Test
fun `speed is not calculated until the required number of samples have been received`() {
var speed : Long? = null
val speedCalc = DownloadSpeedCalculator(2, onSpeedUpdated = { s -> speed = s })
val times = 1
repeat(times) { i -> speedCalc.onProgress(TimeStampedProgress(i * 500L, Progress(i * 1000L, times * 10000L))) }
assert(speed == null)
}
@Test
fun `speed is calculated correctly`() {
var speed : Long? = 0L
val speedCalc = DownloadSpeedCalculator(2, onSpeedUpdated = { s -> speed = s })
val times = 2
repeat(times) { i -> speedCalc.onProgress(TimeStampedProgress(i * 500L, Progress(i * 1000L, times * 10000L))) }
assert(speed == 2L)
}
}
class DownloadSpeedCalculator(private val maxElements: Int = 2, private val onSpeedUpdated : (Long?) -> (Unit) ) : TimeStampedProgressListener(2, TimeUnit.SECONDS) {
private val allProgressEvents = ArrayDeque<TimeStampedProgress>(maxElements)
override fun onProgress(timeStampedProgress: TimeStampedProgress) {
allProgressEvents.apply {
push(timeStampedProgress)
if (size > maxElements) {
removeLast()
}
}
onSpeedUpdated(getAverageSpeedBytesPerMillisecond())
}
/**
* Returns the current speed if it has received the specified minimum number of Progress values to
* make a reasonable speed determination,
* or null if enough Progress values have not yet been received.
*/
private fun getAverageSpeedBytesPerMillisecond(): Long? = allProgressEvents.takeIf {
it.size == maxElements
}?.let {
Total(allProgressEvents.last.progress.currentBytes - allProgressEvents.first.progress.currentBytes,
allProgressEvents.first.lastTimeStamp, allProgressEvents.last.lastTimeStamp).getAverageSpeed()
}
private data class Total(
val totalBytes: Long = 0,
val startTime: Long = 0,
val endTime: Long = 0
) {
fun getAverageSpeed() = totalBytes / (endTime - startTime)
}
}
abstract class TimeStampedProgressListener(updateInterval: Long, updateIntervalUnit: TimeUnit) : OnProgressListener {
private val INITIAL = 0L
private val intervalMillis = TimeUnit.MILLISECONDS.convert(updateInterval, updateIntervalUnit)
private var lastUpdateTime: Long = INITIAL
final override fun onProgress(progress: Progress?) {
defaultConditionalProgress(progress)
}
private fun defaultConditionalProgress(progress: Progress?) {
if (progress == null)
return
val now = now()
if (now - lastUpdateTime >= intervalMillis) {
lastUpdateTime = now
onProgress(TimeStampedProgress(now, progress))
}
}
abstract fun onProgress(progress: TimeStampedProgress)
private fun now(): Long = System.currentTimeMillis()
}
@AniketSK
Copy link
Author

AniketSK commented Sep 2, 2019

A demonstration of how a speed calculation could be done from the OnProgressListener of https://github.com/MindorksOpenSource/PRDownloader
Here's how it works:

  1. To make an accurate estimate of the speed, you need a certain number of progress updates, you can choose what number this is.
  2. If we haven't reached that number of observations, return null.
  3. If we have reached that number of observations, then take the bytes downloaded by the earliest of those, subtract from the bytes downloaded by the latest of time and divide that by the amount of time that has passed.
    This gives you average of bytes/millisecond, over that interval.
  4. Then we just make sure the timing information is added by another abstract class.

This is a junit test, to actually use it, you'd take the classes in it and put them in separate files, then create a subclass of DownloadSpeedQueue as an OnProgressListener, pass the time intervals you'd like to measure in, and the onSpeedUpdated function.
onSpeedUpdated will receive bytes per millisecond when it's calculated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment