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
data CPUHandle = CPUH File (IORef [Int]) (IORef [Int])
newtype TempHandle = TH (Maybe File)
newtype FreqHandle = FH [File]
data NumaHandle = NumaHandle
{ numaCpus :: [String]
, numaHandle :: CPUHandle
}
data ScalingType
= ScalingMax
| ScalingCur
| ScalingNone
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)
isX86PkgTemp :: String -> Bool
isX86PkgTemp xs = unsafePerformIO $do
str <- readFile (thermalBaseP ++ xs ++ "/type")
return (str == "x86_pkg_temp\n")
guessThermalZones :: IO [String]
guessThermalZones = do
filter isX86PkgTemp . filter ("thermal_zone" `isPrefixOf`) <$> tzones
where tzones = getDirectoryContents thermalBaseP
guessThermalZone :: IO (Maybe String)
guessThermalZone = fmap listToMaybe guessThermalZones
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
getCPUPercent :: CPUHandle -> IO [Int]
getCPUPercent = getPercent (\(x:_) -> "cpu" `isPrefixOf` x && length x > 3)
getNumaPercent :: NumaHandle -> IO [Int]
getNumaPercent (NumaHandle cpus h) =
getPercent (\xs -> head xs `elem` cpus) h
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)
getCPUMaxScalingFreq :: FreqHandle -> IO Float
getCPUMaxScalingFreq (FH files) = do
vals <- mapM readValue files
return (fromIntegral (getMax vals) / 1000000)
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)
getFreqHandle :: ScalingType -> IO FreqHandle
getFreqHandle t = do
cpus <- getCPUs pathCPUBase
FH <$> getCPUFreqs t cpus
getFreqNuma :: ScalingType -> NumaHandle -> IO FreqHandle
getFreqNuma t (NumaHandle cpus _) =
FH <$> getCPUFreqs t cpus
getHandle :: String -> IO NumaHandle
getHandle path = do
getNumaHandle =<< getCPUs path
getNumaHandle
:: [String]
-> IO NumaHandle
getNumaHandle cpus = do
workref <- newIORef ([0] :: [Int])
allref <- newIORef ([0] :: [Int])
stat <- fopen pathStat
return $ NumaHandle cpus (CPUH stat allref workref)
getCPUHandle :: IO CPUHandle
getCPUHandle = numaHandle <$> getHandle pathCPUBase
getThermalZone :: String -> IO TempHandle
getThermalZone = fmap (TH . Just) . fopen . pathTemp
getThermalZones :: IO [TempHandle]
getThermalZones = do
real <- mapM (fmap (TH . Just) . fopen . pathTemp) =<< guessThermalZones
return (real ++ repeat (TH Nothing))
getNumaHandles :: IO [NumaHandle]
getNumaHandles = do
nodes <- filter isNode <$> getDirectoryContents pathNumaBase
mapM (getHandle . (pathNumaBase ++)) nodes
where isNode = ("node" `isPrefixOf`)