module Database.VCache.Stats
    ( VCacheStats(..)
    , vcacheStats
    ) where

import Data.IORef
import Control.Concurrent.MVar
import qualified Data.Map.Strict as Map

import Database.LMDB.Raw
import Database.VCache.Types


-- | Miscellaneous statistics for a VCache instance. These are not
-- necessarily consistent, current, or useful. But they can say a
-- a bit about the liveliness and health of a VCache system.
data VCacheStats = VCacheStats
        { vcstat_file_size      :: {-# UNPACK #-} !Int  -- ^ estimated database file size (in bytes)
        , vcstat_vref_count     :: {-# UNPACK #-} !Int  -- ^ number of immutable values in the database
        , vcstat_pvar_count     :: {-# UNPACK #-} !Int  -- ^ number of mutable PVars in the database
        , vcstat_root_count     :: {-# UNPACK #-} !Int  -- ^ number of named roots (a subset of PVars)
        , vcstat_mem_vrefs      :: {-# UNPACK #-} !Int  -- ^ number of VRefs in Haskell process memory (some may share address)
        , vcstat_mem_pvars      :: {-# UNPACK #-} !Int  -- ^ number of PVars in Haskell process memory
        , vcstat_mem_addrs      :: {-# UNPACK #-} !Int  -- ^ number of addresses held by Haskell process memory
        , vcstat_eph_count      :: {-# UNPACK #-} !Int  -- ^ number of addresses with zero references
        , vcstat_alloc_pos      :: {-# UNPACK #-} !Address -- ^ address to next be used by allocator
        , vcstat_alloc_count    :: {-# UNPACK #-} !Int  -- ^ number of allocations by this process 
        , vcstat_cache_count    :: {-# UNPACK #-} !Int  -- ^ number of VRefs with cached values
        , vcstat_cache_limit    :: {-# UNPACK #-} !Int  -- ^ target cache size 
        , vcstat_cache_size     :: {-# UNPACK #-} !Int  -- ^ estimated cache size in bytes
        , vcstat_gc_count       :: {-# UNPACK #-} !Int  -- ^ number of addresses GC'd by this process
        , vcstat_write_pvars    :: {-# UNPACK #-} !Int  -- ^ number of PVar updates to disk (after batching)
        , vcstat_write_sync     :: {-# UNPACK #-} !Int  -- ^ number of sync requests (~ durable transactions)
        , vcstat_write_frames   :: {-# UNPACK #-} !Int  -- ^ number of LMDB-layer transactions by this process
        } deriving (Show, Ord, Eq)

-- | Compute some miscellaneous statistics for a VCache instance at
-- runtime. These aren't really useful for anything, except to gain
-- some confidence about activity or comprehension of performance. 
vcacheStats :: VSpace -> IO VCacheStats
vcacheStats vc = withRdOnlyTxn vc $ \ txnStat -> do
    let db = vcache_db_env vc
    envInfo <- mdb_env_info db
    envStat <- mdb_env_stat db
    dbMemStat <- mdb_stat' txnStat (vcache_db_memory vc)
    rootStat <- mdb_stat' txnStat (vcache_db_vroots vc)
    hashStat <- mdb_stat' txnStat (vcache_db_caddrs vc)
    ephStat <- mdb_stat' txnStat (vcache_db_refct0 vc)
    memory <- readMVar (vcache_memory vc)
    gcCount <- readIORef (vcache_gc_count vc)
    wct <- readIORef (vcache_ct_writes vc)
    cLimit <- readIORef (vcache_climit vc)
    cSizeEst <- readIORef (vcache_csize vc)
    
    let fileSize = (1 + (fromIntegral $ me_last_pgno envInfo)) 
                 * (fromIntegral $ ms_psize envStat)
    let vrefCount = (fromIntegral $ ms_entries hashStat) 
    let pvarCount = (fromIntegral $ ms_entries dbMemStat) - vrefCount
    let ephCount = (fromIntegral $ ms_entries ephStat)
    let rootCount = (fromIntegral $ ms_entries rootStat)
    let cvrefsCount = Map.foldl' (\ a b -> a + Map.size b) 0 (mem_cvrefs memory)
    let evrefsCount = Map.foldl' (\ a b -> a + Map.size b) 0 (mem_evrefs memory)
    let cacheSizeBytes = ceiling $ fromIntegral (Map.size (mem_cvrefs memory))
                                 * csze_addr_size cSizeEst
    let memVRefsCount = cvrefsCount + evrefsCount
    let memPVarsCount = Map.size (mem_pvars memory)
    let memAddrsCount = Map.size (mem_pvars memory) 
                      + Map.size (mem_cvrefs memory) 
                      + Map.size (mem_evrefs memory)
    let allocPos = alloc_new_addr (mem_alloc memory)
    let allocDiff = allocPos - vcache_alloc_init vc
    let allocCount = fromIntegral $ allocDiff `div` 2 
    return $ VCacheStats
        { vcstat_file_size = fileSize
        , vcstat_vref_count = vrefCount
        , vcstat_pvar_count = pvarCount
        , vcstat_root_count = rootCount
        , vcstat_mem_vrefs = memVRefsCount
        , vcstat_mem_pvars = memPVarsCount
        , vcstat_mem_addrs = memAddrsCount
        , vcstat_eph_count = ephCount
        , vcstat_alloc_pos = allocPos
        , vcstat_alloc_count = allocCount
        , vcstat_cache_count = cvrefsCount
        , vcstat_cache_limit = cLimit
        , vcstat_cache_size = cacheSizeBytes
        , vcstat_write_sync = wct_sync wct
        , vcstat_write_pvars = wct_pvars wct
        , vcstat_write_frames = wct_frames wct
        , vcstat_gc_count = gcCount
        }