module Database.VCache.Clean
( cleanStep
) where
import Control.Monad
import Control.Applicative
import Control.Concurrent
import Data.Bits
import qualified Data.Traversable as TR
import qualified Data.Map.Strict as Map
import Data.IORef
import qualified System.Mem.Weak as Weak
import qualified System.Random as Random
import Database.LMDB.Raw
import Database.VCache.Types
cleanStep :: VSpace -> IO ()
cleanStep vc = do
bsig <- shouldSignalWriter vc
when bsig (signalWriter vc)
wtgt <- readIORef (vcache_climit vc)
w0 <- estCacheSize vc
let hitRate =
if ((100 * w0) < ( 80 * wtgt)) then 0.00 else
if ((100 * w0) < (100 * wtgt)) then 0.01 else
if ((100 * w0) < (130 * wtgt)) then 0.02 else
if ((100 * w0) < (170 * wtgt)) then 0.03 else
if ((100 * w0) < (220 * wtgt)) then 0.04 else
if ((100 * w0) < (280 * wtgt)) then 0.05 else
0.06
xcln vc hitRate
updateCacheSizeEst vc
wf <- estCacheSize vc
let bSatisfied = (max w0 wf) < wtgt
let dtSleep = if bSatisfied then 295000 else 95000
usleep dtSleep
usleep :: Int -> IO ()
usleep = threadDelay
estCacheSize :: VSpace -> IO Int
estCacheSize vc = do
csze <- readIORef (vcache_csize vc)
let avgAddr = sqrt (csze_addr_sqsz csze)
ctAddrs <- fromIntegral <$> readCacheAddrCt vc
return $! ceiling $ avgAddr * ctAddrs
readCacheAddrCt :: VSpace -> IO Int
readCacheAddrCt vc = do
m <- readMVar (vcache_memory vc)
return $! Map.size (mem_cvrefs m)
updateCacheSizeEst :: VSpace -> IO ()
updateCacheSizeEst vc =
readMVar (vcache_memory vc) >>= \ m ->
let cvrefs = mem_cvrefs m in
if Map.null cvrefs then return () else
let nextIx = Random.randomR (0, Map.size cvrefs 1) in
let loop !n !r !sz !sqsz =
if (0 == n) then return (sz,sqsz) else
let (ix,r') = nextIx r in
let (_, tym) = Map.elemAt ix cvrefs in
let (_, e) = Map.findMin tym in
readVREphSize e >>= \ esz ->
let addrsz = fromIntegral $ esz * Map.size tym in
let sz' = sz + addrsz in
let sqsz' = sqsz + (addrsz * addrsz) in
loop (n1) r' sz' sqsz'
in
let nSamples = 15 :: Int in
Random.newStdGen >>= \ r ->
loop nSamples r 0 0 >>= \ (totalSize, totalSqSize) ->
let sampleAvg = totalSize / fromIntegral nSamples in
let sampleAvgSq = totalSqSize / fromIntegral nSamples in
readIORef (vcache_csize vc) >>= \ (CacheSizeEst oldAvg oldAvgSq) ->
let alpha = 0.015 :: Double in
let newAvg = alpha * sampleAvg + ((1.0 alpha) * oldAvg) in
let newAvgSq = alpha * sampleAvgSq + ((1.0 alpha) * oldAvgSq) in
writeIORef (vcache_csize vc) $! (CacheSizeEst newAvg newAvgSq)
readVREphSize :: VREph -> IO Int
readVREphSize (VREph { vreph_cache = wk }) =
Weak.deRefWeak wk >>= \ mbc -> case mbc of
Nothing -> return 2048
Just cache -> readIORef cache >>= \ c -> case c of
NotCached ->
let eMsg = "VCache bug: NotCached element found in mem_cvrefs" in
fail eMsg
Cached _ bf ->
let lgSz = 6 + fromIntegral (0x1f .&. bf) in
return $! 1 `shiftL` lgSz
xcln :: VSpace -> Double -> IO ()
xcln !vc !hr = do
ct <- readCacheAddrCt vc
let hct = ceiling $ hr * fromIntegral ct
r <- Random.newStdGen
xclnLoop vc hct r
xclnLoop :: VSpace -> Int -> Random.StdGen -> IO ()
xclnLoop !vc !n !r =
if (n < 1) then return () else
xclnStrike vc r >>= xclnLoop vc (n1)
xclnStrike :: VSpace -> Random.StdGen -> IO Random.StdGen
xclnStrike !vc !r = modifyMVarMasked (vcache_memory vc) $ \ m ->
if Map.null (mem_cvrefs m) then return (m,r) else do
let cvrefs = mem_cvrefs m
let evrefs = mem_evrefs m
let (ix,r') = Random.randomR (0, Map.size cvrefs 1) r
let (addr, tym) = Map.elemAt ix cvrefs
(tymc, tyme) <- Map.mapEither id <$> TR.traverse strikeVREph tym
let cvrefs' = if Map.null tymc then Map.delete addr cvrefs else
if Map.null tyme then cvrefs else
Map.insert addr tymc cvrefs
let evrefs' = if Map.null tyme then evrefs else
Map.insertWith (Map.union) addr tyme evrefs
let m' = m { mem_cvrefs = cvrefs', mem_evrefs = evrefs' }
return (m', m' `seq` r')
strikeVREph :: VREph -> IO (Either VREph VREph)
strikeVREph vreph@(VREph { vreph_cache = wk }) =
Weak.deRefWeak wk >>= \ mbCache -> case mbCache of
Nothing -> return (Right vreph)
Just cache -> atomicModifyIORef cache $ \ c -> case c of
Cached r bf | (0 /= bf .&. 0x60) ->
let bf' = (0x80 .|. (bf 0x20)) in
let c' = Cached r bf' in
(c', c' `seq` (Left vreph))
_ -> (NotCached, Right vreph)
shouldSignalWriter :: VSpace -> IO Bool
shouldSignalWriter vc =
readMVar (vcache_memory vc) >>= \ m ->
let bHoldingAllocs = not (emptyAllocation (mem_alloc m)) in
if bHoldingAllocs then return True else
readZeroesCt vc >>= \ ctZeroes ->
let ctEphAddrs = Map.size (mem_cvrefs m) + Map.size (mem_evrefs m) + Map.size (mem_pvars m) in
if (ctEphAddrs < ctZeroes) then return True else
return False
readZeroesCt :: VSpace -> IO Int
readZeroesCt vc = withRdOnlyTxn vc $ \ txn ->
mdb_stat' txn (vcache_db_refct0 vc) >>= \ stat ->
return $! fromIntegral (ms_entries stat)
emptyAllocation :: Allocator -> Bool
emptyAllocation ac = fn n && fn c && fn p where
n = alloc_frm_next ac
c = alloc_frm_curr ac
p = alloc_frm_prev ac
fn = Map.null . alloc_list
signalWriter :: VSpace -> IO ()
signalWriter vc = void $ tryPutMVar (vcache_signal vc) ()