-- | Rate calculation. module RateCalc ( -- * Types Rate -- * Interface , new , update , extractCount , extractRate ) where import Data.Time.Clock -- | A Rate is a record of information used for calculating the rate data Rate = Rate { rate :: Double -- ^ The current rate , bytes :: Integer -- ^ The amount of bytes transferred since last rate extraction , count :: Integer -- ^ The amount of bytes transferred since last count extraction , nextExpected :: UTCTime -- ^ When is the next rate update expected , lastExt :: UTCTime -- ^ When was the last rate update , rateSince :: UTCTime -- ^ From where is the rate measured } fudge :: NominalDiffTime fudge = fromInteger 5 -- Seconds rateUpdate :: Integer rateUpdate = 5 * 1000 -- Millisecs maxRatePeriod :: NominalDiffTime maxRatePeriod = fromInteger 20 -- Seconds new :: UTCTime -> Rate new t = Rate { rate = 0.0 , bytes = 0 , count = 0 , nextExpected = addUTCTime fudge t , lastExt = addUTCTime (-fudge) t , rateSince = addUTCTime (-fudge) t } -- | The call @update n rt@ updates the rate structure @rt@ with @n@ new bytes update :: Integer -> Rate -> Rate update n rt = rt { bytes = bytes rt + n , count = count rt + n} -- | The call @extractRate t rt@ extracts the current rate from the rate structure and updates the rate -- structures internal book-keeping extractRate :: UTCTime -> Rate -> (Double, Rate) extractRate t rt = let oldWindow :: Double oldWindow = realToFrac $ diffUTCTime (lastExt rt) (rateSince rt) newWindow :: Double newWindow = realToFrac $ diffUTCTime t (rateSince rt) n = bytes rt r = (rate rt * oldWindow + (fromIntegral n)) / newWindow expectN = min 5 (round $ (fromIntegral n / (max r 0.0001))) in -- Update the rate and book-keep the missing pieces. The total is simply a built-in -- counter. The point where we expect the next update is pushed at most 5 seconds ahead -- in time. But it might come earlier if the rate is high. -- Last is updated with the current time. Finally, we move the windows earliest value -- forward if it is more than 20 seconds from now. (r, rt { rate = r , bytes = 0 , nextExpected = addUTCTime (fromInteger expectN) t , lastExt = t , rateSince = max (rateSince rt) (addUTCTime (-maxRatePeriod) t) }) -- | The call @extractCount rt@ extract the bytes transferred since last extraction extractCount :: Rate -> (Integer, Rate) extractCount rt = (count rt, rt { count = 0 })