The following code allows us to measure (and keep track of) the exchange rate (whether upload or download), and the time needed to exchange n additional bytes (assuming the rate is stable).


open System

type RateMeter =
  { mutable ObservationPeriodBeginning : int64
    mutable LastComputation : int64
    mutable NextExpectedComputation : int64
    mutable MaxObservationPeriod : float
    mutable Exchanged : int64
    mutable Rate : float
  }

[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module RateMeter =
  let ticksPerSecond = 10000000L

  let exactElapsedSeconds (t1 : int64) t2 = float (t2 - t1) / float ticksPerSecond 

  type t = RateMeter

  let create maxObservationPeriod initialDelay =
    if initialDelay < 0.0 then
      invalidArg "initialDelay" "delay was negative"
    if maxObservationPeriod < 0.0 then
      invalidArg "maxObservationPeriod" "maximum observation period was negative"
    let initialDelay = int64 (initialDelay * float ticksPerSecond)
    let now = DateTime.Now.Ticks
    let obsPeriodBeginning = now  - initialDelay
    { ObservationPeriodBeginning = obsPeriodBeginning
      LastComputation = obsPeriodBeginning
      NextExpectedComputation = now + initialDelay
      MaxObservationPeriod = maxObservationPeriod
      Exchanged = 0L
      Rate = 0.0
    }  

  let minNextExpectedComputationDelay = 5.0

  let update exchangedBytes rm =
    if exchangedBytes < 0 then
      invalidArg "exchangedBytes" "number of exchanged bytes was negative"
    let now = DateTime.Now.Ticks
    rm.Exchanged <- rm.Exchanged + int64 exchangedBytes
    if now >= rm.NextExpectedComputation || exchangedBytes <> 0 then
      //Time elapsed from the beginning of the period until now,
      //i.e. the period over which the rate is computed
      let elapsedTotal = exactElapsedSeconds rm.ObservationPeriodBeginning now
      //Number of bytes computed between the beginning of the observation period
      //and the last computation
      //Rate x Time = Some amount
      let elapsedBeforeLastComputation =
        exactElapsedSeconds rm.ObservationPeriodBeginning rm.LastComputation
      let presumablyDownloadedBeforeLastComputation =
        rm.Rate * elapsedBeforeLastComputation
      //The total number of bytes = the amount of bytes downloaded between
      //the beginning of the observation period and the last computation,
      //and the amount of bytes downloaded since then
      let presumablyDownloadedTotal =
        presumablyDownloadedBeforeLastComputation + float exchangedBytes
      //Divide the total number of bytes by the time elapsed to get the adjusted rate
      rm.Rate <- presumablyDownloadedTotal / elapsedTotal
      rm.LastComputation <- now
      //If too much time has elapsed since the last computation, bring the beginning
      //of the observation period closer
      let newObservationPeriodBeginning =
        max rm.ObservationPeriodBeginning (now - int64 rm.MaxObservationPeriod)
      rm.ObservationPeriodBeginning <- newObservationPeriodBeginning
      //Wait to download the same number of bytes to recompute the rate...
      let toWait =
        let rate = max rm.Rate 0.0001
        float exchangedBytes / rate
      //...or at least 5 seconds
      rm.NextExpectedComputation <- now + (int64 <| min toWait minNextExpectedComputationDelay)

  let timeLeft dataLeft rm =
    if dataLeft < 0 then invalidArg "dataLeft" "data left was negative"
    float dataLeft / rm.Rate

Comments are closed.