packages feed

gbnet-hs-0.2.0.0: src/GBNet/Stats.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}

-- |
-- Module      : GBNet.Stats
-- Description : Network statistics tracking
--
-- Provides 'NetworkStats' for tracking connection health metrics.
module GBNet.Stats
  ( -- * Connection quality
    ConnectionQuality (..),
    assessConnectionQuality,

    -- * Congestion level
    CongestionLevel (..),

    -- * Network statistics
    NetworkStats (..),
    defaultNetworkStats,

    -- * Socket statistics
    SocketStats (..),
    defaultSocketStats,
  )
where

import Control.DeepSeq (NFData (..), rwhnf)
import Data.Word (Word64)
import GBNet.Reliability (MonoTime)
import Optics.TH (makeFieldLabelsNoPrefix)

-- | Connection quality assessment.
data ConnectionQuality
  = QualityExcellent
  | QualityGood
  | QualityFair
  | QualityPoor
  | QualityBad
  deriving (Eq, Show, Ord)

instance NFData ConnectionQuality where rnf = rwhnf

-- Quality thresholds: (lossPercent, rttMs)
badLossThreshold, poorLossThreshold, fairLossThreshold, goodLossThreshold :: Double
badLossThreshold = 10.0
poorLossThreshold = 5.0
fairLossThreshold = 2.0
goodLossThreshold = 0.5

badRttThreshold, poorRttThreshold, fairRttThreshold, goodRttThreshold :: Double
badRttThreshold = 500.0
poorRttThreshold = 250.0
fairRttThreshold = 150.0
goodRttThreshold = 80.0

-- | Assess connection quality from RTT and packet loss.
assessConnectionQuality :: Double -> Double -> ConnectionQuality
assessConnectionQuality rttMs lossPercent
  | lossPercent > badLossThreshold || rttMs > badRttThreshold = QualityBad
  | lossPercent > poorLossThreshold || rttMs > poorRttThreshold = QualityPoor
  | lossPercent > fairLossThreshold || rttMs > fairRttThreshold = QualityFair
  | lossPercent > goodLossThreshold || rttMs > goodRttThreshold = QualityGood
  | otherwise = QualityExcellent

-- | Congestion pressure level reported to the application.
--
-- Applications can use this to adapt their behavior:
--
-- * 'CongestionNone' — send freely
-- * 'CongestionElevated' — consider reducing non-essential traffic
-- * 'CongestionHigh' — drop low-priority data, reduce send rate
-- * 'CongestionCritical' — sends are being suppressed, only send essential data
data CongestionLevel
  = CongestionNone
  | CongestionElevated
  | CongestionHigh
  | CongestionCritical
  deriving (Eq, Show, Ord)

instance NFData CongestionLevel where rnf = rwhnf

-- | Network statistics for a connection.
data NetworkStats = NetworkStats
  { nsPacketsSent :: !Word64,
    nsPacketsReceived :: !Word64,
    nsBytesSent :: !Word64,
    nsBytesReceived :: !Word64,
    nsRtt :: !Double,
    nsPacketLoss :: !Double,
    nsBandwidthUp :: !Double,
    nsBandwidthDown :: !Double,
    nsConnectionQuality :: !ConnectionQuality,
    nsCongestionLevel :: !CongestionLevel,
    nsDecryptionFailures :: !Word64
  }
  deriving (Show)

instance NFData NetworkStats where
  rnf ns =
    rnf (nsPacketsSent ns) `seq`
      rnf (nsPacketsReceived ns) `seq`
        rnf (nsBytesSent ns) `seq`
          rnf (nsBytesReceived ns) `seq`
            rnf (nsRtt ns) `seq`
              rnf (nsPacketLoss ns) `seq`
                rnf (nsBandwidthUp ns) `seq`
                  rnf (nsBandwidthDown ns) `seq`
                    rnf (nsConnectionQuality ns) `seq`
                      rnf (nsCongestionLevel ns) `seq`
                        rnf (nsDecryptionFailures ns)

-- | Default (zero) network statistics.
defaultNetworkStats :: NetworkStats
defaultNetworkStats =
  NetworkStats
    { nsPacketsSent = 0,
      nsPacketsReceived = 0,
      nsBytesSent = 0,
      nsBytesReceived = 0,
      nsRtt = 0.0,
      nsPacketLoss = 0.0,
      nsBandwidthUp = 0.0,
      nsBandwidthDown = 0.0,
      nsConnectionQuality = QualityExcellent,
      nsCongestionLevel = CongestionNone,
      nsDecryptionFailures = 0
    }

-- | Socket-level statistics.
data SocketStats = SocketStats
  { ssPacketsSent :: !Word64,
    ssPacketsReceived :: !Word64,
    ssBytesSent :: !Word64,
    ssBytesReceived :: !Word64,
    ssCrcDrops :: !Word64,
    ssLastSendTime :: !(Maybe MonoTime),
    ssLastReceiveTime :: !(Maybe MonoTime)
  }
  deriving (Show)

-- | Default socket statistics.
defaultSocketStats :: SocketStats
defaultSocketStats =
  SocketStats
    { ssPacketsSent = 0,
      ssPacketsReceived = 0,
      ssBytesSent = 0,
      ssBytesReceived = 0,
      ssCrcDrops = 0,
      ssLastSendTime = Nothing,
      ssLastReceiveTime = Nothing
    }

makeFieldLabelsNoPrefix ''NetworkStats
makeFieldLabelsNoPrefix ''SocketStats