ghc-bench-0.3.0: src/SystemInfo.hs
module SystemInfo (
collect
, SystemInfo(..)
, Product(..)
, Board(..)
, Cpu(..)
) where
import Imports hiding (product)
import Data.List (nub)
import Control.Exception
import Data.Text qualified as T
import Data.Text.IO.Utf8 qualified as Utf8
import Command (eval, uname, lscpu, free, awk)
data SystemInfo = SystemInfo {
os :: Text
, arch :: Text
, vendor :: Text
, product :: Product
, board :: Board
, cpu :: Cpu
, ram :: Int
} deriving (Eq, Show, Generic)
collect :: IO SystemInfo
collect = do
os <- eval ". /etc/os-release && echo $NAME || uname" <&> strip
arch <- uname ["--machine"]
vendor <- fromFile "/sys/class/dmi/id/sys_vendor"
product <- getProductInfo
board <- getBoardInfo
cpu <- getCpuInfo
ram <- free ["-b"] >>= awk "/Mem:/ {print $2}" <&> toGb . read . strip
return SystemInfo {..}
where
toGb :: Int -> Int
toGb bytes = case ceiling @Double $ fromIntegral bytes / 1024 / 1024 / 1024 of
31 -> 32 -- adjust for reserved ram that is not visible to the os
n -> n
data Product = Product {
category :: Text
, chassis_type :: Text
, family :: Text
, name :: Text
, version :: Text
} deriving (Eq, Show, Generic)
getProductInfo :: IO Product
getProductInfo = do
chassis_type <- fromFile "/sys/class/dmi/id/chassis_type"
family <- fromFile "/sys/class/dmi/id/product_family"
name <- fromFile "/sys/class/dmi/id/product_name"
version <- fromFile "/sys/class/dmi/id/product_version"
return Product {
category = interpretChassisType chassis_type
, ..
}
interpretChassisType :: Text -> Text
interpretChassisType = \ case
"3" -> "desktop"
"4" -> "desktop"
"8" -> "laptop"
"9" -> "laptop"
"10" -> "laptop"
"14" -> "laptop"
_ -> "unknown"
data Board = Board {
vendor :: Text
, name :: Text
} deriving (Eq, Show, Generic)
getBoardInfo :: IO Board
getBoardInfo = do
vendor <- fromFile "/sys/class/dmi/id/board_vendor"
name <- fromFile "/sys/class/dmi/id/board_name"
return Board {..}
data Cpu = Cpu {
name :: Text
, cores :: Int
, threads :: Int
, vendor :: Maybe Text
, family :: Maybe Text
, model :: Maybe Text
, stepping :: Maybe Text
} deriving (Eq, Ord, Show, Generic)
getCpuInfo :: IO Cpu
getCpuInfo = do
threads <- lscpu ["-p=SOCKET,CORE,CPU,MODELNAME"] <&> parse
let
cpus = nub [(socket, name) | (socket, _, _, name) <- threads]
cores = nub [(socket, core) | (socket, core, _, _) <- threads]
fields <- lscpu [] <&> parseFields
return Cpu {
name = T.intercalate " / " $ map snd cpus
, cores = length cores
, threads = length threads
, vendor = lookup "Vendor ID" fields
, family = lookup "CPU family" fields
, model = lookup "Model" fields
, stepping = lookup "Stepping" fields
}
where
parse :: Text -> [(Text, Text, Text, Text)]
parse = mapMaybe parseLine . removeComments . lines
parseLine :: Text -> Maybe (Text, Text, Text, Text)
parseLine = T.splitOn "," >>> \ case
socket : core : thread : name -> Just (socket, core, thread, T.intercalate "," name)
_ -> Nothing
removeComments :: [Text] -> [Text]
removeComments = filter (not . T.isPrefixOf "#")
parseFields :: Text -> [(Text, Text)]
parseFields = map parseField . lines
parseField :: Text -> (Text, Text)
parseField = fmap (strip . T.drop 1) . T.breakOn ": "
fromFile :: FilePath -> IO Text
fromFile p = try @IOException (Utf8.readFile p) <&> \ case
Left _ -> "unknown"
Right c -> strip c