module Ecluse.Runtime (
applyRuntimePosture,
RtsPosture (..),
CgroupLimits (..),
Provenance (..),
RuntimePlan (..),
resolveRuntimePlan,
deriveMaxHeapBytes,
requiredRtsFlags,
renderRuntimePosture,
parseCpuMax,
parseMemoryMax,
parseCgroupSelfPath,
ancestorPaths,
) where
import Data.Text qualified as T
import GHC.Conc (getNumCapabilities, getNumProcessors, setNumCapabilities)
import GHC.RTS.Flags (GCFlags (maxHeapSize, minAllocAreaSize, nurseryChunkSize), getGCFlags)
import System.Environment (getEnvironment, getExecutablePath)
import System.IO.Error (isDoesNotExistError)
import System.Posix.Process (executeFile)
import UnliftIO (tryIO, tryJust)
data RtsPosture = RtsPosture
{ RtsPosture -> Int
rpCapabilities :: Int
, RtsPosture -> Int
rpProcessors :: Int
, RtsPosture -> Int
rpAllocAreaBytes :: Int
, RtsPosture -> Maybe Int
rpNurseryChunkBytes :: Maybe Int
, RtsPosture -> Maybe Int
rpMaxHeapBytes :: Maybe Int
}
deriving stock (RtsPosture -> RtsPosture -> Bool
(RtsPosture -> RtsPosture -> Bool)
-> (RtsPosture -> RtsPosture -> Bool) -> Eq RtsPosture
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: RtsPosture -> RtsPosture -> Bool
== :: RtsPosture -> RtsPosture -> Bool
$c/= :: RtsPosture -> RtsPosture -> Bool
/= :: RtsPosture -> RtsPosture -> Bool
Eq, Int -> RtsPosture -> ShowS
[RtsPosture] -> ShowS
RtsPosture -> String
(Int -> RtsPosture -> ShowS)
-> (RtsPosture -> String)
-> ([RtsPosture] -> ShowS)
-> Show RtsPosture
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> RtsPosture -> ShowS
showsPrec :: Int -> RtsPosture -> ShowS
$cshow :: RtsPosture -> String
show :: RtsPosture -> String
$cshowList :: [RtsPosture] -> ShowS
showList :: [RtsPosture] -> ShowS
Show)
data CgroupLimits = CgroupLimits
{ CgroupLimits -> Maybe Double
cgCpuCores :: Maybe Double
, CgroupLimits -> Maybe Int
cgMemoryMaxBytes :: Maybe Int
}
deriving stock (CgroupLimits -> CgroupLimits -> Bool
(CgroupLimits -> CgroupLimits -> Bool)
-> (CgroupLimits -> CgroupLimits -> Bool) -> Eq CgroupLimits
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: CgroupLimits -> CgroupLimits -> Bool
== :: CgroupLimits -> CgroupLimits -> Bool
$c/= :: CgroupLimits -> CgroupLimits -> Bool
/= :: CgroupLimits -> CgroupLimits -> Bool
Eq, Int -> CgroupLimits -> ShowS
[CgroupLimits] -> ShowS
CgroupLimits -> String
(Int -> CgroupLimits -> ShowS)
-> (CgroupLimits -> String)
-> ([CgroupLimits] -> ShowS)
-> Show CgroupLimits
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> CgroupLimits -> ShowS
showsPrec :: Int -> CgroupLimits -> ShowS
$cshow :: CgroupLimits -> String
show :: CgroupLimits -> String
$cshowList :: [CgroupLimits] -> ShowS
showList :: [CgroupLimits] -> ShowS
Show)
data Provenance
=
FromConfig
|
FromCgroup
|
FromRts
deriving stock (Provenance -> Provenance -> Bool
(Provenance -> Provenance -> Bool)
-> (Provenance -> Provenance -> Bool) -> Eq Provenance
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Provenance -> Provenance -> Bool
== :: Provenance -> Provenance -> Bool
$c/= :: Provenance -> Provenance -> Bool
/= :: Provenance -> Provenance -> Bool
Eq, Int -> Provenance -> ShowS
[Provenance] -> ShowS
Provenance -> String
(Int -> Provenance -> ShowS)
-> (Provenance -> String)
-> ([Provenance] -> ShowS)
-> Show Provenance
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Provenance -> ShowS
showsPrec :: Int -> Provenance -> ShowS
$cshow :: Provenance -> String
show :: Provenance -> String
$cshowList :: [Provenance] -> ShowS
showList :: [Provenance] -> ShowS
Show)
data RuntimePlan = RuntimePlan
{ RuntimePlan -> (Int, Provenance)
planCapabilities :: (Int, Provenance)
, RuntimePlan -> (Maybe Int, Provenance)
planMaxHeapBytes :: (Maybe Int, Provenance)
}
deriving stock (RuntimePlan -> RuntimePlan -> Bool
(RuntimePlan -> RuntimePlan -> Bool)
-> (RuntimePlan -> RuntimePlan -> Bool) -> Eq RuntimePlan
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: RuntimePlan -> RuntimePlan -> Bool
== :: RuntimePlan -> RuntimePlan -> Bool
$c/= :: RuntimePlan -> RuntimePlan -> Bool
/= :: RuntimePlan -> RuntimePlan -> Bool
Eq, Int -> RuntimePlan -> ShowS
[RuntimePlan] -> ShowS
RuntimePlan -> String
(Int -> RuntimePlan -> ShowS)
-> (RuntimePlan -> String)
-> ([RuntimePlan] -> ShowS)
-> Show RuntimePlan
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> RuntimePlan -> ShowS
showsPrec :: Int -> RuntimePlan -> ShowS
$cshow :: RuntimePlan -> String
show :: RuntimePlan -> String
$cshowList :: [RuntimePlan] -> ShowS
showList :: [RuntimePlan] -> ShowS
Show)
resolveRuntimePlan :: Maybe Int -> Maybe Int -> CgroupLimits -> RtsPosture -> RuntimePlan
resolveRuntimePlan :: Maybe Int -> Maybe Int -> CgroupLimits -> RtsPosture -> RuntimePlan
resolveRuntimePlan Maybe Int
cfgCores Maybe Int
cfgMaxHeap CgroupLimits
cgroup RtsPosture
rts =
RuntimePlan
{ planCapabilities :: (Int, Provenance)
planCapabilities = (Int, Provenance)
capabilities
, planMaxHeapBytes :: (Maybe Int, Provenance)
planMaxHeapBytes = (Maybe Int, Provenance)
maxHeap
}
where
capabilities :: (Int, Provenance)
capabilities = case (Maybe Int
cfgCores, CgroupLimits -> Maybe Double
cgCpuCores CgroupLimits
cgroup) of
(Just Int
n, Maybe Double
_) -> (Int -> Int -> Int
forall a. Ord a => a -> a -> a
max Int
1 Int
n, Provenance
FromConfig)
(Maybe Int
Nothing, Just Double
quota) -> (Int -> Int
clamp (Double -> Int
forall b. Integral b => Double -> b
forall a b. (RealFrac a, Integral b) => a -> b
floor Double
quota), Provenance
FromCgroup)
(Maybe Int
Nothing, Maybe Double
Nothing) -> (RtsPosture -> Int
rpCapabilities RtsPosture
rts, Provenance
FromRts)
clamp :: Int -> Int
clamp Int
n = Int -> Int -> Int
forall a. Ord a => a -> a -> a
max Int
1 (Int -> Int -> Int
forall a. Ord a => a -> a -> a
min (RtsPosture -> Int
rpProcessors RtsPosture
rts) Int
n)
maxHeap :: (Maybe Int, Provenance)
maxHeap = case (Maybe Int
cfgMaxHeap, CgroupLimits -> Maybe Int
cgMemoryMaxBytes CgroupLimits
cgroup) of
(Just Int
bytes, Maybe Int
_) -> (Int -> Maybe Int
forall a. a -> Maybe a
Just (Int -> Int
alignToBlock Int
bytes), Provenance
FromConfig)
(Maybe Int
Nothing, Just Int
memMax) ->
(Int -> Maybe Int
forall a. a -> Maybe a
Just (Int -> Int -> Int -> Int
deriveMaxHeapBytes Int
memMax ((Int, Provenance) -> Int
forall a b. (a, b) -> a
fst (Int, Provenance)
capabilities) (RtsPosture -> Int
rpAllocAreaBytes RtsPosture
rts)), Provenance
FromCgroup)
(Maybe Int
Nothing, Maybe Int
Nothing) -> (RtsPosture -> Maybe Int
rpMaxHeapBytes RtsPosture
rts, Provenance
FromRts)
deriveMaxHeapBytes :: Int -> Int -> Int -> Int
deriveMaxHeapBytes :: Int -> Int -> Int -> Int
deriveMaxHeapBytes Int
memMax Int
capabilities Int
allocAreaBytes =
Int -> Int
alignToBlock (Int -> Int -> Int
forall a. Ord a => a -> a -> a
max (Int
memMax Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
nursery Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
slack) (Int
memMax Int -> Int -> Int
forall a. Integral a => a -> a -> a
`div` Int
2))
where
nursery :: Int
nursery = Int
capabilities Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
allocAreaBytes
slack :: Int
slack = Int
memMax Int -> Int -> Int
forall a. Integral a => a -> a -> a
`div` Int
10
alignToBlock :: Int -> Int
alignToBlock :: Int -> Int
alignToBlock Int
bytes = Int -> Int -> Int
forall a. Ord a => a -> a -> a
max Int
rtsBlockBytes (Int
bytes Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
bytes Int -> Int -> Int
forall a. Integral a => a -> a -> a
`mod` Int
rtsBlockBytes)
requiredRtsFlags :: RtsPosture -> RuntimePlan -> [Text]
requiredRtsFlags :: RtsPosture -> RuntimePlan -> [Text]
requiredRtsFlags RtsPosture
rts RuntimePlan
plan =
[Maybe Text] -> [Text]
forall a. [Maybe a] -> [a]
catMaybes [Maybe Text
capsFlag, Maybe Text
heapFlag]
where
capsFlag :: Maybe Text
capsFlag = case RuntimePlan -> (Int, Provenance)
planCapabilities RuntimePlan
plan of
(Int
_, Provenance
FromRts) -> Maybe Text
forall a. Maybe a
Nothing
(Int
n, Provenance
_)
| Int
n Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== RtsPosture -> Int
rpCapabilities RtsPosture
rts -> Maybe Text
forall a. Maybe a
Nothing
| Bool
otherwise -> Text -> Maybe Text
forall a. a -> Maybe a
Just (Text
"-N" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Int -> Text
forall b a. (Show a, IsString b) => a -> b
show Int
n)
heapFlag :: Maybe Text
heapFlag = case RuntimePlan -> (Maybe Int, Provenance)
planMaxHeapBytes RuntimePlan
plan of
(Maybe Int
_, Provenance
FromRts) -> Maybe Text
forall a. Maybe a
Nothing
(Maybe Int
Nothing, Provenance
_) -> Maybe Text
forall a. Maybe a
Nothing
(Just Int
bytes, Provenance
_)
| Int -> Maybe Int
forall a. a -> Maybe a
Just Int
bytes Maybe Int -> Maybe Int -> Bool
forall a. Eq a => a -> a -> Bool
== RtsPosture -> Maybe Int
rpMaxHeapBytes RtsPosture
rts -> Maybe Text
forall a. Maybe a
Nothing
| Bool
otherwise -> Text -> Maybe Text
forall a. a -> Maybe a
Just (Text
"-M" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Int -> Text
forall b a. (Show a, IsString b) => a -> b
show Int
bytes)
renderRuntimePosture :: RuntimePlan -> RtsPosture -> [Text]
renderRuntimePosture :: RuntimePlan -> RtsPosture -> [Text]
renderRuntimePosture RuntimePlan
plan RtsPosture
rts =
[ Text
"runtime: capabilities " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Int -> Text
forall b a. (Show a, IsString b) => a -> b
show ((Int, Provenance) -> Int
forall a b. (a, b) -> a
fst (RuntimePlan -> (Int, Provenance)
planCapabilities RuntimePlan
plan)) Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Provenance -> Text
renderProvenance ((Int, Provenance) -> Provenance
forall a b. (a, b) -> b
snd (RuntimePlan -> (Int, Provenance)
planCapabilities RuntimePlan
plan))
, case RuntimePlan -> (Maybe Int, Provenance)
planMaxHeapBytes RuntimePlan
plan of
(Just Int
bytes, Provenance
prov) -> Text
"runtime: max heap " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Int -> Text
renderMiB Int
bytes Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Provenance -> Text
renderProvenance Provenance
prov
(Maybe Int
Nothing, Provenance
_) -> Text
"runtime: max heap unbounded (the container memory limit is the only backstop; set maxHeapBytes or -M for a graceful ceiling)"
, Text
"runtime: allocation area "
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Int -> Text
renderMiB (RtsPosture -> Int
rpAllocAreaBytes RtsPosture
rts)
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"/capability"
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> (Int -> Text) -> Maybe Int -> Text
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Text
"" (\Int
c -> Text
", nursery chunks " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Int -> Text
renderMiB Int
c) (RtsPosture -> Maybe Int
rpNurseryChunkBytes RtsPosture
rts)
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" (RTS; tune with GHCRTS, see USAGE.md)"
]
renderProvenance :: Provenance -> Text
renderProvenance :: Provenance -> Text
renderProvenance = \case
Provenance
FromConfig -> Text
" (from config)"
Provenance
FromCgroup -> Text
" (derived from the cgroup limit)"
Provenance
FromRts -> Text
" (as the RTS resolved it)"
renderMiB :: Int -> Text
renderMiB :: Int -> Text
renderMiB Int
bytes =
let mib :: Double
mib = Int -> Double
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
bytes Double -> Double -> Double
forall a. Fractional a => a -> a -> a
/ (Double
1024 Double -> Double -> Double
forall a. Num a => a -> a -> a
* Double
1024) :: Double
in if Int -> Double
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Double -> Int
forall b. Integral b => Double -> b
forall a b. (RealFrac a, Integral b) => a -> b
round Double
mib :: Int) Double -> Double -> Bool
forall a. Eq a => a -> a -> Bool
== Double
mib
then Int -> Text
forall b a. (Show a, IsString b) => a -> b
show (Double -> Int
forall b. Integral b => Double -> b
forall a b. (RealFrac a, Integral b) => a -> b
round Double
mib :: Int) Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" MiB"
else String -> Text
forall a. ToText a => a -> Text
toText (Double -> String
showRounded Double
mib) Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" MiB"
showRounded :: Double -> String
showRounded :: Double -> String
showRounded Double
x = Double -> String
forall b a. (Show a, IsString b) => a -> b
show (Int -> Double
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Double -> Int
forall b. Integral b => Double -> b
forall a b. (RealFrac a, Integral b) => a -> b
round (Double
x Double -> Double -> Double
forall a. Num a => a -> a -> a
* Double
10) :: Int) Double -> Double -> Double
forall a. Fractional a => a -> a -> a
/ Double
10 :: Double)
parseCpuMax :: Text -> Maybe Double
parseCpuMax :: Text -> Maybe Double
parseCpuMax Text
body = case Text -> [Text]
T.words (Text -> Text
T.strip Text
body) of
[Text
quota, Text
period] -> do
q <- String -> Maybe Double
forall a. Read a => String -> Maybe a
readMaybe (Text -> String
forall a. ToString a => a -> String
toString Text
quota) :: Maybe Double
p <- readMaybe (toString period) :: Maybe Double
guard (q > 0 && p > 0)
pure (q / p)
[Text]
_ -> Maybe Double
forall a. Maybe a
Nothing
parseMemoryMax :: Text -> Maybe Int
parseMemoryMax :: Text -> Maybe Int
parseMemoryMax Text
body = do
n <- String -> Maybe Int
forall a. Read a => String -> Maybe a
readMaybe (Text -> String
forall a. ToString a => a -> String
toString (Text -> Text
T.strip Text
body)) :: Maybe Int
guard (n > 0)
pure n
applyRuntimePosture :: (Text -> IO ()) -> (Text -> IO ()) -> Maybe Int -> Maybe Int -> IO ()
applyRuntimePosture :: (Text -> IO ())
-> (Text -> IO ()) -> Maybe Int -> Maybe Int -> IO ()
applyRuntimePosture Text -> IO ()
logInfo Text -> IO ()
logWarning Maybe Int
cfgCores Maybe Int
cfgMaxHeap = do
rts <- IO RtsPosture
currentRtsPosture
cgroup <- readCgroupLimits
let plan = Maybe Int -> Maybe Int -> CgroupLimits -> RtsPosture -> RuntimePlan
resolveRuntimePlan Maybe Int
cfgCores Maybe Int
cfgMaxHeap CgroupLimits
cgroup RtsPosture
rts
flags = RtsPosture -> RuntimePlan -> [Text]
requiredRtsFlags RtsPosture
rts RuntimePlan
plan
alreadyApplied <- isJust <$> lookupEnv reexecMarker
case flags of
[] -> RuntimePlan -> RtsPosture -> IO ()
logPosture RuntimePlan
plan RtsPosture
rts
[Text]
_ | Bool
alreadyApplied -> do
(Text -> IO ()) -> [Text] -> IO ()
warnStillDivergent Text -> IO ()
logWarning [Text]
flags
RuntimePlan -> RtsPosture -> IO ()
logPosture RuntimePlan
plan RtsPosture
rts
[Text
capsOnly]
| Text
"-N" Text -> Text -> Bool
`T.isPrefixOf` Text
capsOnly -> do
Int -> IO ()
setNumCapabilities ((Int, Provenance) -> Int
forall a b. (a, b) -> a
fst (RuntimePlan -> (Int, Provenance)
planCapabilities RuntimePlan
plan))
RuntimePlan -> RtsPosture -> IO ()
logPosture RuntimePlan
plan RtsPosture
rts{rpCapabilities = fst (planCapabilities plan)}
[Text]
_ -> do
(Text -> IO ()) -> (Text -> IO ()) -> [Text] -> IO ()
reexecOrWarn Text -> IO ()
logInfo Text -> IO ()
logWarning [Text]
flags
RuntimePlan -> RtsPosture -> IO ()
logPosture RuntimePlan
plan RtsPosture
rts
where
logPosture :: RuntimePlan -> RtsPosture -> IO ()
logPosture RuntimePlan
plan RtsPosture
rts = (Text -> IO ()) -> [Text] -> IO ()
forall (t :: * -> *) (f :: * -> *) a b.
(Foldable t, Applicative f) =>
(a -> f b) -> t a -> f ()
traverse_ Text -> IO ()
logInfo (RuntimePlan -> RtsPosture -> [Text]
renderRuntimePosture RuntimePlan
plan RtsPosture
rts)
warnStillDivergent :: (Text -> IO ()) -> [Text] -> IO ()
warnStillDivergent :: (Text -> IO ()) -> [Text] -> IO ()
warnStillDivergent Text -> IO ()
logWarning [Text]
flags =
Text -> IO ()
logWarning
( Text
"runtime: the resolved plan still requires "
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> [Text] -> Text
T.intercalate Text
" " [Text]
flags
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" after re-launch; an operator GHCRTS may be overriding the configuration, or the RTS rejected a flag. Continuing with the live posture."
)
reexecOrWarn :: (Text -> IO ()) -> (Text -> IO ()) -> [Text] -> IO ()
reexecOrWarn :: (Text -> IO ()) -> (Text -> IO ()) -> [Text] -> IO ()
reexecOrWarn Text -> IO ()
logInfo Text -> IO ()
logWarning [Text]
flags =
IO () -> IO (Either IOException ())
forall (m :: * -> *) a.
MonadUnliftIO m =>
m a -> m (Either IOException a)
tryIO ((Text -> IO ()) -> [Text] -> IO ()
reexecWith Text -> IO ()
logInfo [Text]
flags) IO (Either IOException ())
-> (Either IOException () -> IO ()) -> IO ()
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
Left IOException
err ->
Text -> IO ()
logWarning
( Text
"runtime: re-launching to apply "
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> [Text] -> Text
T.intercalate Text
" " [Text]
flags
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" failed ("
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> IOException -> Text
forall b a. (Show a, IsString b) => a -> b
show IOException
err
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"); continuing with the live posture, unenforced."
)
Right () -> IO ()
forall (f :: * -> *). Applicative f => f ()
pass
currentRtsPosture :: IO RtsPosture
currentRtsPosture :: IO RtsPosture
currentRtsPosture = do
capabilities <- IO Int
getNumCapabilities
processors <- getNumProcessors
gc <- getGCFlags
let blocks a
n = a -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral a
n Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
rtsBlockBytes
pure
RtsPosture
{ rpCapabilities = capabilities
, rpProcessors = processors
, rpAllocAreaBytes = blocks (minAllocAreaSize gc)
, rpNurseryChunkBytes = nonZero (blocks (nurseryChunkSize gc))
, rpMaxHeapBytes = nonZero (blocks (maxHeapSize gc))
}
where
nonZero :: a -> Maybe a
nonZero a
n = if a
n a -> a -> Bool
forall a. Ord a => a -> a -> Bool
<= a
0 then Maybe a
forall a. Maybe a
Nothing else a -> Maybe a
forall a. a -> Maybe a
Just a
n
rtsBlockBytes :: Int
rtsBlockBytes :: Int
rtsBlockBytes = Int
4096
readCgroupLimits :: IO CgroupLimits
readCgroupLimits :: IO CgroupLimits
readCgroupLimits = do
selfCgroup <- String -> IO (Maybe Text)
readIfExists String
"/proc/self/cgroup"
let relative = Text -> Maybe Text -> Text
forall a. a -> Maybe a -> a
fromMaybe Text
"/" (Maybe Text
selfCgroup Maybe Text -> (Text -> Maybe Text) -> Maybe Text
forall a b. Maybe a -> (a -> Maybe b) -> Maybe b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Text -> Maybe Text
parseCgroupSelfPath)
dirs = [String
cgroupRoot String -> ShowS
forall a. Semigroup a => a -> a -> a
<> Text -> String
forall a. ToString a => a -> String
toString Text
suffix | Text
suffix <- Text -> [Text]
ancestorPaths Text
relative]
cpus <- traverse (limitAt parseCpuMax "/cpu.max") dirs
memories <- traverse (limitAt parseMemoryMax "/memory.max") dirs
pure
CgroupLimits
{ cgCpuCores = tightest cpus
, cgMemoryMaxBytes = tightest memories
}
where
cgroupRoot :: String
cgroupRoot = String
"/sys/fs/cgroup"
limitAt :: (Text -> Maybe a) -> String -> FilePath -> IO (Maybe a)
limitAt :: forall a. (Text -> Maybe a) -> String -> String -> IO (Maybe a)
limitAt Text -> Maybe a
parse String
file String
dir = (Maybe Text -> (Text -> Maybe a) -> Maybe a
forall a b. Maybe a -> (a -> Maybe b) -> Maybe b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Text -> Maybe a
parse) (Maybe Text -> Maybe a) -> IO (Maybe Text) -> IO (Maybe a)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> String -> IO (Maybe Text)
readIfExists (String
dir String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
file)
tightest :: (Ord a) => [Maybe a] -> Maybe a
tightest :: forall a. Ord a => [Maybe a] -> Maybe a
tightest [Maybe a]
found = case [Maybe a] -> [a]
forall a. [Maybe a] -> [a]
catMaybes [Maybe a]
found of
[] -> Maybe a
forall a. Maybe a
Nothing
(a
x : [a]
xs) -> a -> Maybe a
forall a. a -> Maybe a
Just ((a -> a -> a) -> a -> [a] -> a
forall b a. (b -> a -> b) -> b -> [a] -> b
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl' a -> a -> a
forall a. Ord a => a -> a -> a
min a
x [a]
xs)
readIfExists :: FilePath -> IO (Maybe Text)
readIfExists :: String -> IO (Maybe Text)
readIfExists String
path =
Either () Text -> Maybe Text
forall l r. Either l r -> Maybe r
rightToMaybe (Either () Text -> Maybe Text)
-> IO (Either () Text) -> IO (Maybe Text)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (IOException -> Maybe ()) -> IO Text -> IO (Either () Text)
forall (m :: * -> *) e b a.
(MonadUnliftIO m, Exception e) =>
(e -> Maybe b) -> m a -> m (Either b a)
tryJust (Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ())
-> (IOException -> Bool) -> IOException -> Maybe ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. IOException -> Bool
isDoesNotExistError) (ByteString -> Text
forall a b. ConvertUtf8 a b => b -> a
decodeUtf8 (ByteString -> Text) -> IO ByteString -> IO Text
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> String -> IO ByteString
forall (m :: * -> *). MonadIO m => String -> m ByteString
readFileBS String
path)
parseCgroupSelfPath :: Text -> Maybe Text
parseCgroupSelfPath :: Text -> Maybe Text
parseCgroupSelfPath Text
body =
[Text] -> Maybe Text
forall a. [a] -> Maybe a
listToMaybe ((Text -> Maybe Text) -> [Text] -> [Text]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (Text -> Text -> Maybe Text
T.stripPrefix Text
"0::") (Text -> [Text]
forall t. IsText t "lines" => t -> [t]
lines (Text -> Text
T.strip Text
body)))
ancestorPaths :: Text -> [Text]
ancestorPaths :: Text -> [Text]
ancestorPaths Text
path = case (Text -> Bool) -> [Text] -> [Text]
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not (Bool -> Bool) -> (Text -> Bool) -> Text -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Bool
T.null) (HasCallStack => Text -> Text -> [Text]
Text -> Text -> [Text]
T.splitOn Text
"/" (Text -> Text
T.strip Text
path)) of
[] -> [Text
""]
[Text]
segments ->
[[Text] -> Text
T.concat [Text
"/" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
seg | Text
seg <- Int -> [Text] -> [Text]
forall a. Int -> [a] -> [a]
take Int
n [Text]
segments] | Int
n <- [[Text] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Text]
segments, [Text] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Text]
segments Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1 .. Int
1]] [Text] -> [Text] -> [Text]
forall a. Semigroup a => a -> a -> a
<> [Text
""]
reexecMarker :: String
reexecMarker :: String
reexecMarker = String
"__ECLUSE_RUNTIME_RTS_APPLIED"
reexecWith :: (Text -> IO ()) -> [Text] -> IO ()
reexecWith :: (Text -> IO ()) -> [Text] -> IO ()
reexecWith Text -> IO ()
logInfo [Text]
flags = do
self <- IO String
getExecutablePath
args <- getArgs
env <- getEnvironment
let prior = (String, String) -> String
forall a b. (a, b) -> b
snd ((String, String) -> String)
-> Maybe (String, String) -> Maybe String
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> ((String, String) -> Bool)
-> [(String, String)] -> Maybe (String, String)
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find ((String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"GHCRTS") (String -> Bool)
-> ((String, String) -> String) -> (String, String) -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (String, String) -> String
forall a b. (a, b) -> a
fst) [(String, String)]
env
appended = Text -> (String -> Text) -> Maybe String -> Text
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Text
newFlags (\String
p -> String -> Text
forall a. ToText a => a -> Text
toText String
p Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
newFlags) Maybe String
prior
env' =
((String
"GHCRTS", Text -> String
forall a. ToString a => a -> String
toString Text
appended) (String, String) -> [(String, String)] -> [(String, String)]
forall a. a -> [a] -> [a]
:)
([(String, String)] -> [(String, String)])
-> ([(String, String)] -> [(String, String)])
-> [(String, String)]
-> [(String, String)]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((String
reexecMarker, String
"1") (String, String) -> [(String, String)] -> [(String, String)]
forall a. a -> [a] -> [a]
:)
([(String, String)] -> [(String, String)])
-> ([(String, String)] -> [(String, String)])
-> [(String, String)]
-> [(String, String)]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((String, String) -> Bool)
-> [(String, String)] -> [(String, String)]
forall a. (a -> Bool) -> [a] -> [a]
filter (\(String
k, String
_) -> String
k String -> String -> Bool
forall a. Eq a => a -> a -> Bool
/= String
"GHCRTS" Bool -> Bool -> Bool
&& String
k String -> String -> Bool
forall a. Eq a => a -> a -> Bool
/= String
reexecMarker)
([(String, String)] -> [(String, String)])
-> [(String, String)] -> [(String, String)]
forall a b. (a -> b) -> a -> b
$ [(String, String)]
env
logInfo ("runtime: re-launching with GHCRTS " <> appended <> " to apply the resolved plan (same process, exec in place)")
executeFile self False args (Just env')
where
newFlags :: Text
newFlags = Text -> [Text] -> Text
T.intercalate Text
" " [Text]
flags