calculate service load rates, interface for querying loads

- define data type for load representation
- this representation can be queried from any Service (closes #72)
- loads are periodically calculated from measured rates (contributes to #2)
This commit is contained in:
Trolli Schmittlauch 2020-09-18 20:26:50 +02:00
parent 7dd7e96cce
commit 576ea2c3f6
2 changed files with 69 additions and 8 deletions

View file

@ -7,8 +7,8 @@
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-} {-# LANGUAGE RankNTypes #-}
module Hash2Pub.FediChordTypes ( module Hash2Pub.FediChordTypes
NodeID -- abstract, but newtype constructors cannot be hidden ( NodeID -- abstract, but newtype constructors cannot be hidden
, idBits , idBits
, getNodeID , getNodeID
, toNodeID , toNodeID
@ -18,6 +18,8 @@ module Hash2Pub.FediChordTypes (
, RemoteNodeState (..) , RemoteNodeState (..)
, RealNode (..) , RealNode (..)
, RealNodeSTM , RealNodeSTM
, LoadStats (..)
, emptyLoadStats
, setSuccessors , setSuccessors
, setPredecessors , setPredecessors
, NodeCache , NodeCache
@ -430,6 +432,23 @@ data FediChordConf = FediChordConf
} }
deriving (Show, Eq) deriving (Show, Eq)
-- ====== k-choices load balancing types ======
data LoadStats = LoadStats
{ loadPerTag :: RingMap NodeID Double
, totalCapacity :: Double
, remainingLoadTarget :: Double
}
deriving (Show, Eq)
-- TODO: figure out a better way of initialising
emptyLoadStats :: LoadStats
emptyLoadStats = LoadStats
{ loadPerTag = emptyRMap
, totalCapacity = 0
, remainingLoadTarget = 0
}
-- ====== Service Types ============ -- ====== Service Types ============
class Service s d where class Service s d where
@ -445,6 +464,7 @@ class Service s d where
-> IO (Either String ()) -- ^ success or failure -> IO (Either String ()) -- ^ success or failure
-- | Wait for an incoming migration from a given node to succeed, may block forever -- | Wait for an incoming migration from a given node to succeed, may block forever
waitForMigrationFrom :: s d -> NodeID -> IO () waitForMigrationFrom :: s d -> NodeID -> IO ()
getServiceLoadStats :: s d -> IO LoadStats
instance Hashable.Hashable NodeID where instance Hashable.Hashable NodeID where
hashWithSalt salt = Hashable.hashWithSalt salt . getNodeID hashWithSalt salt = Hashable.hashWithSalt salt . getNodeID

View file

@ -22,7 +22,7 @@ import qualified Data.DList as D
import Data.Either (lefts, rights) import Data.Either (lefts, rights)
import qualified Data.HashMap.Strict as HMap import qualified Data.HashMap.Strict as HMap
import qualified Data.HashSet as HSet import qualified Data.HashSet as HSet
import Data.Maybe (fromJust, isJust) import Data.Maybe (fromJust, fromMaybe, isJust)
import Data.String (fromString) import Data.String (fromString)
import Data.Text.Lazy (Text) import Data.Text.Lazy (Text)
import qualified Data.Text.Lazy as Txt import qualified Data.Text.Lazy as Txt
@ -64,8 +64,10 @@ data PostService d = PostService
, migrationsInProgress :: TVar (HMap.HashMap NodeID (MVar ())) , migrationsInProgress :: TVar (HMap.HashMap NodeID (MVar ()))
, httpMan :: HTTP.Manager , httpMan :: HTTP.Manager
, statsQueue :: TQueue StatsEvent , statsQueue :: TQueue StatsEvent
, loadStats :: TVar RelayStats , relayStats :: TVar RelayStats
-- ^ current load stats, replaced periodically -- ^ current relay stats, replaced periodically
, loadStats :: TVar LoadStats
-- ^ current load values of the relay, replaced periodically and used by
, logFileHandle :: Handle , logFileHandle :: Handle
} }
deriving (Typeable) deriving (Typeable)
@ -96,7 +98,8 @@ instance DHT d => Service PostService d where
migrationsInProgress' <- newTVarIO HMap.empty migrationsInProgress' <- newTVarIO HMap.empty
httpMan' <- HTTP.newManager HTTP.defaultManagerSettings httpMan' <- HTTP.newManager HTTP.defaultManagerSettings
statsQueue' <- newTQueueIO statsQueue' <- newTQueueIO
loadStats' <- newTVarIO emptyStats relayStats' <- newTVarIO emptyStats
loadStats' <- newTVarIO emptyLoadStats
loggingFile <- openFile (confLogfilePath conf) WriteMode loggingFile <- openFile (confLogfilePath conf) WriteMode
hSetBuffering loggingFile LineBuffering hSetBuffering loggingFile LineBuffering
let let
@ -112,6 +115,7 @@ instance DHT d => Service PostService d where
, migrationsInProgress = migrationsInProgress' , migrationsInProgress = migrationsInProgress'
, httpMan = httpMan' , httpMan = httpMan'
, statsQueue = statsQueue' , statsQueue = statsQueue'
, relayStats = relayStats'
, loadStats = loadStats' , loadStats = loadStats'
, logFileHandle = loggingFile , logFileHandle = loggingFile
} }
@ -153,6 +157,12 @@ instance DHT d => Service PostService d where
-- block until migration finished -- block until migration finished
takeMVar migrationSynchroniser takeMVar migrationSynchroniser
getServiceLoadStats = getLoadStats
getLoadStats :: PostService d -> IO LoadStats
getLoadStats serv = readTVarIO $ loadStats serv
-- | return a WAI application -- | return a WAI application
postServiceApplication :: DHT d => PostService d -> Application postServiceApplication :: DHT d => PostService d -> Application
@ -835,7 +845,12 @@ evaluateStatsThread serv statsAcc = getPOSIXTime >>= loop
-- persistently store in a TVar so it can be retrieved later by the DHT -- persistently store in a TVar so it can be retrieved later by the DHT
let timePassed = (now - previousTs) * fromIntegral (confSpeedupFactor $ serviceConf serv) let timePassed = (now - previousTs) * fromIntegral (confSpeedupFactor $ serviceConf serv)
rateStats = evaluateStats timePassed summedStats rateStats = evaluateStats timePassed summedStats
atomically $ writeTVar (loadStats serv) rateStats currentSubscribers <- readTVarIO $ subscribers serv
-- translate the rate statistics to load values
loads <- evaluateLoadStats rateStats currentSubscribers
atomically $
writeTVar (relayStats serv) rateStats
>> writeTVar (loadStats serv) loads
-- and now what? write a log to file -- and now what? write a log to file
-- format: total relayReceiveRates;total relayDeliveryRates;postFetchRate;postPublishRate; subscriberSum -- format: total relayReceiveRates;total relayDeliveryRates;postFetchRate;postPublishRate; subscriberSum
-- later: current (reported) load, target load -- later: current (reported) load, target load
@ -859,6 +874,32 @@ evaluateStatsThread serv statsAcc = getPOSIXTime >>= loop
0 tagMap 0 tagMap
-- | calculate load values from rate statistics
evaluateLoadStats :: RelayStats -> RelayTags -> IO LoadStats
evaluateLoadStats currentStats currentSubscribers = do
-- load caused by each tag: incomingPostRate * ( 1 + subscribers)
-- calculate remaining load target: post publish rate * 2.5 - sum loadPerTag - postFetchRate
let
totalCapacity' = 2.5 * postPublishRate currentStats
(loadSum, loadPerTag') <- foldM (\(loadSum, loadPerTag') (key, (subscriberMapSTM, _, _)) -> do
numSubscribers <- HMap.size <$> readTVarIO subscriberMapSTM
let
thisTagRate = fromMaybe 0 $ rMapLookup key (relayReceiveRates currentStats)
thisTagLoad = thisTagRate * (1 + fromIntegral numSubscribers)
pure (loadSum + thisTagLoad, addRMapEntry key thisTagLoad loadPerTag')
)
(0, emptyRMap)
$ rMapToListWithKeys currentSubscribers
-- TODO: use underload and overload limits instead of capacity
let remainingLoadTarget' = totalCapacity' - loadSum - postFetchRate currentStats
pure LoadStats
{ loadPerTag = loadPerTag'
, totalCapacity = totalCapacity'
, remainingLoadTarget = remainingLoadTarget'
}
-- | Evaluate the accumulated statistic events: Currently mostly calculates the event -- | Evaluate the accumulated statistic events: Currently mostly calculates the event
-- rates by dividing through the collection time frame -- rates by dividing through the collection time frame
evaluateStats :: POSIXTime -> RelayStats -> RelayStats evaluateStats :: POSIXTime -> RelayStats -> RelayStats