content: module Events.SparkStats
    ( SparkStats(..)
    , initial, create, rescale, aggregate, agEx
    ) where
  
  import Data.Word (Word64)
  
  -- | Sparks change state. Each state transition process has a duration.
  -- Spark statistics, for a given duration, record the spark transition rate
  -- (the number of sparks that enter a given state within the interval)
  -- and the absolute mean, maximal and minimal number of sparks
  -- in the spark pool within the duration.
  data SparkStats =
    SparkStats { rateCreated, rateDud, rateOverflowed,
                 rateConverted, rateFizzled, rateGCd,
                 meanPool, maxPool, minPool :: {-# UNPACK #-}!Double }
    deriving (Show, Eq)
  
  -- | Initial, default value of spark stats, at the start of runtime,
  -- before any spark activity is recorded.
  initial :: SparkStats
  initial = SparkStats 0 0 0 0 0 0 0 0 0
  
  -- | Create spark stats for a duration, given absolute
  -- numbers of sparks in all categories at the start and end of the duration.
  -- The units for spark transitions (first 6 counters) is [spark/duration]:
  -- the fact that intervals may have different lenghts is ignored here.
  -- The units for the pool stats are just [spark].
  -- The values in the second counter have to be greater or equal
  -- to the values in the first counter, except for the spark pool size.
  -- For pool size, we take into account only the first sample,
  -- to visualize more detail at high zoom levels, at the cost
  -- of a slight shift of the graph. Mathematically, this corresponds
  -- to taking the initial durations as centered around samples,
  -- but to have the same tree for rates and pool sizes, we then have
  -- to shift the durations by half interval size to the right
  -- (which would be neglectable if the interval was small and even).
  create :: (Word64, Word64, Word64, Word64, Word64, Word64, Word64)
         -> (Word64, Word64, Word64, Word64, Word64, Word64, Word64)
         -> SparkStats
  create (crt1, dud1, ovf1, cnv1, fiz1, gcd1, remaining1)
         (crt2, dud2, ovf2, cnv2, fiz2, gcd2, _remaining2) =
    let (crt, dud, ovf, cnv, fiz, gcd) =
          (fromIntegral $ crt2 - crt1,
           fromIntegral $ dud2 - dud1,
           fromIntegral $ ovf2 - ovf1,
           fromIntegral $ cnv2 - cnv1,
           fromIntegral $ fiz2 - fiz1,
           fromIntegral $ gcd2 - gcd1)
        p = fromIntegral remaining1
    in SparkStats crt dud ovf cnv fiz gcd p p p
  
  -- | Reduce a list of spark stats; spark pool stats are overwritten.
  foldStats :: (Double -> Double -> Double)
            -> Double -> Double -> Double
            -> [SparkStats] -> SparkStats
  foldStats f meanP maxP minP l
    = SparkStats
        (foldr f 0 (map rateCreated l))
        (foldr f 0 (map rateDud l))
        (foldr f 0 (map rateOverflowed l))
        (foldr f 0 (map rateConverted l))
        (foldr f 0 (map rateFizzled l))
        (foldr f 0 (map rateGCd l))
        meanP maxP minP
  
  -- | Rescale the spark transition stats, e.g., to change their units.
  rescale :: Double -> SparkStats -> SparkStats
  rescale scale s =
    let f w _ = scale * w
    in foldStats f (meanPool s) (maxPool s) (minPool s) [s]
  
  -- | Derive spark stats for an interval from a list of spark stats,
  -- in reverse chronological order, of consecutive subintervals
  -- that sum up to the original interval.
  aggregate :: [SparkStats] -> SparkStats
  aggregate [] = error "aggregate"
  aggregate [s] = s  -- optimization
  aggregate l =
    let meanP = sum (map meanPool l) / fromIntegral (length l) -- TODO: inaccurate
        maxP  = maximum (map maxPool l)
        minP  = minimum (map minPool l)
    in foldStats (+) meanP maxP minP l
  
  -- | Extrapolate spark stats from previous data.
  -- Absolute pools size values extrapolate by staying constant,
  -- rates of change of spark status extrapolate by dropping to 0
  -- (which corresponds to absolute numbers of sparks staying constant).
  extrapolate :: SparkStats -> SparkStats
  extrapolate s =
    let f w _ = 0 * w
    in foldStats f (meanPool s) (maxPool s) (minPool s) [s]
  
  -- | Aggregate, if any data provided. Extrapolate from previous data, otherwise.
  -- In both cases, the second component is the new choice of "previous data".
  -- The list of stats is expected in reverse chronological order,
  -- as for aggregate.
  agEx :: [SparkStats] -> SparkStats -> (SparkStats, SparkStats)
  agEx [] s = (extrapolate s, s)
  agEx l@(s:_) _ = (aggregate l, s)
  