packages feed

cgroup-rts-threads-0.2.0.0: src/System/CGroup/V2/CPU.hs

{-# LANGUAGE TemplateHaskell #-}

-- | Types and operations for CPU-related data within a cgroup.
module System.CGroup.V2.CPU (
  getProcessEffectiveCPUQuota,
  getEffectiveCPUQuota,
  getCPUQuota,
  CPUQuota (..),
) where

import Data.Ratio
import Path
import System.CGroup.Types (CPUQuota (..))
import System.CGroup.V2.CGroup
import System.Directory (doesFileExist)

-- | Get the current process' effective CPU quota
--
-- See 'getEffectiveCPUQuota'
getProcessEffectiveCPUQuota :: IO CPUQuota
getProcessEffectiveCPUQuota = getEffectiveCPUQuota =<< resolveCGroup

-- | Compute the "effective CPU quota" fr a cgroup, which may be smaller than
-- the given cgroup's individual quota.
--
-- When a parent (or grandparent, etc) of this cgroup has a lower cpu quota,
-- the lower quota is returned instead.
getEffectiveCPUQuota :: CGroup -> IO CPUQuota
getEffectiveCPUQuota cgroup = do
  quotas <- cpuQuotasUntilRoot cgroup
  pure (foldr min NoQuota quotas)

-- | Read this specific cgroup's "cpu.max" file into a CPUQuota.
--
-- For example:
--
-- @
-- | cpu.max        | quota            |
-- | -------------- | ---------------- |
-- |  100000 100000 | CPUQuota (1 % 1) |
-- |  200000 100000 | CPUQuota (2 % 1) |
-- |   50000 100000 | CPUQuota (1 % 2) |
-- |     max 100000 | NoQuota          |
-- @
--
--
-- Returns NoQuota for the root cgroup, or when the cpu controller is not enabled in this cgroup.
--
-- __Most often, you'll want to use 'getEffectiveCPUQuota' instead.__
getCPUQuota :: CGroup -> IO CPUQuota
getCPUQuota cgroup = do
  let path = cgroupRoot cgroup </> cgroupLeaf cgroup </> cpuMaxPath
  exists <- doesFileExist (toFilePath path)
  if not exists
    then pure NoQuota
    else do
      content <- readFile (toFilePath path)
      case words content of
        ["max", _] -> pure NoQuota
        [numText, denText] -> do
          num <- readIO numText
          den <- readIO denText
          pure (CPUQuota (num % den))
        _ -> fail "Couldn't parse cpu.max"

-- | Get the parent cgroup. Returns Nothing when the provided cgroup is the root
-- cgroup.
getParentCGroup :: CGroup -> Maybe CGroup
getParentCGroup cgroup =
  if cgroupLeaf cgroup == parent (cgroupLeaf cgroup)
    then Nothing
    else Just cgroup{cgroupLeaf = parent (cgroupLeaf cgroup)}

-- | Return all CPU quotas from all parent cgroups
cpuQuotasUntilRoot :: CGroup -> IO [CPUQuota]
cpuQuotasUntilRoot = traverse getCPUQuota . cgroupsUntilRoot

-- | Return the list of cgroups up to but excluding the root cgroup
cgroupsUntilRoot :: CGroup -> [CGroup]
cgroupsUntilRoot = iterateMaybe getParentCGroup

-- | like 'iterate', but terminated when @Nothing@ is returned by the provided
-- function
iterateMaybe :: (a -> Maybe a) -> a -> [a]
iterateMaybe f x = x : maybe [] (iterateMaybe f) (f x)

cpuMaxPath :: Path Rel File
cpuMaxPath = $(mkRelFile "cpu.max")