cpuinfo-0.1.0.0: src/System/CPU.hs
{-|
Module : System.CPU
Description : Haskell Library for Checking CPU Information
Copyright : Travis Whitaker 2016
License : MIT
Maintainer : pi.boy.travis@gmail.com
Stability : Provisional
Portability : Linux >=2.6
This module provides information about the processors available on a system.
Modern hardware provides not only multiple physical processors and physical
cores, but logical cores which may not have dedicated execution resources.
Intel's Hyper-Threading is an example of such a technology, capable of providing
two logical cores for every physical core present on a supported physical
processor.
These additional logical cores increase the performance of some, but not all
workloads. Indeed, some parallel workloads may suffer a performance decrease if
all logical cores presented by the operating system do not have dedicated
physical resources. This is because technologies providing supernumerary logical
cores typically work by scheduling multiple threads in a shared pool of
execution resources, e.g. ALUs and FPUs. If threads sharing a pool of execution
resources are doing the same sort of work there will be scheduling contention
for a single type of execution resource on the physical core.
It is common for threaded Haskell programs to be run with @+RTS -N@, causing the
RTS to simply multiplex Haskell threads or sparks over the number of logical
cores available. However, if each logical core does not have dedicated physical
resources and the thread/spark workloads are similar, then this might be slower
than multiplexing over fewer cores.
This package allows a program to use information about the physical and logical
features of the available processors as a heuristic for selecting the number of
worker OS threads to use (e.g. via 'setNumCapabilities'). Some workloads may
benefit from, for example, using half the number of logical cores available if
there are in fact two logical cores for each physical core. This is typically
true of numerical workloads, but as always benchmarking should be employed to
evaluate the impact of different heuristics.
In its current state this module can only collect information from Linux systems
with a kernel from the 2.6 branch or later by reading @\/proc\/cpuinfo@. If this
module is unable to provide information on your system please file a bug
including your @\/proc\/cpuinfo@. Help providing Windows support would be
greatly appreciated!
-}
{-# LANGUAGE DeriveAnyClass
, DeriveDataTypeable
, DeriveGeneric
, OverloadedStrings
#-}
module System.CPU (
CPU(..)
-- * Retrieving CPU Information
, getCPUs
, tryGetCPUs
-- * Physical Features
, 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
-- | Representation of a logical processor and its features.
data CPU = CPU {
-- | Logical Processor Index
processorID :: !Word32
-- | CPU Vendor
, vendor :: !(Maybe B.ByteString)
-- | CPU Model Number
, model :: !(Maybe Word32)
-- | CPU Model Name
, modelName :: !(Maybe B.ByteString)
-- | CPU Model Revision
, revision :: !(Maybe Word32)
-- | CPU Microcode Revision
, microcode :: !(Maybe Word32)
-- | Processor Frequency
, freq :: !Double
-- | CPU Cache Size. (TODO figure out how to get the cache topology)
, cache :: !(Maybe Word32)
-- | Physical Processor Index
, physicalID :: !Word32
-- | Number of Physical Cores on this Physical Processor.
, siblings :: !Word32
-- | Physical Core Index
, coreID :: !Word32
-- | CPU APIC Index
, apicID :: !(Maybe Word32)
-- | Whether or not the Physical Core provides a floating point unit.
, fpu :: !(Maybe Bool)
-- | Whether or not the Physical Core provides a floating point exception
-- unit.
, fpuExcept :: !(Maybe Bool)
-- | Vendor-specific CPU flags.
, flags :: !(Maybe [B.ByteString])
-- | MIPS approximation computed by the Linux kernel on boot.
, bogoMIPS :: !Double
-- | Cache line size in bytes.
, cacheAlignment :: !(Maybe Word32)
-- | Physical address width.
, physicalAddress :: !(Maybe Word32)
-- | Virtual address width.
, 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
-- | Read @\/proc\/cpuinfo@ and try to parse the output. If this function
-- returns 'Nothing' on your system, please file a bug report with your
-- @\/proc\/cpuinfo@ contents and CPU specifications.
tryGetCPUs :: IO (Maybe [CPU])
tryGetCPUs = (mapM tryCPU . splitCPULines)
<$> B.readFile "/proc/cpuinfo"
-- | Read @\/proc\/cpuinfo@ and try to parse the output. If this function throws
-- an error on your system, please file a bug report with your
-- @\/proc\/cpuinfo@ contents and CPU specifications.
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"
]
-- | Counts the number of physical processors in the system. A physical
-- processor corresponds to a single CPU unit in a single socket, i.e. unless
-- you have a multi-socket motherboard, this number will be one.
physicalProcessors :: [CPU] -> Int
physicalProcessors = length . nub . map physicalID
-- | Counts the number of physical cores in the system. A physical core is an
-- independent processing unit that reads and executes instructions on its
-- own, but potentially shares its die (and other resources) with other cores.
physicalCores :: [CPU] -> Int
physicalCores = length . nub . map (physicalID &&& coreID)
-- | Counts the number of logical cores in the system. A logical core is a
-- virtual processing unit exposed to the operating system, that may or may
-- not directly correspond with an independent physical processing unit, e.g.
-- a hyperthread appears as an independent processing unit to the operating
-- system, but has no physically dedicated execution resources.
logicalCores :: [CPU] -> Int
logicalCores = length
-- | The hyperthreading factor is the number of logical cores divided by the
-- number of physical cores. This quantity indicates the degree to which
-- physical execution resources are shared among logical processors, and may
-- be used to tune parallel applications.
hyperthreadingFactor :: [CPU] -> Rational
hyperthreadingFactor cpus = fromIntegral (logicalCores cpus)
/ fromIntegral (physicalCores cpus)
-- | If hyperthreading is in use, the 'hyperthreadingFactor' will be greater
-- than 1.
hyperthreadingInUse :: [CPU] -> Bool
hyperthreadingInUse = (/= 1) . hyperthreadingFactor