on develop help sign in
example: add cpu usage indicator example
commitc870264921b0111af1d60b561d78fc60625cb3b7 authorAli Abrar <aliabrar@gmail.com> dateWed 24 Mar 2021, 15:03 (6 years ago) parentf458c84
3 files changed+169−12
src-bin/Example/CPU.hs+147 src-bin/example.hs+21−11 src/Reflex/Vty/Widget.hs+1−1
src-bin/Example/CPU.hsnew file+147
@@ -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+ )
src-bin/example.hs+21−11
@@ -15,6 +15,8 @@ import Reflex
1515import Reflex.Network
1616import Reflex.Vty
1717
18+import Example.CPU
19+
1820type VtyExample t m =
1921 ( MonadFix m
2022 , Reflex t
@@ -35,29 +37,39 @@ data Example = Example_TextEditor
3537 | Example_Todo
3638 | Example_ScrollableTextDisplay
3739 | Example_ClickButtonsGetEmojis
40+ | Example_CPUStat
3841 deriving (Show, Read, Eq, Ord, Enum, Bounded)
3942
40−main :: IO ()
41−main = mainWidget $ do
43+withCtrlC :: (Monad m, HasVtyInput t m, Reflex t) => m () -> m (Event t ())
44+withCtrlC f = do
4245 inp <- input
46+ f
47+ return $ fforMaybe inp $ \case
48+ V.EvKey (V.KChar 'c') [V.MCtrl] -> Just ()
49+ _ -> Nothing
50+
51+main :: IO ()
52+main = mainWidget $ withCtrlC $ do
4353 initManager_ $ do
4454 tabNavigation
4555 let gf = grout . fixed
46− tf = tile . fixed
56+ t = tile flex
4757 buttons = col $ do
48− gf 4 $ col $ do
58+ gf 3 $ col $ do
4959 gf 1 $ text "Select an example."
5060 gf 1 $ text "Esc will bring you back here."
5161 gf 1 $ text "Ctrl+c to quit."
52− a <- tf 5 $ textButtonStatic def "Todo List"
53− b <- tf 5 $ textButtonStatic def "Text Editor"
54− c <- tf 5 $ textButtonStatic def "Scrollable text display"
55− d <- tf 5 $ textButtonStatic def "Clickable buttons"
62+ a <- t $ textButtonStatic def "Todo List"
63+ b <- t $ textButtonStatic def "Text Editor"
64+ c <- t $ textButtonStatic def "Scrollable text display"
65+ d <- t $ textButtonStatic def "Clickable buttons"
66+ e <- t $ textButtonStatic def "CPU Usage"
5667 return $ leftmost
5768 [ Left Example_Todo <$ a
5869 , Left Example_TextEditor <$ b
5970 , Left Example_ScrollableTextDisplay <$ c
6071 , Left Example_ClickButtonsGetEmojis <$ d
72+ , Left Example_CPUStat <$ e
6173 ]
6274 let escapable w = do
6375 void w
@@ -70,11 +82,9 @@ main = mainWidget $ do
7082 Left Example_TextEditor -> escapable testBoxes
7183 Left Example_ScrollableTextDisplay -> escapable scrolling
7284 Left Example_ClickButtonsGetEmojis -> escapable easyExample
85+ Left Example_CPUStat -> escapable cpuStats
7386 Right () -> buttons
7487 return ()
75− return $ fforMaybe inp $ \case
76− V.EvKey (V.KChar 'c') [V.MCtrl] -> Just ()
77− _ -> Nothing
7888
7989-- * Mouse button and emojis example
8090easyExample :: (VtyExample t m, Manager t m, MonadHold t m) => m (Event t ())
src/Reflex/Vty/Widget.hs+1−1
@@ -534,7 +534,7 @@ fill c = do
534534 let fillImg = current $ liftA2 (\w h -> [V.charFill V.defAttr c w h]) dw dh
535535 tellImages fillImg
536536
537−-- | Fill the background with the bottom
537+-- | Fill the background with the bottom box style
538538hRule :: (HasDisplaySize t m, ImageWriter t m) => BoxStyle -> m ()
539539hRule boxStyle = fill (_boxStyle_s boxStyle)
540540