ekg-core-0.1.2.0: System/Metrics/Distribution.hsc
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE UnboxedTuples #-}
{-# OPTIONS_GHC -funbox-strict-fields #-}
#include "distrib.h"
#include "MachDeps.h"
-- | This module defines a type for tracking statistics about a series
-- of events. An event could be handling of a request and the value
-- associated with the event -- the value you'd pass to 'add' -- could
-- be the amount of time spent serving that request (e.g. in
-- milliseconds). All operations are thread safe.
module System.Metrics.Distribution
( Distribution
, new
, add
, addN
, read
-- * Gathered statistics
, Stats
, mean
, variance
, count
, sum
, min
, max
) where
import Control.Monad (forM_, replicateM)
import Prelude hiding (max, min, read, sum)
import Foreign.Storable (sizeOf)
import GHC.Float
import GHC.Int (Int(..), Int64(..))
import GHC.IO
import GHC.Prim
import Data.Array
import System.Metrics.Distribution.Internal (Stats(..))
import System.Metrics.ThreadId
-- Three cases for these definitions
-- 64-bit, GHC has Int64
-- 64-bit, GHC doesn't have Int64
-- 32-bit
-- 64-bit machine, Int ~ Int64, do it the fast way:
#if SIZEOF_HSINT == 8
#if MIN_VERSION_base(4,17,0)
int64ToDouble :: Int64## -> Double##
int64ToDouble i = int2Double## (int64ToInt## i)
intToInt64 :: Int## -> Int64##
intToInt64 = intToInt64##
plusInt64 :: Int64## -> Int64## -> Int64##
plusInt64 = plusInt64##
eqInt64 :: Int64## -> Int64## -> Int##
eqInt64 = eqInt64##
readInt64Array :: MutableByteArray## d -> Int## -> State## d -> (## State## d, Int64## ##)
readInt64Array = readInt64Array##
writeInt64Array :: MutableByteArray## d -> Int## -> Int64## -> State## d -> State## d
writeInt64Array = writeInt64Array##
#else
int64ToDouble :: Int## -> Double##
int64ToDouble = int2Double##
intToInt64 :: Int## -> Int##
intToInt64 i = i
plusInt64 :: Int## -> Int## -> Int##
plusInt64 = (+##)
eqInt64 :: Int## -> Int## -> Int##
eqInt64 = (==##)
readInt64Array :: MutableByteArray## d -> Int## -> State## d -> (## State## d, Int## ##)
readInt64Array = readIntArray##
writeInt64Array :: MutableByteArray## d -> Int## -> Int## -> State## d -> State## d
writeInt64Array = writeIntArray##
#endif
#else
-- NB: I've only tested these with the WASM backend:
intToInt64 :: Int## -> Int64##
intToInt64 = intToInt64##
plusInt64 :: Int64## -> Int64## -> Int64##
plusInt64 = plusInt64##
eqInt64 :: Int64## -> Int64## -> Int##
eqInt64 = eqInt64##
readInt64Array :: MutableByteArray## d -> Int## -> State## d -> (## State## d, Int64## ##)
readInt64Array = readInt64Array##
writeInt64Array :: MutableByteArray## d -> Int## -> Int64## -> State## d -> State## d
writeInt64Array = writeInt64Array##
-- I don't know a better way on 32-bit machines...
int64ToDouble :: Int64## -> Double##
int64ToDouble i =
case fromIntegral (I64## i) of (D## d) -> d
#endif
-- | An metric for tracking events.
newtype Distribution = Distribution { unD :: Array Stripe }
newtype Stripe = Stripe { stripeD :: Distrib }
data Distrib = Distrib (MutableByteArray## RealWorld)
unI :: Int -> Int##
unI (I## x) = x
{-# INLINE unI #-}
distribLen :: Int
distribLen = (#size struct distrib)
lockPos :: Int
lockPos = div (#offset struct distrib, lock)
(sizeOf (undefined :: Int))
countPos :: Int
countPos = div (#offset struct distrib, count)
(sizeOf (undefined :: Int64))
meanPos :: Int
meanPos = div (#offset struct distrib, mean)
(sizeOf (undefined :: Double))
sumSqDeltaPos :: Int
sumSqDeltaPos = div (#offset struct distrib, sum_sq_delta)
(sizeOf (undefined :: Double))
sumPos :: Int
sumPos = div (#offset struct distrib, sum)
(sizeOf (undefined :: Double))
minPos :: Int
minPos = div (#offset struct distrib, min)
(sizeOf (undefined :: Double))
maxPos :: Int
maxPos = div (#offset struct distrib, max)
(sizeOf (undefined :: Double))
newDistrib :: IO Distrib
newDistrib = IO $ \s ->
case newByteArray## (unI distribLen) s of { (## s1, mba ##) ->
-- probably unnecessary
case atomicWriteIntArray## mba (unI lockPos) 0## s1 of { s2 ->
case writeInt64Array mba (unI countPos) (intToInt64 0##) s2 of { s3 ->
case writeDoubleArray## mba (unI meanPos) 0.0#### s3 of { s4 ->
case writeDoubleArray## mba (unI sumSqDeltaPos) 0.0#### s4 of { s5 ->
case writeDoubleArray## mba (unI sumPos) 0.0#### s5 of { s6 ->
case writeDoubleArray## mba (unI minPos) 0.0#### s6 of { s7 ->
case writeDoubleArray## mba (unI maxPos) 0.0#### s7 of { s8 ->
(## s8, Distrib mba ##) }}}}}}}}
newStripe :: IO Stripe
newStripe = do
d <- newDistrib
return $! Stripe
{ stripeD = d
}
-- | Number of lock stripes. Should be greater or equal to the number
-- of HECs.
numStripes :: Int
numStripes = 8
-- | Get the stripe to use for this thread.
myStripe :: Distribution -> IO Stripe
myStripe distrib = do
tid <- myCapability
return $! unD distrib `index` (tid `mod` numStripes)
------------------------------------------------------------------------
-- Exposed API
-- | Create a new distribution.
new :: IO Distribution
new = (Distribution . fromList numStripes) `fmap`
replicateM numStripes newStripe
-- | Add a value to the distribution.
add :: Distribution -> Double -> IO ()
add distrib val = addN distrib val 1
{-# INLINE spinLock #-}
spinLock :: MutableByteArray## RealWorld -> State## RealWorld -> State## RealWorld
spinLock mba = \s ->
case casIntArray## mba (unI lockPos) 0## 1## s of { (## s1, r ##) ->
case r of { 0## -> s1; _ ->
case yield## s1 of { s2 ->
spinLock mba s2 }}}
{-# INLINE spinUnlock #-}
spinUnlock :: MutableByteArray## RealWorld -> State## RealWorld -> State## RealWorld
spinUnlock mba = \s ->
case writeIntArray## mba (unI lockPos) 0## s of { s2 -> s2 }
-- | Add the same value to the distribution N times.
-- Mean and variance are computed according to
-- http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm
addN :: Distribution -> Double -> Int64 -> IO ()
addN distribution (D## val) (I64## n) = IO $ \s ->
case myStripe distribution of { (IO myStripe') ->
case myStripe' s of { (## s1, (Stripe (Distrib mba)) ##) ->
case spinLock mba s1 of { s2 ->
case readInt64Array mba (unI countPos) s2 of { (## s3, count ##) ->
case readDoubleArray## mba (unI meanPos) s3 of { (## s4, mean ##) ->
case readDoubleArray## mba (unI sumSqDeltaPos) s4 of { (## s5, sumSqDelta ##) ->
case readDoubleArray## mba (unI sumPos) s5 of { (## s6, dSum ##) ->
case readDoubleArray## mba (unI minPos) s6 of { (## s7, dMin ##) ->
case readDoubleArray## mba (unI maxPos) s7 of { (## s8, dMax ##) ->
case plusInt64 count n of { count' ->
case val -#### mean of { delta ->
case mean +#### ((int64ToDouble n) *#### delta /#### (int64ToDouble count')) of { mean' ->
case sumSqDelta +#### (delta *#### (val -#### mean') *#### (int64ToDouble n)) of { sumSqDelta' ->
case writeInt64Array mba (unI countPos) count' s8 of { s9 ->
case writeDoubleArray## mba (unI meanPos) mean' s9 of { s10 ->
case writeDoubleArray## mba (unI sumSqDeltaPos) sumSqDelta' s10 of { s11 ->
case writeDoubleArray## mba (unI sumPos) (dSum +#### val) s11 of { s12 ->
case (case val <#### dMin of { 0## -> dMin; _ -> val }) of { dMin' ->
case (case val >#### dMax of { 0## -> dMax; _ -> val }) of { dMax' ->
case writeDoubleArray## mba (unI minPos) dMin' s12 of { s13 ->
case writeDoubleArray## mba (unI maxPos) dMax' s13 of { s14 ->
case spinUnlock mba s14 of { s15 ->
(## s15, () ##) }}}}}}}}}}}}}}}}}}}}}}
-- | Combine 'b' with 'a', writing the result in 'a'. Takes the lock of
-- 'b' while combining, but doesn't otherwise modify 'b'. 'a' is
-- assumed to not be used concurrently.
-- See also:
-- http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Parallel_algorithm
combine :: Distrib -> Distrib -> IO ()
combine (Distrib bMBA) (Distrib aMBA) = IO $ \s ->
case spinLock bMBA s of { s1 ->
case readInt64Array aMBA (unI countPos) s1 of { (## s2, aCount ##) ->
case readInt64Array bMBA (unI countPos) s2 of { (## s3, bCount ##) ->
case plusInt64 aCount bCount of { count' ->
case readDoubleArray## aMBA (unI meanPos) s3 of { (## s4, aMean ##) ->
case readDoubleArray## bMBA (unI meanPos) s4 of { (## s5, bMean ##) ->
case bMean -#### aMean of { delta ->
case ( (((int64ToDouble aCount) *#### aMean) +#### ((int64ToDouble bCount) *#### bMean))
/#### (int64ToDouble count')
) of { mean' ->
case readDoubleArray## aMBA (unI sumSqDeltaPos) s5 of { (## s6, aSumSqDelta ##) ->
case readDoubleArray## bMBA (unI sumSqDeltaPos) s6 of { (## s7, bSumSqDelta ##) ->
case ( aSumSqDelta
+#### bSumSqDelta
+#### ( delta
*#### delta
*#### ( (int64ToDouble aCount) *#### (int64ToDouble bCount)
/#### (int64ToDouble count')
)
)
) of { sumSqDelta' ->
case writeInt64Array aMBA (unI countPos) count' s7 of { s8 ->
case (case eqInt64 count' (intToInt64 0##) of { 0## -> mean'; _ -> 0.0#### }) of { writeMean ->
case writeDoubleArray## aMBA (unI meanPos) writeMean s8 of { s9 ->
case writeDoubleArray## aMBA (unI sumSqDeltaPos) sumSqDelta' s9 of { s10 ->
case readDoubleArray## aMBA (unI sumPos) s10 of { (## s11, aSum ##) ->
case readDoubleArray## bMBA (unI sumPos) s11 of { (## s12, bSum ##) ->
case writeDoubleArray## aMBA (unI sumPos) (aSum +#### bSum) s12 of { s13 ->
case readDoubleArray## bMBA (unI minPos) s13 of { (## s14, bMin ##) ->
case writeDoubleArray## aMBA (unI minPos) bMin s14 of { s15 ->
-- This is slightly hacky, but ok: see
-- 813aa426be78e8abcf1c7cdd43433bcffa07828e
case readDoubleArray## bMBA (unI maxPos) s15 of { (## s16, bMax ##) ->
case writeDoubleArray## aMBA (unI maxPos) bMax s16 of { s17 ->
case spinUnlock bMBA s17 of { s18 ->
(## s18, () ##) }}}}}}}}}}}}}}}}}}}}}}}
-- | Get the current statistical summary for the event being tracked.
read :: Distribution -> IO Stats
read distrib = do
result@(Distrib mba) <- newDistrib
forM_ (toList $ unD distrib) $ \(Stripe d) ->
combine d result
IO $ \s ->
case readInt64Array mba (unI countPos) s of { (## s1, count ##) ->
case readDoubleArray## mba (unI meanPos) s1 of { (## s2, mean ##) ->
case readDoubleArray## mba (unI sumSqDeltaPos) s2 of { (## s3, sumSqDelta ##) ->
case readDoubleArray## mba (unI sumPos) s3 of { (## s4, dSum ##) ->
case readDoubleArray## mba (unI minPos) s4 of { (## s5, dMin ##) ->
case readDoubleArray## mba (unI maxPos) s5 of { (## s6, dMax ##) ->
(## s6
, Stats { mean = (D## mean)
, variance = if (I64## count) == 0 then 0.0
else (D## sumSqDelta) / (D## (int64ToDouble count))
, count = (I64## count)
, sum = (D## dSum)
, min = (D## dMin)
, max = (D## dMax)
}
##) }}}}}}