@@ -0,0 +1,147 @@
1+{-|
2+ Description: A CPU usage indicator
3+-}
4+module Example.CPU where
5+
6+import Control.Exception
7+import Control.Monad.Fix
8+import Control.Monad.IO.Class
9+import Data.Time
10+import Data.Word
11+
12+import Reflex
13+import Reflex.Vty
14+
15+-- | Each constructor represents a cpu statistic column as presented in @/proc/stat@
16+data CpuStat
17+ = CpuStat_User
18+ | CpuStat_Nice
19+ | CpuStat_System
20+ | CpuStat_Idle
21+ | CpuStat_Iowait
22+ | CpuStat_Irq
23+ | CpuStat_Softirq
24+ | CpuStat_Steal
25+ | CpuStat_Guest
26+ | CpuStat_GuestNice
27+ deriving (Show, Read, Eq, Ord, Enum, Bounded)
28+
29+-- | Read @/proc/stat@
30+getCpuStat :: IO (Maybe (CpuStat -> Word64))
31+getCpuStat = do
32+ s <- readFile "/proc/stat"
33+ _ <- evaluate $ length s -- Make readFile strict
34+ pure $ do
35+ cpuSummaryLine : _ <- pure $ lines s
36+ [user, nice, system, idle, iowait, irq, softirq, steal, guest, guestNice] <- pure $ map read $ words $ drop 4 cpuSummaryLine
37+ pure $ \case
38+ CpuStat_User -> user
39+ CpuStat_Nice -> nice
40+ CpuStat_System -> system
41+ CpuStat_Idle -> idle
42+ CpuStat_Iowait -> iowait
43+ CpuStat_Irq -> irq
44+ CpuStat_Softirq -> softirq
45+ CpuStat_Steal -> steal
46+ CpuStat_Guest -> guest
47+ CpuStat_GuestNice -> guestNice
48+
49+sumStats :: (CpuStat -> Word64) -> [CpuStat] -> Word64
50+sumStats get stats = sum $ get <$> stats
51+
52+-- | user + nice + system + irq + softirq + steal
53+nonIdleStats :: [CpuStat]
54+nonIdleStats =
55+ [ CpuStat_User
56+ , CpuStat_Nice
57+ , CpuStat_System
58+ , CpuStat_Irq
59+ , CpuStat_Softirq
60+ , CpuStat_Steal
61+ ]
62+
63+-- | idle + iowait
64+idleStats :: [CpuStat]
65+idleStats =
66+ [ CpuStat_Idle
67+ , CpuStat_Iowait
68+ ]
69+
70+-- | Draws the cpu usage percent as a live-updating bar graph. The output should look like:
71+--
72+--
73+-- > ╔═══════ CPU Usage ══════╗
74+-- > ║ ║
75+-- > ║ ║
76+-- > ║ ║
77+-- > ║ ║
78+-- > ║ ║
79+-- > ║ ║
80+-- > ║████████████████████████║
81+-- > ║████████████████████████║
82+-- > ║████████████████████████║
83+-- > ║████████████████████████║
84+-- > ╚════════════════════════╝
85+--
86+cpuStats
87+ :: ( Reflex t
88+ , MonadFix m
89+ , MonadHold t m
90+ , MonadIO (Performable m)
91+ , MonadIO m
92+ , PerformEvent t m
93+ , PostBuild t m
94+ , TriggerEvent t m
95+ , HasDisplaySize t m
96+ , ImageWriter t m
97+ , MonadLayout t m
98+ , MonadFocus t m
99+ , MonadNodeId m
100+ , HasVtyWidgetCtx t m
101+ , HasVtyInput t m
102+ )
103+ => m ()
104+cpuStats = do
105+ tick <- tickLossy 0.25 =<< liftIO getCurrentTime
106+ cpuStat :: Event t (Word64, Word64) <- fmap (fmapMaybe id) $
107+ performEvent $ ffor tick $ \_ -> do
108+ get <- liftIO getCpuStat
109+ pure $ case get of
110+ Nothing -> Nothing
111+ Just get' -> Just (sumStats get' nonIdleStats, sumStats get' idleStats)
112+ active <- foldDyn cpuPercentStep ((0, 0), 0) cpuStat
113+ let pct = fmap snd active
114+ boxTitle (pure doubleBoxStyle) " CPU Usage " $ col $ do
115+ grout flex blank
116+ dh <- displayHeight
117+ let h :: Dynamic t Int = ceiling <$> ((*) <$> (fromIntegral <$> dh) <*> pct)
118+ tile (fixed h) $ fill '█'
119+
120+-- | Determine the current percentage usage according to this algorithm:
121+--
122+-- PrevIdle = previdle + previowait
123+-- Idle = idle + iowait
124+--
125+-- PrevNonIdle = prevuser + prevnice + prevsystem + previrq + prevsoftirq + prevsteal
126+-- NonIdle = user + nice + system + irq + softirq + steal
127+--
128+-- PrevTotal = PrevIdle + PrevNonIdle
129+-- Total = Idle + NonIdle
130+--
131+-- totald = Total - PrevTotal
132+-- idled = Idle - PrevIdle
133+--
134+-- CPU_Percentage = (totald - idled)/totald
135+--
136+-- Source: https://stackoverflow.com/questions/23367857/accurate-calculation-of-cpu-usage-given-in-percentage-in-linux
137+cpuPercentStep
138+ :: (Word64, Word64) -- Current active, Current idle
139+ -> ((Word64, Word64), Double) -- (Previous idle, Previous total), previous percent
140+ -> ((Word64, Word64), Double) -- (New idle, new total), percent
141+cpuPercentStep (nonidle, idle) ((previdle, prevtotal), _) =
142+ let total = idle + nonidle
143+ idled = idle - previdle
144+ totald = total - prevtotal
145+ in ( (idle, total)
146+ , (fromIntegral $ totald - idled) / fromIntegral totald
147+ )