diff options
| author | Michal Zielonka <michal.zielonka.8001@gmail.com> | 2021-10-07 00:46:02 +0200 | 
|---|---|---|
| committer | Michal Zielonka <michal.zielonka.8001@gmail.com> | 2021-10-07 08:36:53 +0200 | 
| commit | 34ff313d90b48033282af7b3e845614cfabcfef7 (patch) | |
| tree | 17149b2735eb8d2b070c1be45fe657ddb0b675a6 /src/Xmobar/Plugins/Monitors/Net.hsc | |
| parent | 1f183cd7592af46c015ce57d60a88c2289069d2c (diff) | |
| download | xmobar-34ff313d90b48033282af7b3e845614cfabcfef7.tar.gz xmobar-34ff313d90b48033282af7b3e845614cfabcfef7.tar.bz2 | |
Add freebsd support for net monitor plugin.
In freebsd /sys/class/net is absent so we should use sysctl for
obtaining info about stats of network.
For parsing if_data struct we could use a "Foreign.Storable"
Diffstat (limited to 'src/Xmobar/Plugins/Monitors/Net.hsc')
| -rw-r--r-- | src/Xmobar/Plugins/Monitors/Net.hsc | 334 | 
1 files changed, 334 insertions, 0 deletions
| diff --git a/src/Xmobar/Plugins/Monitors/Net.hsc b/src/Xmobar/Plugins/Monitors/Net.hsc new file mode 100644 index 0000000..53a1a9e --- /dev/null +++ b/src/Xmobar/Plugins/Monitors/Net.hsc @@ -0,0 +1,334 @@ +----------------------------------------------------------------------------- +-- | +-- Module      :  Plugins.Monitors.Net +-- Copyright   :  (c) 2011, 2012, 2013, 2014, 2017, 2020 Jose Antonio Ortega Ruiz +--                (c) 2007-2010 Andrea Rossato +-- License     :  BSD-style (see LICENSE) +-- +-- Maintainer  :  Jose A. Ortega Ruiz <jao@gnu.org> +-- Stability   :  unstable +-- Portability :  unportable +-- +-- A net device monitor for Xmobar +-- + +----------------------------------------------------------------------------- + +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ForeignFunctionInterface #-} +{-# LANGUAGE CPP #-} +{-# LANGUAGE CApiFFI #-} + +module Xmobar.Plugins.Monitors.Net ( +                        startNet +                      , startDynNet +                      ) where + +import Xmobar.Plugins.Monitors.Common + +import Data.IORef (IORef, newIORef, readIORef, writeIORef) +import Data.Time.Clock (UTCTime, getCurrentTime, diffUTCTime) +import Data.Word (Word64) +import System.Console.GetOpt + +#ifdef FREEBSD +import Control.Monad (forM) +import Foreign (Int32, plusPtr) +import Foreign.C.Types (CUIntMax, CUChar) +import Foreign.C.String (peekCString) +import Foreign.ForeignPtr () +import Foreign.Storable (Storable, alignment, sizeOf, peek, poke) +import System.BSD.Sysctl (OID, sysctlPrepareOid, sysctlReadInt, sysctlPeek) +#else +import Control.Monad (forM, filterM) +import System.Directory (getDirectoryContents, doesFileExist) +import System.FilePath ((</>)) +import System.IO.Error (catchIOError) +import System.IO.Unsafe (unsafeInterleaveIO) + +import qualified Data.ByteString.Char8 as B +#endif + +type DevList = [String] + +parseDevList :: String -> DevList +parseDevList = splitOnComma +  where splitOnComma [] = [[]] +        splitOnComma (',':xs) = [] : splitOnComma xs +        splitOnComma (x:xs) = +           let rest = splitOnComma xs +           in (x : head rest) : tail rest + +data NetOpts = NetOpts +  { rxIconPattern :: Maybe IconPattern +  , txIconPattern :: Maybe IconPattern +  , onlyDevList :: Maybe DevList +  , upIndicator :: String +  } + +defaultOpts :: NetOpts +defaultOpts = NetOpts +  { rxIconPattern = Nothing +  , txIconPattern = Nothing +  , onlyDevList = Nothing +  , upIndicator = "+" +  } + +options :: [OptDescr (NetOpts -> NetOpts)] +options = +  [ Option "" ["rx-icon-pattern"] (ReqArg (\x o -> +     o { rxIconPattern = Just $ parseIconPattern x }) "") "" +  , Option "" ["tx-icon-pattern"] (ReqArg (\x o -> +     o { txIconPattern = Just $ parseIconPattern x }) "") "" +  , Option "" ["up"] (ReqArg (\x o -> o { upIndicator = x }) "") "" +  , Option "" ["devices"] (ReqArg (\x o -> +     o { onlyDevList = Just $ parseDevList x }) "") "" +  ] + +data UnitPerSec = Bs | KBs | MBs | GBs deriving (Eq,Enum,Ord) +data NetValue = NetValue Float UnitPerSec deriving (Eq,Show) + +instance Show UnitPerSec where +    show Bs  = "B/s" +    show KBs = "KB/s" +    show MBs = "MB/s" +    show GBs = "GB/s" + +data NetDev num = N String (NetDevInfo num) | NA deriving (Eq,Show,Read) +data NetDevInfo num = NI | ND num num deriving (Eq,Show,Read) + +type NetDevRawTotal = NetDev Word64 +type NetDevRate = NetDev Float + +type NetDevRef = IORef (NetDevRawTotal, UTCTime) + +-- The more information available, the better. +-- Note that names don't matter. Therefore, if only the names differ, +-- a compare evaluates to EQ while (==) evaluates to False. +instance Ord num => Ord (NetDev num) where +    compare NA NA             = EQ +    compare NA _              = LT +    compare _  NA             = GT +    compare (N _ i1) (N _ i2) = i1 `compare` i2 + +instance Ord num => Ord (NetDevInfo num) where +    compare NI NI                 = EQ +    compare NI ND {}              = LT +    compare ND {} NI              = GT +    compare (ND x1 y1) (ND x2 y2) = x1 `compare` x2 <> y1 `compare` y2 + +netConfig :: IO MConfig +netConfig = mkMConfig +    "<dev>: <rx>KB|<tx>KB"      -- template +    ["dev", "rx", "tx", "rxbar", "rxvbar", "rxipat", "txbar", "txvbar", "txipat", "up"]     -- available replacements + + +#ifdef FREEBSD + +#include <sys/sysctl.h> +#include <net/if.h> +#include <net/if_mib.h> + +data IfData = IfData { +  name :: String +  , txBytes :: CUIntMax +  , rxBytes :: CUIntMax +  , isUp :: Bool +  } +  deriving (Show, Read, Eq) + +instance Storable IfData where +  alignment _ = #{alignment struct ifmibdata} +  sizeOf _    = #{size struct ifmibdata} +  peek ptr    = do +    cname <- peekCString (ptr `plusPtr` (#offset struct ifmibdata, ifmd_name)) +    tx <- peek ((ifmd_data_ptr ptr) `plusPtr` (#offset struct if_data, ifi_obytes)) :: IO CUIntMax +    rx <- peek ((ifmd_data_ptr ptr) `plusPtr` (#offset struct if_data, ifi_ibytes)) :: IO CUIntMax +    state <- peek ((ifmd_data_ptr ptr) `plusPtr` (#offset struct if_data, ifi_link_state)) :: IO CUChar +    return $ IfData {name = cname, txBytes = tx, rxBytes = rx, isUp = up state} +      where +        up state = state == (#const LINK_STATE_UP) +        ifmd_data_ptr p = p `plusPtr` (#offset struct ifmibdata, ifmd_data) + +  poke _ _    = pure () + +getNetIfCountOID :: IO OID +getNetIfCountOID = sysctlPrepareOid [ +  #const CTL_NET +  , #const PF_LINK +  , #const NETLINK_GENERIC +  , #const IFMIB_SYSTEM +  , #const IFMIB_IFCOUNT] + +getNetIfDataOID :: Int32 -> IO OID +getNetIfDataOID i = sysctlPrepareOid [ +  #const CTL_NET +  , #const PF_LINK +  , #const NETLINK_GENERIC +  , #const IFMIB_IFDATA +  , i +  , #const IFDATA_GENERAL] + +getNetIfCount :: IO Int32 +getNetIfCount = do +  oid <- getNetIfCountOID +  sysctlReadInt oid + +getNetIfData :: Int32 -> IO IfData +getNetIfData i = do +  oid <- getNetIfDataOID i +  res <- sysctlPeek oid :: IO IfData +  return res + +getAllNetworkData :: IO [IfData] +getAllNetworkData = do +  count <- getNetIfCount +  result <- mapM getNetIfData [1..count] +  return $ result + +existingDevs :: IO [String] +existingDevs = getAllNetworkData >>= (\xs -> return $ filter (/= "lo0") $ fmap name xs) + +convertIfDataToNetDev :: IfData -> IO NetDevRawTotal +convertIfDataToNetDev ifData = do +  let up = isUp ifData +      rx = fromInteger . toInteger $ rxBytes ifData +      tx = fromInteger . toInteger $ txBytes ifData +      d = name ifData +  return $ N d (if up then ND rx tx else NI) + +netConvertIfDataToNetDev :: [IfData] -> IO [NetDevRawTotal] +netConvertIfDataToNetDev = mapM convertIfDataToNetDev + +findNetDev :: String -> IO NetDevRawTotal +findNetDev dev = do +  nds <- getAllNetworkData >>= netConvertIfDataToNetDev +  case filter isDev nds of +    x:_ -> return x +    _ -> return NA +  where isDev (N d _) = d == dev +        isDev NA = False + +#else +operstateDir :: String -> FilePath +operstateDir d = "/sys/class/net" </> d </> "operstate" + +existingDevs :: IO [String] +existingDevs = getDirectoryContents "/sys/class/net" >>= filterM isDev +  where isDev d | d `elem` excludes = return False +                | otherwise = doesFileExist (operstateDir d) +        excludes = [".", "..", "lo"] + +isUp :: String -> IO Bool +isUp d = flip catchIOError (const $ return False) $ do +  operstate <- B.readFile (operstateDir d) +  return $! (head . B.lines) operstate `elem` ["up", "unknown"] + +readNetDev :: [String] -> IO NetDevRawTotal +readNetDev ~[d, x, y] = do +  up <- unsafeInterleaveIO $ isUp d +  return $ N d (if up then ND (r x) (r y) else NI) +    where r s | s == "" = 0 +              | otherwise = read s + +netParser :: B.ByteString -> IO [NetDevRawTotal] +netParser = mapM (readNetDev . splitDevLine) . readDevLines +  where readDevLines = drop 2 . B.lines +        splitDevLine = map B.unpack . selectCols . filter (not . B.null) . B.splitWith (`elem` [' ',':']) +        selectCols cols = map (cols!!) [0,1,9] + +findNetDev :: String -> IO NetDevRawTotal +findNetDev dev = do +  nds <- B.readFile "/proc/net/dev" >>= netParser +  case filter isDev nds of +    x:_ -> return x +    _ -> return NA +  where isDev (N d _) = d == dev +        isDev NA = False + +#endif + +formatNet :: Maybe IconPattern -> Float -> Monitor (String, String, String, String) +formatNet mipat d = do +    s <- getConfigValue useSuffix +    dd <- getConfigValue decDigits +    let str True v = showDigits dd d' ++ show u +            where (NetValue d' u) = byteNetVal v +        str False v = showDigits dd $ v / 1024 +    b <- showLogBar 0.9 d +    vb <- showLogVBar 0.9 d +    ipat <- showLogIconPattern mipat 0.9 d +    x <- showWithColors (str s) d +    return (x, b, vb, ipat) + +printNet :: NetOpts -> NetDevRate -> Monitor String +printNet opts nd = +  case nd of +    N d (ND r t) -> do +        (rx, rb, rvb, ripat) <- formatNet (rxIconPattern opts) r +        (tx, tb, tvb, tipat) <- formatNet (txIconPattern opts) t +        parseTemplate [d,rx,tx,rb,rvb,ripat,tb,tvb,tipat, upIndicator opts] +    N _ NI -> return "" +    NA -> getConfigValue naString + +parseNet :: NetDevRef -> String -> IO NetDevRate +parseNet nref nd = do +  (n0, t0) <- readIORef nref +  n1 <- findNetDev nd +  t1 <- getCurrentTime +  writeIORef nref (n1, t1) +  let scx = realToFrac (diffUTCTime t1 t0) +      scx' = if scx > 0 then scx else 1 +      rate da db = takeDigits 2 $ fromIntegral (db - da) / scx' +      diffRate (N d (ND ra ta)) (N _ (ND rb tb)) = N d (ND (rate ra rb) (rate ta tb)) +      diffRate (N d NI) _ = N d NI +      diffRate _ (N d NI) = N d NI +      diffRate _ _ = NA +  return $ diffRate n0 n1 + +runNet :: NetDevRef -> String -> [String] -> Monitor String +runNet nref i argv = do +  dev <- io $ parseNet nref i +  opts <- io $ parseOptsWith options defaultOpts argv +  printNet opts dev + +parseNets :: [(NetDevRef, String)] -> IO [NetDevRate] +parseNets = mapM $ uncurry parseNet + +runNets :: [(NetDevRef, String)] -> [String] -> Monitor String +runNets refs argv = do +  opts <- io $ parseOptsWith options defaultOpts argv +  dev <- io $ parseActive $ filterRefs opts refs +  printNet opts dev +    where parseActive refs' = fmap selectActive (parseNets refs') +          refInDevList opts' (_, refname') = case onlyDevList opts' of +            Just theList -> refname' `elem` theList +            Nothing -> True +          filterRefs opts' refs' = case filter (refInDevList opts') refs' of +            [] -> refs' +            xs -> xs +          selectActive = maximum + +startNet :: String -> [String] -> Int -> (String -> IO ()) -> IO () +startNet i a r cb = do +  t0 <- getCurrentTime +  nref <- newIORef (NA, t0) +  _ <- parseNet nref i +  runM a netConfig (runNet nref i) r cb + +startDynNet :: [String] -> Int -> (String -> IO ()) -> IO () +startDynNet a r cb = do +  devs <- existingDevs +  refs <- forM devs $ \d -> do +            t <- getCurrentTime +            nref <- newIORef (NA, t) +            _ <- parseNet nref d +            return (nref, d) +  runM a netConfig (runNets refs) r cb + +byteNetVal :: Float -> NetValue +byteNetVal v +    | v < 1024**1 = NetValue v Bs +    | v < 1024**2 = NetValue (v/1024**1) KBs +    | v < 1024**3 = NetValue (v/1024**2) MBs +    | otherwise   = NetValue (v/1024**3) GBs | 
