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