1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
|
{-#LANGUAGE RecordWildCards#-}
-----------------------------------------------------------------------------
-- |
-- Module : Plugins.Monitors.Cpu
-- Copyright : (c) 2011, 2017 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 cpu monitor for Xmobar
--
-----------------------------------------------------------------------------
module Xmobar.Plugins.Monitors.Cpu
( startCpu
, runCpu
, cpuConfig
, CpuDataRef
, CpuOpts
, CpuArguments
, parseCpu
, getArguments
) where
import Xmobar.Plugins.Monitors.Common
import qualified Data.ByteString.Lazy.Char8 as B
import Data.IORef (IORef, newIORef, readIORef, writeIORef)
import System.Console.GetOpt
import Xmobar.App.Timer (doEveryTenthSeconds)
import Control.Monad (void)
newtype CpuOpts = CpuOpts
{ loadIconPattern :: Maybe IconPattern
}
defaultOpts :: CpuOpts
defaultOpts = CpuOpts
{ loadIconPattern = Nothing
}
options :: [OptDescr (CpuOpts -> CpuOpts)]
options =
[ Option "" ["load-icon-pattern"] (ReqArg (\x o ->
o { loadIconPattern = Just $ parseIconPattern x }) "") ""
]
barField :: String
barField = "bar"
vbarField :: String
vbarField = "vbar"
ipatField :: String
ipatField = "ipat"
totalField :: String
totalField = "total"
userField :: String
userField = "user"
niceField :: String
niceField = "nice"
systemField :: String
systemField = "system"
idleField :: String
idleField = "idle"
iowaitField :: String
iowaitField = "iowait"
cpuConfig :: IO MConfig
cpuConfig =
mkMConfig
"Cpu: <total>%"
[ barField
, vbarField
, ipatField
, totalField
, userField
, niceField
, systemField
, idleField
, iowaitField
]
type CpuDataRef = IORef [Int]
-- Details about the fields here: https://www.kernel.org/doc/Documentation/filesystems/proc.txt
cpuData :: IO [Int]
cpuData = cpuParser <$> B.readFile "/proc/stat"
readInt :: B.ByteString -> Int
readInt bs = case B.readInt bs of
Nothing -> 0
Just (i, _) -> i
cpuParser :: B.ByteString -> [Int]
cpuParser = map readInt . tail . B.words . head . B.lines
data CpuData = CpuData {
cpuUser :: !Float,
cpuNice :: !Float,
cpuSystem :: !Float,
cpuIdle :: !Float,
cpuIowait :: !Float,
cpuTotal :: !Float
}
convertToCpuData :: [Float] -> CpuData
convertToCpuData (u:n:s:id:iw:_) = CpuData {
cpuUser = u,
cpuNice = n,
cpuSystem = s,
cpuIdle = id,
cpuIowait = iw,
cpuTotal = sum [u,n,s]
}
convertToCpuData args = error $ "convertToCpuData: Unexpected list" <> show args
parseCpu :: CpuDataRef -> IO CpuData
parseCpu cref =
do a <- readIORef cref
b <- cpuData
writeIORef cref b
let dif = zipWith (-) b a
tot = fromIntegral $ sum dif
safeDiv n = case tot of
0 -> 0
v -> (fromIntegral n) / v
percent = map safeDiv dif
return $ convertToCpuData percent
conditionalCompute :: [String] -> String -> IO String -> IO String
conditionalCompute allFields field action = if field `elem` allFields
then action
else pure []
data Field = Field {
fieldName :: !String,
fieldCompute :: !ShouldCompute
} deriving (Eq, Ord, Show)
data ShouldCompute = Compute | Skip deriving (Eq, Ord, Show)
formatField :: PureConfig -> CpuOpts -> CpuData -> Field -> IO String
formatField cpuParams cpuOpts cpuInfo@CpuData{..} Field{..}
| fieldName == barField = if fieldCompute == Compute
then pShowPercentBar cpuParams (100 * cpuTotal) cpuTotal
else pure []
| fieldName == vbarField = if fieldCompute == Compute
then pShowVerticalBar cpuParams (100 * cpuTotal) cpuTotal
else pure []
| fieldName == ipatField = if fieldCompute == Compute
then pShowIconPattern (loadIconPattern cpuOpts) cpuTotal
else pure []
| otherwise = if fieldCompute == Compute
then pShowPercentWithColors cpuParams (getFieldValue fieldName cpuInfo)
else pure []
getFieldValue :: String -> CpuData -> Float
getFieldValue field CpuData{..}
| field == barField = cpuTotal
| field == vbarField = cpuTotal
| field == ipatField = cpuTotal
| field == totalField = cpuTotal
| field == userField = cpuUser
| field == niceField = cpuNice
| field == systemField = cpuSystem
| field == idleField = cpuIdle
| otherwise = cpuIowait
computeFields :: [String] -> [String] -> [Field]
computeFields [] _ = []
computeFields (x:xs) inputFields =
if x `elem` inputFields
then (Field {fieldName = x, fieldCompute = Compute}) :
(computeFields xs inputFields)
else (Field {fieldName = x, fieldCompute = Skip}) : (computeFields xs inputFields)
formatCpu :: CpuArguments -> CpuData -> IO [String]
formatCpu args@CpuArguments{..} cpuData = do
strs <- mapM (formatField cpuParams cpuOpts cpuData) cpuFields
pure $ filter (not . null) strs
getInputFields :: CpuArguments -> [String]
getInputFields CpuArguments{..} = map (\(_,f,_) -> f) cpuInputTemplate
optimizeAllTemplate :: CpuArguments -> CpuArguments
optimizeAllTemplate args@CpuArguments{..} =
let inputFields = getInputFields args
allTemplates = filter (\(field, _) -> field `elem` inputFields) cpuAllTemplate
in args { cpuAllTemplate = allTemplates }
data CpuArguments = CpuArguments {
cpuDataRef :: !CpuDataRef,
cpuParams :: !PureConfig,
cpuArgs :: ![String],
cpuOpts :: !CpuOpts,
cpuInputTemplate :: ![(String, String, String)], -- [("Cpu: ","total","% "),("","user","%")]
cpuAllTemplate :: ![(String, [(String, String, String)])], -- [("bar",[]),("vbar",[]),("ipat",[]),("total",[]),...]
cpuFields :: ![Field]
}
getArguments :: [String] -> IO CpuArguments
getArguments cpuArgs = do
initCpuData <- cpuData
cpuDataRef <- newIORef initCpuData
cpuData <- parseCpu cpuDataRef
cpuParams <- computePureConfig cpuArgs cpuConfig
cpuInputTemplate <- runTemplateParser cpuParams
cpuAllTemplate <- runExportParser (pExport cpuParams)
nonOptions <- case getOpt Permute commonOptions cpuArgs of
(_, n, []) -> pure n
(_,_,errs) -> error $ "getArguments: " <> show errs
cpuOpts <- case getOpt Permute options nonOptions of
(o, _, []) -> pure $ foldr id defaultOpts o
(_,_,errs) -> error $ "getArguments options: " <> show errs
let cpuFields = computeFields (map fst cpuAllTemplate) (map (\(_,f,_) -> f) cpuInputTemplate)
pure $ optimizeAllTemplate CpuArguments{..}
runCpu :: CpuArguments -> IO String
runCpu args@CpuArguments{..} = do
cpuValue <- parseCpu cpuDataRef
temMonitorValues <- formatCpu args cpuValue
let templateInput = TemplateInput { temInputTemplate = cpuInputTemplate, temAllTemplate = cpuAllTemplate, ..}
pureParseTemplate cpuParams templateInput
startCpu :: [String] -> Int -> (String -> IO ()) -> IO ()
startCpu args refreshRate cb = do
cpuArgs <- getArguments args
doEveryTenthSeconds refreshRate (runCpu cpuArgs >>= cb)
|