Skip to content

Commit

Permalink
Profiteur timing visualisation (#3951)
Browse files Browse the repository at this point in the history
This PR adds a small utility for turning JSON logs into timing profiles
visualised via the https://github.com/jaspervdj/profiteur library

<img width="1649" alt="image"
src="https://github.com/runtimeverification/haskell-backend/assets/10553895/8531096f-4554-4223-b6ef-031898be680c">

---------

Co-authored-by: github-actions <[email protected]>
  • Loading branch information
goodlyrottenapple and github-actions authored Jun 24, 2024
1 parent 77e50ab commit 47f2ac8
Show file tree
Hide file tree
Showing 15 changed files with 604 additions and 162 deletions.
39 changes: 28 additions & 11 deletions booster/library/Booster/CLOptions.hs
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,8 @@ allowedLogLevels =
( "EquationWarnings"
, "Log warnings indicating soft-violations of conditions, i.e. exceeding the equation recursion/iteration limit "
)
, ("TimeProfile", "Logs for timing analysis")
, ("Timing", "Formerly --print-stats")
]

levelToContext :: Map Text [ContextFilter]
Expand All @@ -244,49 +246,64 @@ levelToContext =
[
( "Simplify"
,
[ [ctxt| booster|kore>function*|simplification*|hook*,success|failure|abort|detail |]
, [ctxt| booster|kore>function*|simplification*,match,failure|abort |]
[ [ctxt| request*,booster|kore>function*|simplification*|hook*,success|failure|abort|detail |]
, [ctxt| request*,booster|kore>function*|simplification*,match,failure|abort |]
]
)
,
( "SimplifySuccess"
,
[ [ctxt| booster|kore>function*|simplification*|hook*,success|detail |]
[ [ctxt| request*,booster|kore>function*|simplification*|hook*,success|detail |]
]
)
,
( "Rewrite"
,
[ [ctxt| booster|kore>rewrite*,success|failure|abort|detail |]
, [ctxt| booster|kore>rewrite*,match|definedness|constraint,failure|abort |]
[ [ctxt| request*,booster|kore>rewrite*,success|failure|abort|detail |]
, [ctxt| request*,booster|kore>rewrite*,match|definedness|constraint,failure|abort |]
]
)
,
( "RewriteSuccess"
,
[ [ctxt| booster|kore>rewrite*,success|detail |]
[ [ctxt| request*,booster|kore>rewrite*,success|detail |]
]
)
,
( "SMT"
,
[ [ctxt| booster|kore>smt |]
[ [ctxt| request*,booster|kore>smt |]
]
)
,
( "Aborts"
,
[ [ctxt| booster>rewrite*,detail. |]
, [ctxt| booster>rewrite*,match|definedness|constraint,abort. |]
[ [ctxt| request*,booster>rewrite*,detail. |]
, [ctxt| request*,booster>rewrite*,match|definedness|constraint,abort. |]
, [ctxt| proxy. |]
, [ctxt| proxy,abort. |]
, [ctxt| booster>failure,abort |]
, [ctxt| request*,booster>failure,abort |]
]
)
,
( "EquationWarnings"
,
[ [ctxt| booster>(simplification *|function *)>warning |]
[ [ctxt| request*,booster>(simplification *|function *)>warning |]
]
)
,
( "TimeProfile"
,
[ [ctxt| request*,booster|kore>rewrite*,success|failure|abort|detail |]
, [ctxt| request*,booster|kore>rewrite*,match|definedness|condition,failure|abort |]
, [ctxt| request*,booster|kore>function*|simplification*,success|failure|abort|detail |]
, [ctxt| request*,booster|kore>function*|simplification*,match,failure|abort |]
]
)
,
( "Timing"
,
[ [ctxt| *>timing |]
]
)
]
Expand Down
5 changes: 5 additions & 0 deletions booster/library/Booster/Log.hs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import Data.Maybe (fromMaybe)
import Data.Set qualified as Set
import Data.Text (Text, pack)
import Data.Text.Encoding (decodeUtf8)
import Network.JSONRPC qualified as JSONRPC
import Prettyprinter (Pretty, pretty)
import System.Log.FastLogger (FormattedTime)
import UnliftIO (MonadUnliftIO)
Expand Down Expand Up @@ -222,6 +223,10 @@ instance ContextFor Symbol where
withContextFor s =
withContext_ (CLWithId . CtxHook $ maybe "not-hooked" decodeUtf8 s.attributes.hook)

instance ContextFor (JSONRPC.Id) where
withContextFor r =
withContext_ (CLWithId . CtxRequest $ pack $ JSONRPC.fromId r)

parseRuleId :: RewriteRule tag -> CL.UniqueId
parseRuleId = fromMaybe CL.UNKNOWN . CL.parseUId . coerce . (.attributes.uniqueId)

Expand Down

Large diffs are not rendered by default.

34 changes: 14 additions & 20 deletions booster/tools/booster/Server.hs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE QuasiQuotes #-}
{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}

{- |
Expand Down Expand Up @@ -35,6 +34,7 @@ import Data.Set qualified as Set
import Data.Text (Text)
import Data.Text qualified as Text
import Data.Text.Encoding qualified as Text (decodeUtf8, encodeUtf8)
import Network.JSONRPC qualified as JSONRPC
import Options.Applicative
import System.Clock (
Clock (..),
Expand Down Expand Up @@ -137,8 +137,7 @@ main = do
}
, proxyOptions =
ProxyOptions
{ printStats
, forceFallback
{ forceFallback
, boosterSMT
, fallbackReasons
, simplifyAtEnd
Expand All @@ -149,7 +148,6 @@ main = do
logContextsWithcustomLevelContexts =
logContexts
<> concatMap (\case LevelOther o -> fromMaybe [] $ levelToContext Map.!? o; _ -> []) customLevels
<> [[Ctxt.ctxt| *>timing |] | printStats]
contextLoggingEnabled = not (null logContextsWithcustomLevelContexts)
koreSolverOptions = translateSMTOpts smtOptions
timestampFlag = case timeStampsFormat of
Expand Down Expand Up @@ -312,10 +310,11 @@ main = do
Booster.Log.withContext CtxProxy $
Booster.Log.logMessage' ("Starting RPC server" :: Text)

let koreRespond, boosterRespond :: Respond (API 'Req) (Booster.Log.LoggerT IO) (API 'Res)
koreRespond = Kore.respond kore.serverState (ModuleName kore.mainModule) runSMT
boosterRespond =
Booster.Log.withContext CtxBooster
let koreRespond, boosterRespond :: JSONRPC.Id -> Respond (API 'Req) (Booster.Log.LoggerT IO) (API 'Res)
koreRespond reqId = Kore.respond (fromId reqId) kore.serverState (ModuleName kore.mainModule) runSMT
boosterRespond reqId =
Booster.Log.withContextFor reqId
. Booster.Log.withContext CtxBooster
. Booster.respond boosterState

proxyConfig =
Expand All @@ -332,9 +331,10 @@ main = do
jsonRpcServer
srvSettings
( \rawReq req ->
runBoosterLogger $
logRequestId (fromId $ getReqId rawReq)
>> Proxy.respondEither proxyConfig boosterRespond koreRespond req
let reqId = getReqId rawReq
in runBoosterLogger $
logRequestId reqId
>> Proxy.respondEither proxyConfig (boosterRespond reqId) (koreRespond reqId) req
)
[ Kore.handleDecidePredicateUnknown
, Booster.handleSmtError
Expand Down Expand Up @@ -362,7 +362,7 @@ main = do
Booster.Log.withContext CtxProxy $
Booster.Log.logMessage' $
Text.pack $
"Processing request " <> rid
"Processing request " <> fromId rid

isInterrupt :: AsyncException -> Maybe ()
isInterrupt UserInterrupt = Just ()
Expand Down Expand Up @@ -405,9 +405,7 @@ data CLProxyOptions = CLProxyOptions
}

data ProxyOptions = ProxyOptions
{ printStats :: Bool
-- ^ print timing statistics per request and on shutdown
, forceFallback :: Maybe Depth
{ forceFallback :: Maybe Depth
-- ^ force fallback every n-steps
, boosterSMT :: Bool
-- ^ whether to use an SMT solver in booster code (but keeping kore-rpc's SMT solver)
Expand All @@ -432,11 +430,7 @@ clProxyOptionsParser =
where
parseProxyOptions =
ProxyOptions
<$> switch
( long "print-stats"
<> help "(development) Print timing information per request and on shutdown"
)
<*> optional
<$> optional
( option
(Depth <$> auto)
( metavar "INTERIM_SIMPLIFICATION"
Expand Down
7 changes: 7 additions & 0 deletions cabal.project
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,10 @@ source-repository-package
type: git
location: https://github.com/goodlyrottenapple/tasty-test-reporter.git
tag: b704130545aa3925a8487bd3e92f1dd5ce0512e2

source-repository-package
type: git
location: https://github.com/goodlyrottenapple/profiteur.git
tag: 7b30bbe6b2a6b72a5b4896f3a0eed5587a5bf465

constraints: profiteur +embed-data-files
6 changes: 3 additions & 3 deletions dev-tools/kore-rpc-dev/Server.hs
Original file line number Diff line number Diff line change
Expand Up @@ -233,12 +233,12 @@ main = do
Booster.Log.withContext CtxKore $
Booster.Log.logMessage' ("Starting RPC server" :: Text.Text)

let koreRespond :: Respond (API 'Req) (LoggerT IO) (API 'Res)
koreRespond = Kore.respond kore.serverState (ModuleName kore.mainModule) runSMT
let koreRespond :: Id -> Respond (API 'Req) (LoggerT IO) (API 'Res)
koreRespond reqId = Kore.respond (fromId reqId) kore.serverState (ModuleName kore.mainModule) runSMT
server =
jsonRpcServer
srvSettings
(const $ runBoosterLogger . respond koreRespond)
(\rawReq -> runBoosterLogger . respond (koreRespond $ getReqId rawReq))
[Kore.handleDecidePredicateUnknown, handleErrorCall, handleSomeException]
interruptHandler _ = do
when (logLevel >= LevelInfo) $
Expand Down
17 changes: 17 additions & 0 deletions dev-tools/package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,23 @@ executables:
- text
ghc-options:
- -rtsopts
time-profile:
source-dirs: time-profile
main: Main.hs
dependencies:
- aeson
- base
- bytestring
- containers
- directory
- filepath
- kore-rpc-types
- profiteur
- text
- unordered-containers
- vector
ghc-options:
- -rtsopts
parsetest-binary:
source-dirs: parsetest-binary
main: ParsetestBinary.hs
Expand Down
59 changes: 59 additions & 0 deletions dev-tools/time-profile/Main.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{- | Stand-alone parser executable for extracting timing information from JSON context logs
Copyright : (c) Runtime Verification, 2022
License : BSD-3-Clause
-}
module Main (
main,
) where

import Data.Aeson (decode)
import Data.ByteString.Lazy.Char8 qualified as BS
import Data.Maybe (mapMaybe)
import Profiteur
import Profiteur.Main (writeReport)
import System.Environment (getArgs)
import System.IO qualified as IO

{- | Utility for parsing and extracting timing information from context logs,
produced by running the booster binary with `-l TimeProfile --log-format json --log-timestamps --timestamp-format nanosecond`.
This tool collects the timing per each context level and uses the profiteur library to generate an HTML report of the timing information
Call via `timing <path>.log`
-}
main :: IO ()
main =
getArgs >>= \case
files
| "-h" `elem` files || "--help" `elem` files -> do
putStrLn
"This tool parses the JSON contextual logs with timestamps and generates a time profile."
putStrLn "Call via `time-profile <path>.log`"
putStrLn
"To produce the correct context logs, run kore-rpc-booster with `-l TimeProfile --log-format json --log-timestamps --timestamp-format nanosecond`"
[profFile] -> do
logs <- mapMaybe decode . BS.lines <$> BS.readFile profFile

let (timings, ruleMap) = case logs of
m : ms -> collectTiming mempty m ms
[] -> mempty
timing = foldr (((<>)) . fmap (,Count 1) . computeTimes) (TimeMap mempty) timings
htmlFile = profFile ++ ".html"
IO.withBinaryFile htmlFile IO.WriteMode $ \h -> do
-- produce a timing map mirroring the context levels, roughly:
-- main -> request n -> kore|booster -> execute|simplify|implies -> term rid -> rewrite id|simplification id|equation id -> success|failure
writeReport h profFile $ toNodeMap timing ruleMap

let htmlAggregateFile = profFile ++ ".aggregate.html"
IO.withBinaryFile htmlAggregateFile IO.WriteMode $ \h -> do
writeReport h profFile $
-- produce an agregate profile of all the number and total time spent trying each rewrite rule. The structure is:
-- main -> request n -> rewrite id -> kore|booster
toNodeMap (aggregateRewriteRules aggregateRewriteRulesPerRequest timing) ruleMap

let htmlAggregateFile2 = profFile ++ ".aggregate2.html"
IO.withBinaryFile htmlAggregateFile2 IO.WriteMode $ \h -> do
writeReport h profFile $
-- produce an agregate profile of all the number and total time spent trying each rewrite rule. The structure is:
-- main -> request n -> kore|booster -> rewrite id
toNodeMap (aggregateRewriteRules aggregateRewriteRulesPerRequest2 timing) ruleMap
_ -> error "invalid arguments"
Loading

0 comments on commit 47f2ac8

Please sign in to comment.