-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathCPU.hs
258 lines (201 loc) · 7.47 KB
/
CPU.hs
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
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
{-
Copyright 2015-2017 Markus Ongyerth, Stephan Guenther
This file is part of Monky.
Monky is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Monky is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with Monky. If not, see <http://www.gnu.org/licenses/>.
-}
{-# LANGUAGE CPP #-}
{-|
Module : Monky.CPU
Description : Allows access to information about the systems cpu
Maintainer : ongy
Stability : testing
Portability : Linux
-}
module Monky.CPU
( CPUHandle
, TempHandle
, FreqHandle
, NumaHandle(..)
, getCPUHandle
, getCPUPercent
, getNumaPercent
, getCPUTemp
, getCPUMaxScalingFreq
, ScalingType(..)
, getNumaHandles
, guessThermalZone
, getThermalZone
, getThermalZones
, getFreqHandle
, getFreqNuma
)
where
import Data.Char (isDigit)
import System.IO.Unsafe (unsafePerformIO)
import System.Directory (getDirectoryContents)
import Monky.Utility
import Data.List (isPrefixOf)
import Data.Maybe (listToMaybe, fromMaybe)
import Data.IORef
import Data.ByteString (ByteString)
import qualified Data.ByteString.Char8 as BS (readInt, words, unpack)
#if MIN_VERSION_base(4,8,0)
#else
import Control.Applicative ((<$>))
#endif
{- Stat work all-}
-- |The handle exported by this module
data CPUHandle = CPUH File (IORef [Int]) (IORef [Int])
-- |The handle for themperature
newtype TempHandle = TH (Maybe File)
-- |The handle for cpu frequency
newtype FreqHandle = FH [File]
-- Wrapper around numa nodes for stat reading
-- |Numa aware version of 'CPUHandle'
data NumaHandle = NumaHandle
{ numaCpus :: [String]
, numaHandle :: CPUHandle
}
-- |Which values should be returned by getCPUFreq
data ScalingType
= ScalingMax -- ^Use the maximum frequencie allowed
| ScalingCur -- ^Use the current frequencie used
| ScalingNone -- ^Don't open any files for scaling type
pathStat :: String
pathStat = "/proc/stat"
pathCPUBase :: String
pathCPUBase = "/sys/devices/system/cpu"
thermalBaseP :: String
thermalBaseP = "/sys/class/thermal/"
pathTemp :: String -> String
pathTemp zone = thermalBaseP ++ zone ++ "/temp"
pathMaxScaling :: String -> String
pathMaxScaling str = "/sys/devices/system/cpu/" ++ str ++ "/cpufreq/scaling_max_freq"
pathCurScaling :: String -> String
pathCurScaling str = "/sys/devices/system/cpu/" ++ str ++ "/cpufreq/scaling_cur_freq"
pathNumaBase :: String
pathNumaBase = "/sys/devices/system/node/"
getCPUFreqsCur :: [String] -> IO [File]
getCPUFreqsCur = mapM (fopen . pathCurScaling)
getCPUFreqsMax :: [String] -> IO [File]
getCPUFreqsMax = mapM (fopen . pathMaxScaling)
-- | Check if thermal zone is x86_pkg_temp
checkType :: (String -> Bool) -> String -> Bool
checkType f xs = unsafePerformIO $ do
f <$> readFile (thermalBaseP ++ xs ++ "/type")
-- | Get the filter function for the thermal zone getter
thermalGuesser :: String -> Bool
thermalGuesser =
let (ma, mi) = getKernelVersion
in if ma <= 3 && mi <= 13
then checkType ("pkg-temp-" `isPrefixOf`)
else checkType (== "x86_pkg_temp\n")
-- | Try to guess the thermal zones
guessThermalZones :: IO [String]
guessThermalZones = do
filter thermalGuesser . filter ("thermal_zone" `isPrefixOf`) <$> tzones
where tzones = getDirectoryContents thermalBaseP
-- |Tries to guess the thermal zone based on type
guessThermalZone :: IO (Maybe String)
guessThermalZone = fmap listToMaybe guessThermalZones
-- |Calcualate the work done by the cores (all, work)
calculateWork :: [[Int]] -> ([Int], [Int])
calculateWork xs =
let work = map (sum . take 3) xs
sall = zipWith (\x y -> x + sum y) work (map (drop 3) xs) in
(sall, work)
calculatePercent :: [Int] -> [Int] -> [Int] -> [Int] -> [Int]
calculatePercent sall work owork oall =
let cwork = zipWith (-) work owork
call = zipWith (-) sall oall in
zipWith (sdivBound . (* 100)) cwork call
readVals :: [ByteString] -> [Int]
readVals = map (fst . fromMaybe (error "CPUModule: Something in /proc/stat was unexpted") . BS.readInt) . tail
getPercent :: ([String] -> Bool) -> CPUHandle -> IO [Int]
getPercent f (CPUH file aref wref) = do
content <- map BS.words <$> readContent file
let cpus = filter (f . map BS.unpack) content
let d = map readVals cpus
let (sall, work) = calculateWork d
a <- readIORef aref
w <- readIORef wref
writeIORef wref work
writeIORef aref sall
return $ calculatePercent sall work w a
-- |Get the cpu usage in percent for each (virtual) cpu
getCPUPercent :: CPUHandle -> IO [Int]
getCPUPercent = getPercent (\(x:_) -> "cpu" `isPrefixOf` x && length x > 3)
-- |Read node information
getNumaPercent :: NumaHandle -> IO [Int]
getNumaPercent (NumaHandle cpus h) =
getPercent (\xs -> head xs `elem` cpus) h
-- |get current CPU temperature
getCPUTemp :: TempHandle -> IO Int
getCPUTemp (TH Nothing) = return (-1)
getCPUTemp (TH (Just f)) = do
temp <- readValue f
return (temp `div` 1000)
getMax :: [Int] -> Int
getMax = foldr max (-1)
{- |This function returns a frequency according the 'ScalingType' of the handle.
The returned valued will be the max of all (virtual) proceessors on the system.
-}
getCPUMaxScalingFreq :: FreqHandle -> IO Float
getCPUMaxScalingFreq (FH files) = do
vals <- mapM readValue files
return (fromIntegral (getMax vals) / 1000000)
-- open the files to read the frequency from
getCPUFreqs :: ScalingType -> [String] -> IO [File]
getCPUFreqs ScalingMax = getCPUFreqsMax
getCPUFreqs ScalingCur = getCPUFreqsCur
getCPUFreqs ScalingNone = (\_ -> return [])
getCPUs :: String -> IO [String]
getCPUs = fmap (filter isCPU) . getDirectoryContents
where isCPU ys = "cpu" `isPrefixOf` ys && all isDigit (drop 3 ys)
-- |Get a frequency handle by type
getFreqHandle :: ScalingType -> IO FreqHandle
getFreqHandle t = do
cpus <- getCPUs pathCPUBase
FH <$> getCPUFreqs t cpus
-- |Get a frequency handle limited to the cpus the numa handle uses
getFreqNuma :: ScalingType -> NumaHandle -> IO FreqHandle
getFreqNuma t (NumaHandle cpus _) =
FH <$> getCPUFreqs t cpus
getHandle :: String -> IO NumaHandle
getHandle path = do
getNumaHandle =<< getCPUs path
-- |Raw-ish access to numa handle, this can be (ab)used to make your own filter on cpus
getNumaHandle
:: [String] -- ^CPU "names" to include ([cpu0, cpu1, cpu2, ..])
-> IO NumaHandle
getNumaHandle cpus = do
workref <- newIORef ([0] :: [Int])
allref <- newIORef ([0] :: [Int])
stat <- fopen pathStat
return $ NumaHandle cpus (CPUH stat allref workref)
-- |Create an 'CPUHandle'
getCPUHandle :: IO CPUHandle
getCPUHandle = numaHandle <$> getHandle pathCPUBase
-- |Get the CPUs thermal zone
getThermalZone :: String -> IO TempHandle
getThermalZone = fmap (TH . Just) . fopen . pathTemp
-- |Get the CPUs thermal zones, will be same order as numa nodes (hopefully)
getThermalZones :: IO [TempHandle]
getThermalZones = do
real <- mapM (fmap (TH . Just) . fopen . pathTemp) =<< guessThermalZones
return (real ++ repeat (TH Nothing))
-- |Get the Numa aware handle
getNumaHandles :: IO [NumaHandle]
getNumaHandles = do
nodes <- filter isNode <$> getDirectoryContents pathNumaBase
mapM (getHandle . (pathNumaBase ++)) nodes
where isNode = ("node" `isPrefixOf`)