From 22c9d47dfc9eb6113620258400424bc777cb3236 Mon Sep 17 00:00:00 2001 From: Jan Beinke <git@janbeinke.com> Date: Fri, 14 Feb 2020 11:36:10 +0100 Subject: [PATCH] Add a native cpu-usage block --- src/QBar/Blocks.hs | 2 + src/QBar/Blocks/CpuUsage.hs | 152 ++++++++++++++++++++++++++++++++++++ src/QBar/DefaultConfig.hs | 4 +- 3 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 src/QBar/Blocks/CpuUsage.hs diff --git a/src/QBar/Blocks.hs b/src/QBar/Blocks.hs index cedacdb..1b15b76 100644 --- a/src/QBar/Blocks.hs +++ b/src/QBar/Blocks.hs @@ -1,8 +1,10 @@ module QBar.Blocks ( QBar.Blocks.Battery.batteryBlock, + QBar.Blocks.CpuUsage.cpuUsageBlock, QBar.Blocks.Date.dateBlock, ) where import qualified QBar.Blocks.Battery +import qualified QBar.Blocks.CpuUsage import qualified QBar.Blocks.Date diff --git a/src/QBar/Blocks/CpuUsage.hs b/src/QBar/Blocks/CpuUsage.hs new file mode 100644 index 0000000..12fc773 --- /dev/null +++ b/src/QBar/Blocks/CpuUsage.hs @@ -0,0 +1,152 @@ +{-# LANGUAGE TemplateHaskell #-} + +module QBar.Blocks.CpuUsage where + +import Control.Applicative ((<|>)) +import Control.Lens +import Control.Monad.State +import qualified Data.Attoparsec.Text.Lazy as AT +import qualified Data.Text.Lazy as T +import QBar.BlockOutput +import QBar.Blocks.Utils +import QBar.Core + +{- + For time accounting the guest fields need to be ignored according to the kernel source code + (https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/kernel/sched/cputime.c#n150) + the accounting also counts the guest time to user or nice respectively so applications + that are not aware of the new fields do not loose time. +-} +data CpuStat + = CpuStat + { userTime :: Int, + niceTime :: Int, + systemTime :: Int, + idleTime :: Int, + iowaitTime :: Int, + irqTime :: Int, + softirqTime :: Int, + stealTime :: Int + } + deriving (Show) + +getCpuStat :: IO (Maybe CpuStat) +getCpuStat = parseFile "/proc/stat" cpuStat + where + skipLine :: AT.Parser () + skipLine = AT.skipWhile (not . AT.isEndOfLine) *> AT.skipWhile AT.isEndOfLine + cpuStat :: AT.Parser CpuStat + cpuStat = cpuLine <|> (skipLine *> cpuStat) + cpuLine :: AT.Parser CpuStat + cpuLine = AT.string "cpu " *> do + userTime' <- AT.skipSpace *> AT.decimal + niceTime' <- AT.skipSpace *> AT.decimal + systemTime' <- AT.skipSpace *> AT.decimal + idleTime' <- AT.skipSpace *> AT.decimal + iowaitTime' <- AT.skipSpace *> AT.decimal + irqTime' <- AT.skipSpace *> AT.decimal + softirqTime' <- AT.skipSpace *> AT.decimal + stealTime' <- AT.skipSpace *> AT.decimal + return $ + CpuStat + { userTime = userTime', + niceTime = niceTime', + systemTime = systemTime', + idleTime = idleTime', + iowaitTime = iowaitTime', + irqTime = irqTime', + softirqTime = softirqTime', + stealTime = stealTime' + } + +differenceCpuStat :: CpuStat -> CpuStat -> CpuStat +differenceCpuStat a b = + CpuStat + { userTime = userTime a - userTime b, + niceTime = niceTime a - niceTime b, + systemTime = systemTime a - systemTime b, + idleTime = idleTime a - idleTime b, + iowaitTime = iowaitTime a - iowaitTime b, + irqTime = irqTime a - irqTime b, + softirqTime = softirqTime a - softirqTime b, + stealTime = stealTime a - stealTime b + } + +cpuTotalTime :: Num a => CpuStat -> a +cpuTotalTime + CpuStat + { userTime, + niceTime, + systemTime, + idleTime, + iowaitTime, + irqTime, + softirqTime, + stealTime + } = + fromIntegral . sum $ + [ userTime, + niceTime, + systemTime, + idleTime, + iowaitTime, + irqTime, + softirqTime, + stealTime + ] + +cpuUsage :: CpuStat -> Float +cpuUsage stat@CpuStat {idleTime, iowaitTime} = 1 - (totalIdleTime / totalTime) + where + totalTime :: Num a => a + totalTime = cpuTotalTime stat + totalIdleTime :: Num a => a + totalIdleTime = fromIntegral $ idleTime + iowaitTime + +data CpuBlockState + = CpuBlockState + { _lastCpuStat :: CpuStat, + _lastCpuUsage :: Float + } + deriving (Show) + +makeLenses ''CpuBlockState + +cpuUsageBlock :: Int -> PullBlock +cpuUsageBlock decimalPlaces = evalStateT cpuUsageBlock' createState + where + cpuUsageBlock' :: StateT CpuBlockState Block PullMode + cpuUsageBlock' = do + updateState + importance <- cpuUsageImportance + updateBlock . mkBlockOutput . importantText importance =<< cpuUsageText + cpuUsageBlock' + createState :: CpuBlockState + createState = + CpuBlockState + { _lastCpuStat = CpuStat 0 0 0 0 0 0 0 0, + _lastCpuUsage = 0 + } + cpuUsageImportance :: Monad m => StateT CpuBlockState m Importance + cpuUsageImportance = toImportance (100, 90, 80, 60, 50, 0) <$> use lastCpuUsage + cpuUsageTextWidth :: Num a => a + cpuUsageTextWidth + | decimalPlaces == 0 = 3 + | otherwise = fromIntegral $ 4 + decimalPlaces + cpuUsageText :: Monad m => StateT CpuBlockState m Text + cpuUsageText = do + cpuUsage' <- use lastCpuUsage + let cpuUsageText' = formatFloatN decimalPlaces cpuUsage' <> "%" + if T.length cpuUsageText' <= cpuUsageTextWidth + then return $ T.justifyRight cpuUsageTextWidth ' ' cpuUsageText' + else return $ "99" <> (if decimalPlaces == 0 then "" else "." <> T.replicate (fromIntegral decimalPlaces) "9") <> "%" + updateState :: MonadBarIO m => StateT CpuBlockState m () + updateState = do + oldCpuStat <- use lastCpuStat + newCpuStat' <- liftIO getCpuStat + case newCpuStat' of + Nothing -> return () + (Just newCpuStat) -> do + let cpuStatDiff = differenceCpuStat newCpuStat oldCpuStat + lastCpuUsage .= 100 * cpuUsage cpuStatDiff + lastCpuStat .= newCpuStat diff --git a/src/QBar/DefaultConfig.hs b/src/QBar/DefaultConfig.hs index 8118633..1d09d4a 100644 --- a/src/QBar/DefaultConfig.hs +++ b/src/QBar/DefaultConfig.hs @@ -18,18 +18,18 @@ generateDefaultBarConfig = do let todo = systemInfoInterval (blockScript $ blockLocation "todo") let wifi = systemInfoInterval (blockScript $ blockLocation "wifi2") >-> modify (addIcon "📡\xFE0E") let networkEnvironment = systemInfoInterval (blockScript $ blockLocation "network-environment") - let cpu = systemInfoInterval (blockScript $ blockLocation "cpu_usage") >-> modify ((blockName ?~ "cpu") . addIcon "💻\xFE0E") >-> autoPadding let ram = systemInfoInterval (blockScript $ blockLocation "memory") >-> modify (addIcon "ðŸ\xFE0E") >-> autoPadding let temperature = systemInfoInterval (blockScript $ blockLocation "temperature") >-> autoPadding let volumeBlock = persistentBlockScript $ blockLocation "volume-pulseaudio -S -F3" let battery = systemInfoInterval $ batteryBlock >-> modify (blockName ?~ "battery") + let cpuUsage = systemInfoInterval $ cpuUsageBlock 1 >-> modify ((blockName ?~ "cpuUsage") . addIcon "💻\xFE0E") addBlock dateBlock addBlock battery addBlock volumeBlock addBlock temperature addBlock ram - addBlock cpu + addBlock cpuUsage addBlock networkEnvironment addBlock wifi addBlock todo -- GitLab