system-linux-proc-0.1.0.1: System/Linux/Proc/MemInfo.hs
{-# LANGUAGE OverloadedStrings #-}
module System.Linux.Proc.MemInfo
( MemInfo (..)
, readProcMemInfo
, readProcMemInfoKey
, readProcMemUsage
) where
import Control.Error (ExceptT (..), fromMaybe, runExceptT, throwE)
import Data.Attoparsec.ByteString.Char8 (Parser)
import qualified Data.Attoparsec.ByteString.Char8 as A
import Data.ByteString (ByteString)
import qualified Data.ByteString.Char8 as BS
import qualified Data.List as DL
import qualified Data.Map.Strict as DM
import Data.Maybe (mapMaybe)
import qualified Data.Text as T
import Data.Word (Word64)
import System.Linux.Proc.IO
import System.Linux.Proc.Errors
-- | A struct to contain information parsed from the `/proc/meminfo` file
-- (Linux only AFAIK). Fields that are listed as being in kilobytes in the proc
-- filesystem are converted to bytes.
-- Not all versions of the Linux kernel make all the fields in this struct
-- available in which case they will be assigned a value of zero.
data MemInfo = MemInfo
{ memTotal :: !Word64 -- ^ Total physical RAM.
, memFree :: !Word64 -- ^ Total free RAM (which includes memory used for filesystem caching).
, memAvailable :: !Word64 -- ^ Available memory.
, memBuffers :: !Word64 -- ^ Amount of RAM used for file buffers.
, memSwapTotal :: !Word64 -- ^ Total about of swap space.
, memSwapFree :: !Word64 -- ^ Amount of swap space that is free.
} deriving (Eq, Show)
-- | Read the `/proc/meminfo` file (Linux only AFAIK) and return a
-- `MemInfo` structure.
-- Although this is in `IO` all exceptions and errors should be caught and
-- returned as a `ProcError`.
readProcMemInfo :: IO (Either ProcError MemInfo)
readProcMemInfo =
runExceptT $ do
bs <- readProcFile fpMemInfo
case A.parseOnly parseFields bs of
Left e -> throwE $ ProcParseError fpMemInfo (T.pack e)
Right xs -> pure $ construct xs
-- | Read `/proc/meminfo` file and return a value calculated from:
--
-- MemAvailable / MemTotal
--
-- Although this is in `IO` all exceptions and errors should be caught and
-- returned as a `ProcError`.
readProcMemUsage :: IO (Either ProcError Double)
readProcMemUsage =
runExceptT $ do
xs <- BS.lines <$> readProcFile fpMemInfo
pure . convert $ DL.foldl' getValues (0, 1) xs
where
getValues :: (Word64, Word64) -> ByteString -> (Word64, Word64)
getValues (avail, total) bs =
case BS.break (== ':') bs of
("MemTotal", rest) -> (avail, fromEither total $ A.parseOnly pValue rest)
("MemAvailable", rest) -> (fromEither avail $ A.parseOnly pValue rest, total)
_ -> (avail, total)
convert :: (Word64, Word64) -> Double
convert (avail, total) = fromIntegral avail / fromIntegral total
-- | Read the value for the given key from `/proc/meminfo`.
-- Although this is in `IO` all exceptions and errors should be caught and
-- returned as a `ProcError`.
readProcMemInfoKey :: ByteString -> IO (Either ProcError Word64)
readProcMemInfoKey target =
runExceptT $ do
xs <- BS.lines <$> readProcFile fpMemInfo
hoistEither . headEither keyError $ mapMaybe findValue xs
where
findValue :: ByteString -> Maybe Word64
findValue bs =
let (key, rest) = BS.break (== ':') bs in
if key /= target
then Nothing
else either (const Nothing) Just $ A.parseOnly pValue rest
keyError :: ProcError
keyError = ProcMemInfoKeyError $ T.pack (BS.unpack target)
-- -----------------------------------------------------------------------------
-- Internals.
fpMemInfo :: FilePath
fpMemInfo = "/proc/meminfo"
fromEither :: a -> Either e a -> a
fromEither a = either (const a) id
headEither :: e -> [a] -> Either e a
headEither e [] = Left e
headEither _ (x:_) = Right x
hoistEither :: Monad m => Either e a -> ExceptT e m a
hoistEither = ExceptT . pure
construct :: [(ByteString, Word64)] -> MemInfo
construct xs =
MemInfo
(fromMaybe 0 $ DM.lookup "MemTotal" mp)
(fromMaybe 0 $ DM.lookup "MemFree" mp)
(fromMaybe 0 $ DM.lookup "MemAvailable" mp)
(fromMaybe 0 $ DM.lookup "Buffers" mp)
(fromMaybe 0 $ DM.lookup "SwapTotal" mp)
(fromMaybe 0 $ DM.lookup "SwapFree" mp)
where
mp = DM.fromList xs
-- -----------------------------------------------------------------------------
-- Parsers.
parseFields :: Parser [(ByteString, Word64)]
parseFields =
A.many1 (pFieldValue <* A.endOfLine)
{-
The /proc/meminfo file's contents takes the form:
MemTotal: 16336908 kB
MemFree: 9605680 kB
MemAvailable: 12756896 kB
Buffers: 1315348 kB
....
-}
pFieldValue :: Parser (ByteString, Word64)
pFieldValue =
(,) <$> pName <*> pValue
pName :: Parser ByteString
pName =
A.takeWhile (/= ':')
pValue :: Parser Word64
pValue = do
val <- A.char ':' *> A.skipSpace *> A.decimal
A.skipSpace
rest <- A.takeWhile (not . A.isSpace)
case rest of
"" -> pure val
"kB" -> pure $ 1024 * val
_ -> fail $ "Unexpected '" ++ BS.unpack rest ++ "'"