72

How do I tell stack to build my executable and all its dependencies with -prof?

Simply adding it to ghc-options in the .cabal file is not enough, because it only tries to build the executable with profiling enabled, which fails.

Matthias Braun
  • 32,039
  • 22
  • 142
  • 171
Sebastian Graf
  • 3,602
  • 3
  • 27
  • 38

4 Answers4

115

Profiling builds with Stack 1.0.0 and newer

To build with profiling enabled:

stack build --profile

You may need to run stack clean first, but this should be fixed in Stack 1.5.0.

To profile:

stack exec --profile -- <your program> +RTS <profiling options>

where for <profiling options> you might want -p for time profiling or -h for memory profiling. For time profiling, the profile appears in ./<your program>.prof, and for memory profiling, the profile appears in ./<your program>.hp.

See GHC profiling documentation for more profiling options.

Avoiding unnecessary rebuilding of local packages (fixed in Stack 2.X?)

Due to a long standing Stack issue, switching between profiling and non-profiling builds can cause a lot of unnecessary rebuilding of local packages and extra-deps. To work around this, you can use separate build caches for your profiling and non-profiling builds. For example, where you use stack <cmd> for non profiling you can use

stack --work-dir .stack-work-profile --profile <cmd>

for a profiling version of <cmd>. This uses a separate cache in .stack-work-profile for profiling artifacts, whereas non profiling artifacts will be preserved in the default .stack-work cache.

Profiling builds with Stack versions before 1.0.0 (i.e. from 2015)

To build with profiling enabled:

stack build --executable-profiling --library-profiling --ghc-options="-fprof-auto -rtsopts"

To profile:

stack exec -- <your program> +RTS <profiling options>

Example for Stack 1.0.0 and newer

Suppose you have a package called test with a single executable test defined by main here:

module Main where

main :: IO ()
main = do
  print $ foo 0

foo :: Int -> Int
foo x = fooSub (x+1)
  where
    fooSub x = bar (x+1)

bar :: Int -> Int
bar x = barSub (x+1)
  where
    barSub x = barSubSub (x+1)
      where
        barSubSub x = x+1

then doing stack build --profile && stack exec -- test +RTS -p will produce a file ./test.prof which includes

                                                                                                individual      inherited
COST CENTRE                 MODULE                SRC                        no.     entries  %time %alloc   %time %alloc

  [... many lines omitted ...]
  main                      Main                  src/Main.hs:(4,1)-(5,15)    97          0    0.0    0.0     0.0    0.0
   foo                      Main                  src/Main.hs:(8,1)-(10,24)   98          1    0.0    0.0     0.0    0.0
    foo.fooSub              Main                  src/Main.hs:10:5-24         99          1    0.0    0.0     0.0    0.0
     bar                    Main                  src/Main.hs:(13,1)-(17,46) 100          1    0.0    0.0     0.0    0.0
      bar.barSub            Main                  src/Main.hs:(15,5)-(17,46) 101          1    0.0    0.0     0.0    0.0
       bar.barSub.barSubSub Main                  src/Main.hs:17:9-46        102          1    0.0    0.0     0.0    0.0
 main                       Main                  src/Main.hs:(4,1)-(5,15)    95          0    0.0   20.5     0.0   20.5

I.e., there is profiling information for all definitions, including local definitions in where clauses.

If you only want to profile top-level definitions, you can build with the GHC option -fprof-auto-top instead: doing stack build --profile --ghc-options=-fprof-auto-top && stack exec -- test +RTS -p produces a ./test.prof which includes

                                                                                individual      inherited
COST CENTRE MODULE                SRC                        no.     entries  %time %alloc   %time %alloc

 [... many lines omitted ...]
  main      Main                  src/Main.hs:(4,1)-(5,15)    97          0    0.0    0.0     0.0    0.0
   foo      Main                  src/Main.hs:(8,1)-(10,24)   98          1    0.0    0.0     0.0    0.0
    bar     Main                  src/Main.hs:(13,1)-(17,46)  99          1    0.0    0.0     0.0    0.0
 main       Main                  src/Main.hs:(4,1)-(5,15)    95          0    0.0   20.5     0.0   20.5

instead.

Finally, note that stack build --profile also turns on stack traces. If you change the program so that barSubSub x = error $ show x, then running stack build --profile && stack exec test produces

test: 4
CallStack (from HasCallStack):
  error, called at src/Main.hs:17:23 in main:Main
CallStack (from -prof):
  Main.bar.barSub.barSubSub (src/Main.hs:17:9-36)
  Main.bar.barSub (src/Main.hs:(15,5)-(17,36))
  Main.bar (src/Main.hs:(13,1)-(17,36))
  Main.foo.fooSub (src/Main.hs:10:5-24)
  Main.foo (src/Main.hs:(8,1)-(10,24))
  Main.main (src/Main.hs:(4,1)-(5,15))
  Main.CAF:lvl8_r4Fc (<no location info>)

Pretty cool!

ntc2
  • 11,203
  • 7
  • 53
  • 70
  • 9
    This doesn't work. Using stack exec my-exe +RTS -p shows executables haven't been compiled with -prof, and trying to do so shows that ld cannot find the profiled versions of the libraries. – Yves Parès Sep 26 '15 at 23:06
  • 2
    This worked for me: `stack install --enable-executable-profiling --enable-library-profiling --ghc-options="-fprof-auto -rtsopts"` – Dfr Dec 11 '15 at 20:49
  • How do you execute it / profile it after the build has finished? – Chris Stryczynski Feb 20 '17 at 14:54
  • 1
    @ChrisStryczynski: I just edited my answer. Hope that helps. – ntc2 Feb 20 '17 at 18:48
  • 2
    Could you put the part about `.stack-work-profile` first? The first time I read this answer, I saw the first line and did it, and regretted it once I read on. – Mario Carneiro Nov 04 '18 at 02:29
  • 4
    Hi from 2019! Today with Stack 2.1.3, these instructions did not work for me. I needed to add `--profile` to the `stack exec` call as well. This worked: `stack build --profile && stack exec --profile -- my-exe +RTS ...` – Calvin Oct 15 '19 at 02:51
  • 1
    @Calvin: thanks for the heads up! Ijust updated my answer to include `--profile` in the `stack exec` command. My brief testing indicates that Stack 2.x versions are smarter than before about profiling, automatically keeping the regular and `--profile` versions separate, but I can't find the documentation for this. I believe this is the relevant Stack issue, but it's still open: https://github.com/commercialhaskell/stack/issues/4032 – ntc2 Oct 15 '19 at 04:59
  • What do the numbers mean in `src/Main.hs:(4,1)-(5,15)`? I initially thought they're range of line numbers but they dont make sense; also they dont seem to match in my case.. The first of each pair may define a range of lines? – Nawaz Jul 08 '20 at 18:12
  • Ok. It seems they indicate the pair of the cursor locations.. as in the code-snippet _from_ and _to_ .. – Nawaz Jul 08 '20 at 18:16
14

I had this problem as well and found that the problem was in the invocation:

stack exec my-exe +RTS -p passes -p to stack instead of my-exe. This works:

stack exec -- my-exe +RTS -p
user905686
  • 4,491
  • 8
  • 39
  • 60
Tomáš Janoušek
  • 9,877
  • 1
  • 13
  • 13
6

For stack build, stack bench and stack test you can just use stack build/bench/test --profile. You may have to stack clean first to get it to recompile with profiling.

For stack build you will still have to pass +RTS -p or whatever option you need (see GHC User Guide) when running the executable as in @Tomáš Janoušek answer.

You can also find more information in the debugging section of the stack user guide.

Community
  • 1
  • 1
Julia Path
  • 2,356
  • 15
  • 21
3

Assuming a project called project-name, this is how I get a time and heap profile (with colors):

  1. Add dependencies to the build-depends section of project-name.cabal
  2. Get dependent packages: stack build
  3. From inside project-name/app compile the program with profiling enabled: stack ghc -- -prof -fprof-auto -rtsopts -O2 Main.hs
  4. Then create the heap and time profile ./Main +RTS -hc -p. This will produce Main.hp and Main.prof
  5. Turn the heap profile into a PostScript file and then into a PDF chart with colors: stack exec -- hp2ps -c Main.hp && ps2pdf Main.ps

That's the heap profile from the PDF:

heap_profile_with_colors

Matthias Braun
  • 32,039
  • 22
  • 142
  • 171
  • @LupusOssorum: I don't know if there's a way to turn a `.prof` file into PDF. But there are visualization tools for `.prof` files such as [profiteur](https://jaspervdj.be/posts/2014-02-25-profiteur-ghc-prof-visualiser.html), [professor](https://github.com/vincenthz/professor), and [viewprof](https://github.com/maoe/viewprof). – Matthias Braun Apr 27 '19 at 08:33