on master help sign in
Add ob profile command

This dumps some profiling information that can be viewed and used to debug Obelisk projects. ‘ob profile’ works like ob run, but instead of using ghci, it builds an executable that is built with profiling enabled.

commit53d3dffd22b5a6fd4ff5ec858f7f6c926ead8818 authorMatthew Bauer <mjbauer95@gmail.com> dateTue 11 Feb 2020, 11:47 (7 years ago) parent383781d
3 files changed+74−21
ChangeLog.md+3 lib/command/src/Obelisk/Command.hs+4−1 lib/command/src/Obelisk/Command/Run.hs+67−20
ChangeLog.md+3
@@ -8,6 +8,9 @@ This project's release branch is `master`. This log is written from the perspect
88* Add local unpacked packages to the `ob run`, `ob watch`, and `ob repl` sessions. Any `.cabal` or hpack package inside the current obelisk project will be loaded into the session. For `ob run`/`ob watch` this means the session will automatically reload when you save a source file in any of those packages. For `ob repl` it means that `:r` will reload changes to any of those packages. There are some edge cases where this integration is still rough. Report any issues you encounter. ([#489](https://github.com/obsidiansystems/obelisk/pull/489))
99* Add `ob hoogle` command to start a local [Hoogle](https://hoogle.haskell.org/) server for the project. ([#628](https://github.com/obsidiansystems/obelisk/pull/628))
1010* `ob thunk pack` will now attempt to automatically detect if the thunk is a private or public repo. To avoid this detection, specify `--private` or `--public` manually. ([#607](https://github.com/obsidiansystems/obelisk/pull/607))
11+* Add `ob profile` command to run Obelisk project with profiling. `ob
12+profile` works like ob run, but instead of using ghci, it builds an
13+executable that is built with profiling enabled.
1114
1215## v0.4.0.0 - 2020-01-10
1316
lib/command/src/Obelisk/Command.hs+4−1
@@ -87,6 +87,7 @@ data ObCommand
8787 = ObCommand_Init InitSource Bool
8888 | ObCommand_Deploy DeployCommand
8989 | ObCommand_Run
90+ | ObCommand_Profile
9091 | ObCommand_Thunk ThunkCommand
9192 | ObCommand_Repl
9293 | ObCommand_Watch
@@ -108,6 +109,7 @@ obCommand cfg = hsubparser
108109 [ command "init" $ info (ObCommand_Init <$> initSource <*> initForce) $ progDesc "Initialize an Obelisk project"
109110 , command "deploy" $ info (ObCommand_Deploy <$> deployCommand cfg) $ progDesc "Prepare a deployment for an Obelisk project"
110111 , command "run" $ info (pure ObCommand_Run) $ progDesc "Run current project in development mode"
112+ , command "profile" $ info (pure ObCommand_Profile) $ progDesc "Run current project with profiling enabled"
111113 , command "thunk" $ info (ObCommand_Thunk <$> thunkCommand) $ progDesc "Manipulate thunk directories"
112114 , command "repl" $ info (pure ObCommand_Repl) $ progDesc "Open an interactive interpreter"
113115 , command "watch" $ info (pure ObCommand_Watch) $ progDesc "Watch current project for errors and warnings"
@@ -365,7 +367,8 @@ ob = \case
365367 Just RemoteBuilder_ObeliskVM -> (:[]) <$> VmBuilder.getNixBuildersArg
366368 DeployCommand_Update -> deployUpdate "."
367369 DeployCommand_Test (platform, extraArgs) -> deployMobile platform extraArgs
368− ObCommand_Run -> run
370+ ObCommand_Run -> run False
371+ ObCommand_Profile -> run True
369372 ObCommand_Thunk tc -> case tc of
370373 ThunkCommand_Update thunks config -> for_ thunks (updateThunkToLatest config)
371374 ThunkCommand_Unpack thunks -> for_ thunks unpackThunk
lib/command/src/Obelisk/Command/Run.hs+67−20
@@ -42,12 +42,15 @@ import Language.Haskell.Extension
4242import Network.Socket hiding (Debug)
4343import System.Directory
4444import System.Environment (getExecutablePath)
45+import System.Exit (ExitCode (ExitSuccess, ExitFailure))
4546import System.FilePath
4647import qualified System.Info
47−import System.IO.Temp (withSystemTempDirectory)
48+import System.IO (hPutStr, hFlush, hClose)
49+import System.IO.Temp (withSystemTempDirectory, withSystemTempFile)
50+import System.Process (waitForProcess)
4851
4952import Obelisk.App (MonadObelisk)
50−import Obelisk.CliApp (Severity (..) , failWith, putLog, proc, readCreateProcessWithExitCode)
53+import Obelisk.CliApp (Severity (..) , failWith, putLog, proc, readCreateProcessWithExitCode, setCwd, setDelegateCtlc, createProcess_)
5154import Obelisk.Command.Project (obeliskDirName, toObeliskDir, withProjectRoot, nixShellWithPkgs, findProjectAssets)
5255import Obelisk.Command.Thunk (attrCacheFileName)
5356import Obelisk.Command.Utils (findExePath, ghcidExePath)
@@ -71,20 +74,67 @@ data CabalPackageInfo = CabalPackageInfo
7174preprocessorIdentifier :: String
7275preprocessorIdentifier = "__preprocessor-apply-packages"
7376
74−run :: MonadObelisk m => m ()
75−run = withProjectRoot "." $ \root -> do
77+-- | Imports needed to use Obelisk.Run
78+obRunImports :: [String]
79+obRunImports = [ "import qualified Obelisk.Run"
80+ , "import qualified Frontend"
81+ , "import qualified Backend" ]
82+
83+run
84+ :: MonadObelisk m
85+ => Bool
86+ -- ^ Whether to enable profiling in the current project
87+ -> m ()
88+run profiled = withProjectRoot "." $ \root -> do
7689 pkgs <- fmap toList . parsePackagesOrFail =<< getLocalPkgs root
77− withGhciScript pkgs root $ \dotGhciPath -> do
78− freePort <- getFreePort
79− assets <- findProjectAssets root
80− putLog Debug $ "Assets impurely loaded from: " <> assets
81− runGhcid root True dotGhciPath pkgs $ Just $ unwords
82− [ "Obelisk.Run.run"
83− , show freePort
84− , "(Obelisk.Run.runServeAsset " ++ show assets ++ ")"
85− , "Backend.backend"
86− , "Frontend.frontend"
87− ]
90+ freePort <- getFreePort
91+ assets <- findProjectAssets root
92+ putLog Debug $ "Assets impurely loaded from: " <> assets
93+ let obRunExpr = unwords
94+ [ "Obelisk.Run.run"
95+ , show freePort
96+ , "(Obelisk.Run.runServeAsset " ++ show assets ++ ")"
97+ , "Backend.backend"
98+ , "Frontend.frontend"
99+ ]
100+ case profiled of
101+ True -> do
102+ putLog Debug "Using profiled build of project."
103+ let exeSource =
104+ unlines $
105+ [ "module Main where"
106+ , "import Control.Concurrent"
107+ , "import Control.Monad.Fix"
108+ , "import Reflex.Profiled" ]
109+ <> obRunImports <>
110+ [ "main :: IO ()"
111+ , "main = do"
112+ , " forkIO $ fix $ \\rec -> do"
113+ -- TODO: make this filename customizable
114+ , " writeProfilingData \"ob-run.rfprof\""
115+ -- write every .1 seconds.
116+ -- TODO: perhaps it should only write once at the end of ecah run
117+ , " threadDelay 10000"
118+ , " rec"
119+ , " " <> obRunExpr]
120+ -- sane flags to enable by default, enable time profiling + closure heap profiling
121+ rtsFlags = [ "+RTS", "-p", "-hc", "-RTS" ]
122+ withSystemTempFile "ob-run-profiled.hs" $ \hsFname hsHandle -> withSystemTempFile "ob-run" $ \exeFname exeHandle -> do
123+ liftIO $ hPutStr hsHandle exeSource
124+ liftIO $ hFlush hsHandle
125+ (_, _, _, ph1) <- createProcess_ "nixGhcWithProfiling" $ setCwd (Just root) $ proc "nix-shell" [ "-p", "((import ./. {}).profiled.ghc.ghcWithPackages (p: [ p.backend p.frontend]))", "--run", unwords [ "ghc", "-x", "hs", "-prof", "-fprof-auto", hsFname, "-o", exeFname ] ]
126+ code <- liftIO $ waitForProcess ph1
127+ case code of
128+ ExitSuccess -> do
129+ liftIO $ hClose exeHandle
130+ (_, _, _, ph2) <- createProcess_ "runProfExe" $ setCwd (Just root) $ setDelegateCtlc True $ proc exeFname rtsFlags
131+ _ <- liftIO $ waitForProcess ph2
132+ pure ()
133+ ExitFailure _ -> do
134+ pure ()
135+ False ->
136+ withGhciScript pkgs root $ \dotGhciPath -> do
137+ runGhcid root True dotGhciPath pkgs $ Just obRunExpr
88138
89139runRepl :: MonadObelisk m => m ()
90140runRepl = withProjectRoot "." $ \root -> do
@@ -256,15 +306,12 @@ withGhciScript packageInfos pathBase f = do
256306 , [ "Backend" | "backend" `Set.member` packageNames ]
257307 , [ "Frontend" | "frontend" `Set.member` packageNames ]
258308 ]
259− dotGhci = unlines
309+ dotGhci = unlines $
260310 -- TODO: Shell escape
261311 [ ":set -F -pgmF " <> selfExe <> " -optF " <> preprocessorIdentifier <> " " <> unwords (map (("-optF " <>) . makeRelative pathBase . _cabalPackageInfo_packageFile) packageInfos)
262312 , ":set -i" <> intercalate ":" (packageInfos >>= rootedSourceDirs)
263313 , if null modulesToLoad then "" else ":load " <> unwords modulesToLoad
264− , "import qualified Obelisk.Run"
265− , "import qualified Frontend"
266− , "import qualified Backend"
267− ]
314+ ] <> obRunImports
268315 withSystemTempDirectory "ob-ghci" $ \fp -> do
269316 let dotGhciPath = fp </> ".ghci"
270317 liftIO $ writeFile dotGhciPath dotGhci