{-# LANGUAGE UnicodeSyntax #-}
module System.Linux.ProcStat ( ProcState (..)
                             , ProcFlag  (..)
                             , ProcInfo  (..)
                             , Pid
                             , procStat
                             ) where

import Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as S8
import Data.Word
import Data.Attoparsec.Char8
import Control.Applicative
import System.IO
import Control.Exception (bracket)
import Data.Bits

type Pid = Int

data ProcState = Running
               | Sleeping
               | DiskSleeping
               | Zombie
               | Traced
               | Paging
               deriving Show

data ProcFlag = KSoftIrqD
              | Starting
              | Exiting
              | ExitPidDone
              | VCpu
              | WqWorker
              | ForkNoExec
              | MceProcess
              | SuperPriv
              | DumpCore
              | Signaled
              | MemAlloc
              | UsedMath
              | Freezing
              | NoFreeze
              | Frozen
              | FsTrans
              | KSwapD
              | OomOrigin
              | LessThrottle
              | KThread
              | Randomize
              | SwapWrite
              | SpreadPage
              | SpreadSlab
              | ThreadBound
              | MceEarly
              | MemPolicy
              | MutexTester
              | FreezerSkip
              | FreezerNoSig
              deriving (Show, Enum)

data ProcInfo = ProcInfo { procPid           Pid
                         , procName          ByteString
                         , procState         ProcState
                         , procPPid          Int
                         , procGId           Int
                         , procSId           Int
                         , procTty           Int
                         , procTtyGid        Int
                         , procFlags         [ProcFlag]
                         , procMinFlt        Word
                         , procCMinFlt       Word
                         , procMajFlt        Word
                         , procCMajFlt       Word
                         , procUTime         Word
                         , procSTime         Word
                         , procCUTime        Int
                         , procCSTime        Int
                         , procPriority      Int
                         , procNice          Int
                         , procNumThreads    Int
                         , procStartTime     Word64
                         , procVSize         Word
                         , procRss           Int
                         , procRssLim        Word
                         , procStartCode     Word
                         , procEndCode       Word
                         , procStartStack    Word
                         , procEsp           Word
                         , procEip           Word
                         , procSignal        Word
                         , procBlocked       Word
                         , procSigIgnore     Word
                         , procSigCatch      Word
                         , procWChan         Word
                         , procNSwap         Word
                         , procCNSwap        Word
                         , procExitSignal    Int
                         , procCpuNum        Int
                         , procRtPriority    Word
                         , procPolicy        Word
                         , procBlkIoTicks    Word64
                         , procGuestTime     Word
                         , procCGuestTime    Int
                         } deriving Show

charToProcState  Char  Maybe ProcState
charToProcState c = case c of
    'R'  Just Running
    'S'  Just Sleeping
    'D'  Just DiskSleeping
    'Z'  Just Zombie
    'T'  Just Traced
    'W'  Just Paging
    _    Nothing

flagToProcFlags  Word  [ProcFlag]
flagToProcFlags x = foldr step [] flagMap
  where
    flagMap = zip (iterate (*2) 1) [KSoftIrqD .. FreezerNoSig]
    hasFlag  Bits a  a  a  Bool
    hasFlag a b = a .&. b == b
    step (val, pflag) pflags | x `hasFlag` val = pflag : pflags
                             | otherwise       = pflags

parser  Parser ProcInfo
parser = do
    pid              sdc
    name             dos $ char '(' *> takeTill (== ')') <* anyChar
    state            maybe empty return =<< charToProcState <$> dos anyChar
    ppid             sdc
    gid              sdc
    sid              sdc
    tty              sdc
    ttyGid           sdc
    flags            flagToProcFlags <$> sdc
    minFlt           dc
    cMinFlt          dc
    majFlt           dc
    cMajFlt          dc
    uTime            dc
    sTime            dc
    cuTime           sdc
    csTime           sdc
    pri              sdc
    nice             sdc
    nt               sdc
    _                dos $ char '0'
    st               sdc
    vSize            dc
    rss              dc
    rssLim           dc
    startCode        dc
    endCode          dc
    startStack       dc
    esp              dc
    eip              dc
    signal           dc
    blockedS         dc
    sigIgnore        dc
    sigCatch         dc
    wChan            dc
    nSwap            dc
    cnSwap           dc
    exitSignal       sdc
    cpuNum           sdc
    rtPri            dc
    policy           dc
    blkIOTicks       dc
    guestTime        dc
    cGuestTime       signed decimal
    return $ ProcInfo pid
                      name
                      state
                      ppid
                      gid
                      sid
                      tty
                      ttyGid
                      flags
                      minFlt
                      cMinFlt
                      majFlt
                      cMajFlt
                      uTime
                      sTime
                      cuTime
                      csTime
                      pri
                      nice
                      nt
                      st
                      vSize
                      rss
                      rssLim
                      startCode
                      endCode
                      startStack
                      esp
                      eip
                      signal
                      blockedS
                      sigIgnore
                      sigCatch
                      wChan
                      nSwap
                      cnSwap
                      exitSignal
                      cpuNum
                      rtPri
                      policy
                      blkIOTicks
                      guestTime
                      cGuestTime
  where
    dos  Parser a  Parser a
    dos p = p >>= \x  space >> return x
    dc  Integral a  Parser a
    dc = dos decimal
    sdc  Integral a  Parser a
    sdc = signed dc

procStat  Pid  IO (Maybe ProcInfo)
procStat pid = (`catch` const (return Nothing)) $ do
    s  bracket (openFile filename ReadMode) hClose S8.hGetContents
    return $ maybeResult $ parse parser s
  where
    filename = "/proc/" ++ show pid ++ "/stat"