module System.CPU (
CPU(..)
, getCPUs
, tryGetCPUs
, physicalProcessors
, physicalCores
, logicalCores
, hyperthreadingFactor
, hyperthreadingInUse
) where
import Control.Applicative
import Control.Arrow
import Control.DeepSeq
import qualified Data.Attoparsec.ByteString.Char8 as A
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as BC
import Data.Data
import Data.Foldable
import Data.List
import Data.Maybe
import Data.Word
import GHC.Generics
data CPU = CPU {
processorID :: !Word32
, vendor :: !(Maybe B.ByteString)
, model :: !(Maybe Word32)
, modelName :: !(Maybe B.ByteString)
, revision :: !(Maybe Word32)
, microcode :: !(Maybe Word32)
, freq :: !Double
, cache :: !(Maybe Word32)
, physicalID :: !Word32
, siblings :: !Word32
, coreID :: !Word32
, apicID :: !(Maybe Word32)
, fpu :: !(Maybe Bool)
, fpuExcept :: !(Maybe Bool)
, flags :: !(Maybe [B.ByteString])
, bogoMIPS :: !Double
, cacheAlignment :: !(Maybe Word32)
, physicalAddress :: !(Maybe Word32)
, virtualAddress :: !(Maybe Word32)
} deriving ( Eq
, Ord
, Read
, Show
, Data
, Typeable
, Generic
, NFData
)
parsePair :: B.ByteString -> A.Parser a -> A.Parser a
parsePair k vp = A.string k
*> A.skipSpace
*> A.char ':'
*> A.skipSpace
*> vp
parseProcessor :: A.Parser Word32
parseProcessor = parsePair "processor" A.decimal
parseVendor :: A.Parser B.ByteString
parseVendor = parsePair "vendor_id" A.takeByteString
parseModel :: A.Parser Word32
parseModel = parsePair "model" A.decimal
parseModelName :: A.Parser B.ByteString
parseModelName = parsePair "model name" A.takeByteString
parseRevision :: A.Parser Word32
parseRevision = parsePair "stepping" A.decimal
parseMicrocode :: A.Parser Word32
parseMicrocode = parsePair "microcode" (A.string "0x" *> A.hexadecimal)
parseFreq :: A.Parser Double
parseFreq = parsePair "cpu MHz" A.double
parseCache :: A.Parser Word32
parseCache = parsePair "cache size"
(A.decimal <* (A.skipSpace *> A.string "KB"))
parsePhysicalID :: A.Parser Word32
parsePhysicalID = parsePair "physical id" A.decimal
parseSiblings :: A.Parser Word32
parseSiblings = parsePair "siblings" A.decimal
parseCoreID :: A.Parser Word32
parseCoreID = parsePair "core id" A.decimal
parseApicID :: A.Parser Word32
parseApicID = parsePair "apicid" A.decimal
parseFpu :: A.Parser Bool
parseFpu = parsePair "fpu" parseBool
parseFpuExcept :: A.Parser Bool
parseFpuExcept = parsePair "fpu_exception" parseBool
parseFlags :: A.Parser [B.ByteString]
parseFlags = parsePair "flags" parseWords
parseBogoMIPS :: A.Parser Double
parseBogoMIPS = parsePair "bogomips" A.double
parseCacheAlignment :: A.Parser Word32
parseCacheAlignment = parsePair "cache_alignment" A.decimal
parseAddresses :: A.Parser (Word32, Word32)
parseAddresses = parsePair "address sizes"
((,) <$> parsePhysicalAddress
<*> parseVirtualAddress)
parsePhysicalAddress :: A.Parser Word32
parsePhysicalAddress = A.decimal <* A.string " bits physical, "
parseVirtualAddress :: A.Parser Word32
parseVirtualAddress = A.decimal <* A.string " bits virtual"
parseBool :: A.Parser Bool
parseBool = (A.string "yes" *> pure True)
<|> (A.string "no" *> pure False)
parseWords :: A.Parser [B.ByteString]
parseWords = A.sepBy (A.takeWhile1 (/= ' ')) (A.char ' ')
parseMaybe :: A.Parser a -> B.ByteString -> Maybe a
parseMaybe = (check .) . A.parseOnly
where check (Left _) = Nothing
check (Right x) = Just x
keepTrying :: [B.ByteString] -> A.Parser a -> Maybe a
keepTrying bs p = asum (map (parseMaybe p) bs)
splitCPULines :: B.ByteString -> [[B.ByteString]]
splitCPULines = splitCPUs . BC.lines
where splitCPUs [] = []
splitCPUs ls = let (cs, lss) = break (== "") ls
in cs : case lss of [] -> []
(_:ls') -> splitCPUs ls'
tryCPU :: [B.ByteString] -> Maybe CPU
tryCPU bs = do
proc <- keepTrying bs parseProcessor
vend <- pure (keepTrying bs parseVendor)
modl <- pure (keepTrying bs parseModel)
modn <- pure (keepTrying bs parseModelName)
rev <- pure (keepTrying bs parseRevision)
mcode <- pure (keepTrying bs parseMicrocode)
frq <- keepTrying bs parseFreq
cch <- pure (keepTrying bs parseCache)
pid <- keepTrying bs parsePhysicalID
sib <- keepTrying bs parseSiblings
cid <- keepTrying bs parseCoreID
aid <- pure (keepTrying bs parseApicID)
flpu <- pure (keepTrying bs parseFpu)
flpex <- pure (keepTrying bs parseFpuExcept)
flg <- pure (keepTrying bs parseFlags)
bgm <- keepTrying bs parseBogoMIPS
ca <- pure (keepTrying bs parseCacheAlignment)
(pa, va) <- pure $ case keepTrying bs parseAddresses
of Nothing -> (Nothing, Nothing)
Just (p, v) -> (Just p, Just v)
pure $ CPU proc
vend
modl
modn
rev
mcode
frq
cch
pid
sib
cid
aid
flpu
flpex
flg
bgm
ca
pa
va
tryGetCPUs :: IO (Maybe [CPU])
tryGetCPUs = (mapM tryCPU . splitCPULines)
<$> B.readFile "/proc/cpuinfo"
getCPUs :: IO [CPU]
getCPUs = fromMaybe (error e) <$> tryGetCPUs
where e = unlines [ "Couldn't parse your /proc/cpuinfo contents."
, "Please file a bug including your /proc/cpuinfo here:"
, "https://github.com/traviswhitaker/cpuinfo/issues"
]
physicalProcessors :: [CPU] -> Int
physicalProcessors = length . nub . map physicalID
physicalCores :: [CPU] -> Int
physicalCores = length . nub . map (physicalID &&& coreID)
logicalCores :: [CPU] -> Int
logicalCores = length
hyperthreadingFactor :: [CPU] -> Rational
hyperthreadingFactor cpus = fromIntegral (logicalCores cpus)
/ fromIntegral (physicalCores cpus)
hyperthreadingInUse :: [CPU] -> Bool
hyperthreadingInUse = (/= 1) . hyperthreadingFactor