smp server: prometheus histogram for message confirmation times (ACK) (#1575)

* time buckets

* split max time metric

* histogram

* histogram for confirmed delivery times

* gaugehistogram

* fix created, _ in gauge_histogram

* remove comments

* fix metrics
This commit is contained in:
Evgeny
2025-06-20 13:46:59 +01:00
committed by GitHub
parent 79c67f2026
commit 455360205c
5 changed files with 149 additions and 50 deletions
+1 -1
View File
@@ -412,7 +412,7 @@ data SubscriptionThread = NoSub | SubPending | SubThread (Weak ThreadId)
data Sub = Sub
{ subThread :: ServerSub, -- Nothing value indicates that sub
delivered :: TMVar MsgId
delivered :: TMVar (MsgId, RoundedSystemTime)
}
newServer :: IO (Server s)
+43 -2
View File
@@ -6,6 +6,8 @@
module Simplex.Messaging.Server.Prometheus where
import Data.Int (Int64)
import qualified Data.IntMap.Strict as IM
import Data.List (mapAccumL)
import Data.Text (Text)
import qualified Data.Text as T
import Data.Time.Clock (UTCTime (..), diffUTCTime)
@@ -35,6 +37,7 @@ data RealTimeMetrics = RealTimeMetrics
threadsCount :: Int,
clientsCount :: Int,
deliveredSubs :: RTSubscriberMetrics,
deliveredTimes :: TimeBuckets,
smpSubs :: RTSubscriberMetrics,
ntfSubs :: RTSubscriberMetrics,
loadedCounts :: LoadedQueueCounts
@@ -57,6 +60,7 @@ prometheusMetrics sm rtm ts =
threadsCount,
clientsCount,
deliveredSubs,
deliveredTimes,
smpSubs,
ntfSubs,
loadedCounts
@@ -90,6 +94,7 @@ prometheusMetrics sm rtm ts =
_msgSentLarge,
_msgSentBlock,
_msgRecv,
_msgRecvAckTimes,
_msgRecvGet,
_msgGet,
_msgGetNoMsg,
@@ -436,6 +441,25 @@ prometheusMetrics sm rtm ts =
\# TYPE simplex_smp_delivered_clients_total gauge\n\
\simplex_smp_delivered_clients_total " <> mshow (subClientsCount deliveredSubs) <> "\n# delivered.subClientsCount\n\
\\n\
\# HELP simplex_smp_delivery_ack_confirmed_time Times to confirm message delivery, only confirmed deliveries\n\
\# TYPE simplex_smp_delivery_ack_confirmed_time histogram\n\
\simplex_smp_delivery_ack_confirmed_time_sum " <> mshow (sumTime _msgRecvAckTimes) <> "\n\
\simplex_smp_delivery_ack_confirmed_time_count " <> mshow (_msgRecv + _msgRecvGet) <> "\n"
<> showTimeBuckets "simplex_smp_delivery_ack_confirmed_time" (timeBuckets _msgRecvAckTimes)
<> showTimeBucket "simplex_smp_delivery_ack_confirmed_time" "+Inf" (_msgRecv + _msgRecvGet)
<> "\n\
\# HELP simplex_smp_delivery_ack_confirmed_count Counts for confirmed deliveries\n\
\# TYPE simplex_smp_delivery_ack_confirmed_count counter\n"
<> showBucketSums "simplex_smp_delivery_ack_confirmed_count" (timeBuckets _msgRecvAckTimes)
<> "\n\
\# HELP simplex_smp_delivery_ack_pending_count Counts for pending delivery\n\
\# TYPE simplex_smp_delivery_ack_pending_count gauge\n"
<> showBucketSums "simplex_smp_delivery_ack_pending_count" (timeBuckets deliveredTimes)
<> "\n\
\# HELP simplex_smp_delivery_ack_time_max Max time to confirm message delivery\n\
\# TYPE simplex_smp_delivery_ack_time_max gauge\n\
\simplex_smp_delivery_ack_time_max " <> mshow (maxTime deliveredTimes) <> "\n# delivered.maxTime\n\
\\n\
\# HELP simplex_smp_subscribtion_total Total SMP subscriptions\n\
\# TYPE simplex_smp_subscribtion_total gauge\n\
\simplex_smp_subscribtion_total " <> mshow (subsCount smpSubs) <> "\n# smp.subsCount\n\
@@ -480,15 +504,32 @@ prometheusMetrics sm rtm ts =
\# TYPE simplex_smp_loaded_queues_ntf_lock_count gauge\n\
\simplex_smp_loaded_queues_ntf_lock_count " <> mshow (notifierLockCount loadedCounts) <> "\n# loadedCounts.notifierLockCount\n"
showTimeBuckets :: Text -> IM.IntMap Int -> Text
showTimeBuckets metric = T.concat . snd . mapAccumL accumBucket (0, 0) . IM.assocs
where
accumBucket (prevSec, total) (sec, cnt) =
let t
| sec - 60 > prevSec = showTimeBucket metric (tshow (sec - 60)) total
| otherwise = ""
in ((sec, total + cnt), t <> showTimeBucket metric (tshow sec) (total + cnt))
showTimeBucket :: Text -> Text -> Int -> Text
showTimeBucket metric sec count = metric <> "_bucket{le=\"" <> sec <> "\"} " <> mshow count <> "\n"
showBucketSums :: Text -> IM.IntMap Int -> Text
showBucketSums metric buckets = T.concat $ map showBucketSum [(0, 60), (60, 300), (300, 1200), (1200, 3600), (3600, maxBound)]
where
showBucketSum (minTime, maxTime) =
metric <> "{period=\"" <> tshow minTime <> (if maxTime <= 3600 then "-" <> tshow maxTime else "+") <> "\"} " <> mshow bucketsSum <> "\n"
where
bucketsSum = IM.foldl' (+) 0 $ IM.filter (\sec -> minTime <= sec && sec < maxTime) buckets
socketsMetric :: (SocketStats -> Int) -> Text -> Text -> Text
socketsMetric sel metric descr =
"# HELP " <> metric <> " " <> descr <> "\n"
<> "# TYPE " <> metric <> " gauge\n"
<> T.concat (map (\(port, ss) -> metric <> "{port=\"" <> T.pack port <> "\"} " <> mshow (sel ss) <> "\n") socketStats)
<> "\n"
mstr a = a <> " " <> tsEpoch
mstr a = a <> " " <> tsEpoch ts
mshow :: Show a => a -> Text
mshow = mstr . tshow
tsEpoch = tshow @Int64 $ floor @Double $ realToFrac (ts `diffUTCTime` epoch) * 1000
tsEpoch t = tshow @Int64 $ floor @Double $ realToFrac (t `diffUTCTime` epoch) * 1000
epoch = UTCTime systemEpochDay 0
{-# FOURMOLU_ENABLE\n#-}
@@ -127,3 +127,6 @@ getRoundedSystemTime prec = (\t -> RoundedSystemTime $ (systemSeconds t `div` pr
getSystemDate :: IO RoundedSystemTime
getSystemDate = getRoundedSystemTime 86400
getSystemSeconds :: IO RoundedSystemTime
getSystemSeconds = RoundedSystemTime . systemSeconds <$> getSystemTime
+41
View File
@@ -14,6 +14,8 @@ import Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as B
import Data.Hashable (hash)
import Data.IORef
import Data.Int (Int64)
import qualified Data.IntMap.Strict as IM
import Data.IntSet (IntSet)
import qualified Data.IntSet as IS
import Data.Set (Set)
@@ -25,6 +27,7 @@ import Data.Time.Clock (UTCTime (..))
import GHC.IORef (atomicSwapIORef)
import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Protocol (EntityId (..))
import Simplex.Messaging.Server.QueueStore (RoundedSystemTime (..))
import Simplex.Messaging.Util (atomicModifyIORef'_, tshow, unlessM)
data ServerStats = ServerStats
@@ -57,6 +60,7 @@ data ServerStats = ServerStats
msgSentLarge :: IORef Int,
msgSentBlock :: IORef Int,
msgRecv :: IORef Int,
msgRecvAckTimes :: IORef TimeBuckets,
msgRecvGet :: IORef Int,
msgGet :: IORef Int,
msgGetNoMsg :: IORef Int,
@@ -115,6 +119,7 @@ data ServerStatsData = ServerStatsData
_msgSentLarge :: Int,
_msgSentBlock :: Int,
_msgRecv :: Int,
_msgRecvAckTimes :: TimeBuckets,
_msgRecvGet :: Int,
_msgGet :: Int,
_msgGetNoMsg :: Int,
@@ -174,6 +179,7 @@ newServerStats ts = do
msgSentLarge <- newIORef 0
msgSentBlock <- newIORef 0
msgRecv <- newIORef 0
msgRecvAckTimes <- newIORef $ TimeBuckets 0 0 IM.empty
msgRecvGet <- newIORef 0
msgGet <- newIORef 0
msgGetNoMsg <- newIORef 0
@@ -230,6 +236,7 @@ newServerStats ts = do
msgSentLarge,
msgSentBlock,
msgRecv,
msgRecvAckTimes,
msgRecvGet,
msgGet,
msgGetNoMsg,
@@ -288,6 +295,7 @@ getServerStatsData s = do
_msgSentLarge <- readIORef $ msgSentLarge s
_msgSentBlock <- readIORef $ msgSentBlock s
_msgRecv <- readIORef $ msgRecv s
_msgRecvAckTimes <- readIORef $ msgRecvAckTimes s
_msgRecvGet <- readIORef $ msgRecvGet s
_msgGet <- readIORef $ msgGet s
_msgGetNoMsg <- readIORef $ msgGetNoMsg s
@@ -344,6 +352,7 @@ getServerStatsData s = do
_msgSentLarge,
_msgSentBlock,
_msgRecv,
_msgRecvAckTimes,
_msgRecvGet,
_msgGet,
_msgGetNoMsg,
@@ -403,6 +412,7 @@ setServerStats s d = do
writeIORef (msgSentLarge s) $! _msgSentLarge d
writeIORef (msgSentBlock s) $! _msgSentBlock d
writeIORef (msgRecv s) $! _msgRecv d
writeIORef (msgRecvAckTimes s) $! _msgRecvAckTimes d
writeIORef (msgRecvGet s) $! _msgRecvGet d
writeIORef (msgGet s) $! _msgGet d
writeIORef (msgGetNoMsg s) $! _msgGetNoMsg d
@@ -592,6 +602,7 @@ instance StrEncoding ServerStatsData where
_msgSentLarge,
_msgSentBlock,
_msgRecv,
_msgRecvAckTimes = emptyTimeBuckets,
_msgRecvGet,
_msgGet,
_msgGetNoMsg,
@@ -944,3 +955,33 @@ instance StrEncoding ServiceStatsData where
_srvSubQueues,
_srvSubEnd
}
data TimeBuckets = TimeBuckets
{ sumTime :: Int64,
maxTime :: Int64,
timeBuckets :: IM.IntMap Int
}
deriving (Show)
emptyTimeBuckets :: TimeBuckets
emptyTimeBuckets = TimeBuckets 0 0 IM.empty
updateTimeBuckets :: RoundedSystemTime -> RoundedSystemTime -> TimeBuckets -> TimeBuckets
updateTimeBuckets
(RoundedSystemTime deliveryTime)
(RoundedSystemTime currTime)
TimeBuckets {sumTime, maxTime, timeBuckets} =
TimeBuckets
{ sumTime = sumTime + t,
maxTime = max maxTime t,
timeBuckets = IM.alter (Just . maybe 1 (+ 1)) seconds timeBuckets
}
where
t = currTime - deliveryTime
seconds
| t <= 5 = fromIntegral t
| t <= 30 = t `toBucket` 5
| t <= 60 = t `toBucket` 10
| t <= 180 = t `toBucket` 30
| otherwise = t `toBucket` 60
toBucket n m = - fromIntegral (((- n) `div` m) * m) -- round up