Profiling builds with Stack

15,376

Solution 1

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!

Solution 2

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

Solution 3

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.

Solution 4

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

Share:
15,376
Sebastian Graf
Author by

Sebastian Graf

Updated on July 07, 2022

Comments

  • Sebastian Graf
    Sebastian Graf almost 2 years

    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.

  • Yves Parès
    Yves Parès over 8 years
    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.
  • Dfr
    Dfr over 8 years
    This worked for me: stack install --enable-executable-profiling --enable-library-profiling --ghc-options="-fprof-auto -rtsopts"
  • Chris Stryczynski
    Chris Stryczynski over 7 years
    How do you execute it / profile it after the build has finished?
  • ntc2
    ntc2 over 7 years
    @ChrisStryczynski: I just edited my answer. Hope that helps.
  • John
    John about 7 years
    You can also add "--force-dirty" to the end of your stack build command to force it to recompile.
  • Mario Carneiro
    Mario Carneiro over 5 years
    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.
  • Matthias Braun
    Matthias Braun about 5 years
    @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, professor, and viewprof.
  • Calvin
    Calvin over 4 years
    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 ...
  • ntc2
    ntc2 over 4 years
    @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: github.com/commercialhaskell/stack/issues/4032
  • arya
    arya over 4 years
    did you mean to say stack exec?
  • Nawaz
    Nawaz almost 4 years
    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
    Nawaz almost 4 years
    Ok. It seems they indicate the pair of the cursor locations.. as in the code-snippet from and to ..