log: add much more logging

This commit is contained in:
1computer1 2020-06-17 00:29:25 -04:00
parent 2597c80f3d
commit 3082f9b35e
4 changed files with 80 additions and 28 deletions

View file

@ -22,6 +22,6 @@ runMyriadServer configPath languagesDir = do
startCleanup startCleanup
logInfo ["Finished Docker-related setup"] logInfo ["Finished Docker-related setup"]
let myriadPort = fromIntegral $ env ^. #config % #port let myriadPort = fromIntegral $ env ^. #config % #port
onReady = runStdoutLoggingT $ logInfo ["Server started on port ", cs $ show myriadPort, "!"] onReady = runStdoutLoggingT $ logInfo ["Server started on http://localhost:", cs $ show myriadPort]
settings = setPort myriadPort . setBeforeMainLoop onReady $ defaultSettings settings = setPort myriadPort . setBeforeMainLoop onReady $ defaultSettings
runSettings settings $ app env runSettings settings $ app env

View file

@ -12,6 +12,8 @@ module Myriad.Core
, exec , exec
, exec_ , exec_
, logInfo , logInfo
, logDebug
, logWarn
, logError , logError
, mapMVar , mapMVar
, writeMVar , writeMVar
@ -19,7 +21,7 @@ module Myriad.Core
import Control.Monad.Base import Control.Monad.Base
import Control.Monad.Except import Control.Monad.Except
import Control.Monad.Logger hiding (logError, logInfo) import Control.Monad.Logger hiding (logError, logDebug, logWarn, logInfo)
import Control.Monad.Reader import Control.Monad.Reader
import Control.Monad.State import Control.Monad.State
import Control.Monad.Trans.Control import Control.Monad.Trans.Control
@ -28,6 +30,7 @@ import Control.Monad.Writer
import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Lazy as BL
import qualified Data.Map.Strict as M import qualified Data.Map.Strict as M
import Data.Snowflake import Data.Snowflake
import Data.String.Conversions
import qualified Data.Text as T import qualified Data.Text as T
import Control.Concurrent.MVar.Lifted import Control.Concurrent.MVar.Lifted
@ -94,15 +97,23 @@ initEnv configPath languagesDir =
runMyriadT :: MonadIO m => Env -> MyriadT m a -> m a runMyriadT :: MonadIO m => Env -> MyriadT m a -> m a
runMyriadT env = runStdoutLoggingT . flip runReaderT env . unMyriadT runMyriadT env = runStdoutLoggingT . flip runReaderT env . unMyriadT
exec :: MonadIO m => [String] -> m BL.ByteString exec :: (MonadIO m, MonadLogger m) => [String] -> m BL.ByteString
exec = readProcessInterleaved_ . shell . mconcat exec args = do
logDebug ["Executing `", cs $ mconcat args, "`"]
readProcessInterleaved_ . shell $ mconcat args
exec_ :: MonadIO m => [String] -> m () exec_ :: (MonadIO m, MonadLogger m) => [String] -> m ()
exec_ = (() <$) . exec exec_ = void . exec
logInfo :: MonadLogger m => [T.Text] -> m () logInfo :: MonadLogger m => [T.Text] -> m ()
logInfo = logInfoN . mconcat logInfo = logInfoN . mconcat
logDebug :: MonadLogger m => [T.Text] -> m ()
logDebug = logDebugN . mconcat
logWarn :: MonadLogger m => [T.Text] -> m ()
logWarn = logWarnN . mconcat
logError :: MonadLogger m => [T.Text] -> m () logError :: MonadLogger m => [T.Text] -> m ()
logError = logErrorN . mconcat logError = logErrorN . mconcat

View file

@ -58,8 +58,12 @@ buildAllImages :: Myriad ()
buildAllImages = do buildAllImages = do
config <- gview #config config <- gview #config
if config ^. #buildConcurrently if config ^. #buildConcurrently
then forConcurrently_ (config ^. #languages) buildImage then do
else forM_ (config ^. #languages) buildImage logInfo ["Building all images concurrently"]
forConcurrently_ (config ^. #languages) buildImage
else do
logInfo ["Building all images sequentially"]
forM_ (config ^. #languages) buildImage
startCleanup :: Myriad () startCleanup :: Myriad ()
startCleanup = do startCleanup = do
@ -71,8 +75,9 @@ startCleanup = do
timer :: Int -> Myriad () timer :: Int -> Myriad ()
timer t = forever $ do timer t = forever $ do
threadDelay t threadDelay t
logInfo ["Starting cleanup of containers"]
n <- killContainers n <- killContainers
logInfo ["Cleaned up ", cs $ show n, " containers"] logInfo ["Cleaned up ", cs $ show n, " containers, next in ", cs $ show t, " seconds"]
timer t timer t
setupContainer :: Language -> Myriad ContainerName setupContainer :: Language -> Myriad ContainerName
@ -86,6 +91,7 @@ setupContainer lang = do
setup = do setup = do
ref <- gview #containers ref <- gview #containers
cnt <- newContainerName lang cnt <- newContainerName lang
logInfo ["Setting up new container ", cs cnt]
exec_ exec_
[ "docker run --rm --name=" [ "docker run --rm --name="
, cs cnt , cs cnt
@ -105,7 +111,7 @@ setupContainer lang = do
exec_ ["docker exec ", cnt, " mkdir eval"] exec_ ["docker exec ", cnt, " mkdir eval"]
exec_ ["docker exec ", cnt, " chmod 711 eval"] exec_ ["docker exec ", cnt, " chmod 711 eval"]
mapMVar ref $ M.insert (lang ^. #name) cnt mapMVar ref $ M.insert (lang ^. #name) cnt
logInfo ["Started container ", cs cnt] logInfo ["Started new container ", cs cnt]
pure cnt pure cnt
killContainer :: LanguageName -> Myriad Bool killContainer :: LanguageName -> Myriad Bool
@ -114,9 +120,12 @@ killContainer lang = do
case containers M.!? lang of case containers M.!? lang of
Nothing -> pure False Nothing -> pure False
Just cnt -> do Just cnt -> do
logInfo ["Killing container ", cs cnt]
res <- kill cnt res <- kill cnt
case res of case res of
Nothing -> pure True Nothing -> do
logInfo ["Killed container ", cs cnt]
pure True
Just err -> do Just err -> do
logError ["An exception occured when killing ", cs cnt, ":\n", cs $ show err] logError ["An exception occured when killing ", cs cnt, ":\n", cs $ show err]
pure False pure False
@ -128,20 +137,20 @@ killContainer lang = do
res <- try $ exec_ ["docker kill ", cnt] res <- try $ exec_ ["docker kill ", cnt]
case res of case res of
Left err -> pure $ Just err Left err -> pure $ Just err
Right _ -> do Right _ -> pure Nothing
logInfo ["Killed container ", cs cnt]
pure Nothing
killContainers :: Myriad [ContainerName] killContainers :: Myriad [ContainerName]
killContainers = do killContainers = do
containers <- gview #containers >>= readMVar containers <- gview #containers >>= readMVar
logInfo ["Starting killing of containers"]
xs <- forConcurrently (M.toList containers) $ \(k, v) -> (v,) <$> killContainer k xs <- forConcurrently (M.toList containers) $ \(k, v) -> (v,) <$> killContainer k
logInfo ["Finished killing of containers"]
pure . map fst $ filter snd xs pure . map fst $ filter snd xs
evalCode :: Language -> Int -> String -> Myriad EvalResult evalCode :: Language -> Int -> String -> Myriad EvalResult
evalCode lang numRetries code = withContainer $ \cnt -> do evalCode lang numRetries code = withContainer $ \cnt -> do
doneRef <- newMVar False -- For keeping track of if the evaluation is done, i.e. succeeded or timed out. doneRef <- newMVar False -- For keeping track of if the evaluation is done, i.e. succeeded or timed out.
void . fork $ timer doneRef -- `race` could not have been used here since some evals can't be cancelled. void . fork $ timer cnt doneRef -- `race` could not have been used here since some evals can't be cancelled.
snowflakeGen <- gview #snowflakeGen snowflakeGen <- gview #snowflakeGen
snowflake <- liftIO $ nextSnowflake snowflakeGen snowflake <- liftIO $ nextSnowflake snowflakeGen
res <- try $ eval cnt snowflake res <- try $ eval cnt snowflake
@ -152,17 +161,31 @@ evalCode lang numRetries code = withContainer $ \cnt -> do
if done if done
-- If we find the eval is done from an exception, then it was timed out. -- If we find the eval is done from an exception, then it was timed out.
then do then do
logError ["Code timed out in container ", cs cnt, ", evaluation ", cs $ show snowflake] logInfo ["Code timed out in container ", cs cnt, ", evaluation ", cs $ show snowflake]
pure EvalTimedOut pure EvalTimedOut
-- Otherwise, the container was killed from another eval, so we should retry. -- Otherwise, the container was killed from another eval, so we should retry.
else do else do
writeMVar doneRef True writeMVar doneRef True
if numRetries < fromIntegral (lang ^. #retries) if numRetries < fromIntegral (lang ^. #retries)
then do then do
logError ["An exception occured in ", cs cnt, ", evaluation ", cs $ show snowflake, ", retrying:\n", cs $ show err] logInfo
[ "An exception occured in "
, cs cnt
, ", evaluation "
, cs $ show snowflake
, "retrying:\n"
, cs $ show err
]
evalCode lang (numRetries + 1) code evalCode lang (numRetries + 1) code
else do else do
logError ["An exception occured in ", cs cnt, ", evaluation ", cs $ show snowflake, ":\n", cs $ show err] logInfo
[ "An exception occured in "
, cs cnt
, ", evaluation "
, cs $ show snowflake
, ":\n"
, cs $ show err
]
pure EvalErrored pure EvalErrored
Right x -> do Right x -> do
writeMVar doneRef True writeMVar doneRef True
@ -177,13 +200,18 @@ evalCode lang numRetries code = withContainer $ \cnt -> do
cnt <- bracket_ (waitQSem csem) (signalQSem csem) $ setupContainer lang cnt <- bracket_ (waitQSem csem) (signalQSem csem) $ setupContainer lang
f cnt f cnt
timer :: MVar Bool -> Myriad () timer :: ContainerName -> MVar Bool -> Myriad ()
timer doneRef = do timer cnt doneRef = do
logDebug ["Starting timeout of ", cs . show $ lang ^. #timeout, " seconds for container ", cs cnt]
threadDelay $ fromIntegral (lang ^. #timeout) * 1000000 threadDelay $ fromIntegral (lang ^. #timeout) * 1000000
done <- readMVar doneRef done <- readMVar doneRef
unless done . void $ do if done
writeMVar doneRef True then do
killContainer $ lang ^. #name logDebug ["Finished timeout for container ", cs cnt, ", but container already done"]
else do
logDebug ["Finished timeout for container ", cs cnt, " and killing it"]
writeMVar doneRef True
void . killContainer $ lang ^. #name
eval :: ContainerName -> Snowflake -> Myriad EvalResult eval :: ContainerName -> Snowflake -> Myriad EvalResult
eval cnt snowflake = do eval cnt snowflake = do
@ -191,8 +219,15 @@ evalCode lang numRetries code = withContainer $ \cnt -> do
exec_ ["docker exec ", cs cnt, " mkdir eval/", show snowflake] exec_ ["docker exec ", cs cnt, " mkdir eval/", show snowflake]
exec_ ["docker exec ", cs cnt, " chmod 777 eval/", show snowflake] exec_ ["docker exec ", cs cnt, " chmod 777 eval/", show snowflake]
-- User 1001 will be used for the actual execution so that they can't access `eval` itself -- User 1001 will be used for the actual execution so that they can't access `eval` itself
let cmd = concat ["docker exec -i -u1001:1001 -w/tmp/eval/", show snowflake, " ", cnt, " /bin/sh /var/run/run.sh | head -c 4K"] let cmd = mconcat
[ "docker exec -i -u1001:1001 -w/tmp/eval/"
, show snowflake
, " "
, cnt
, " /bin/sh /var/run/run.sh | head -c 4K"
]
pr = setStdin (byteStringInput $ cs code) $ shell cmd pr = setStdin (byteStringInput $ cs code) $ shell cmd
logDebug ["Executing with stdin `", cs cmd, "`"]
output <- readProcessInterleaved_ pr output <- readProcessInterleaved_ pr
exec_ ["docker exec ", cnt, " rm -rf eval/", show snowflake] exec_ ["docker exec ", cnt, " rm -rf eval/", show snowflake]
logInfo ["Ran code in container ", cs cnt, ", evaluation ", cs $ show snowflake] logInfo ["Ran code in container ", cs cnt, ", evaluation ", cs $ show snowflake]

View file

@ -58,14 +58,20 @@ serverT = handleLanguages :<|> handleEval :<|> handleContainers :<|> handleClean
logInfo ["POST /eval"] logInfo ["POST /eval"]
languages <- gview $ #config % #languages languages <- gview $ #config % #languages
case find (\x -> x ^. #name == language) languages of case find (\x -> x ^. #name == language) languages of
Nothing -> throwError $ err404 { errBody = "Language " <> cs language <> " was not found" } Nothing -> do
logDebug ["Language ", cs language , " was not found (404)"]
throwError $ err404 { errBody = "Language " <> cs language <> " was not found" }
Just cfg -> do Just cfg -> do
env <- ask env <- ask
res <- withAsync (liftIO . runMyriadT env . evalCode cfg 0 $ cs code) wait res <- withAsync (liftIO . runMyriadT env . evalCode cfg 0 $ cs code) wait
case res of case res of
EvalErrored -> throwError $ err500 { errBody = "Evaluation failed" } EvalErrored -> do
EvalTimedOut -> throwError $ err504 { errBody = "Evaluation timed out" } logDebug ["Evaluation failed (500)"]
EvalOk xs -> pure . EvalResponse $ cs xs throwError $ err500 { errBody = "Evaluation failed" }
EvalTimedOut -> do
logDebug ["Evaluation timed out (504)"]
throwError $ err504 { errBody = "Evaluation timed out" }
EvalOk xs -> pure . EvalResponse $ cs xs
handleContainers :: Myriad [T.Text] handleContainers :: Myriad [T.Text]
handleContainers = do handleContainers = do