Wizek
Wizek

Reputation: 4993

With Nix, how can I specify Haskell dependencies with profiling enabled?

I was trying like this initially:

nix-shell -p "haskell.packages.ghc821.ghcWithPackages (p: with p; [text hspec lens])" -j4 --run 'ghc Main.hs -prof

Then GHC told me

Main.hs:4:1: error:
    Could not find module ‘Control.Lens’
    Perhaps you haven't installed the profiling libraries for package ‘lens-4.15.4’?
    Use -v to see a list of the files searched for.

Searching around the web I found this: https://github.com/NixOS/nixpkgs/issues/22340

So it seems I won't be able to download from the cache. But that's okay, if at least I can build the profiled variants locally.

Can I do that somehow simply by modifying the nix expression given to -p slightly?

Then at this point in writing this question, I remembered this resource: https://github.com/NixOS/nixpkgs/blob/bd6ba7/pkgs/development/haskell-modules/lib.nix

Where I found enableLibraryProfiling. So I tried:

nix-shell -p "haskell.packages.ghc821.ghcWithPackages (p: with p; [text hspec (haskell.lib.enableLibraryProfiling lens)])" -j4 --run 'ghc Main.hs -prof'

Which got me to a new error:

src/Control/Lens/Internal/Getter.hs:26:1: error:
    Could not find module ‘Data.Functor.Contravariant’
    Perhaps you haven't installed the profiling libraries for package ‘contravariant-1.4’?
    Use -v to see a list of the files searched for.
   |
26 | import Data.Functor.Contravariant
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

So if I could map over all packages to enableLibraryProfiling on them, then I guess this could work. But my nix knowledge doesn't quite extend that far at the moment. How could I do that? And is this even the correct path to pursue?

Upvotes: 9

Views: 1886

Answers (2)

Colin Woodbury
Colin Woodbury

Reputation: 1809

I figured out a simple approach that I'm working into a largest blog post on Haskell development using Nix. For now, here's the text of just the profiling section:

Nix makes this fairly easy. First, we add the following to a ~/.config/nixpkgs/config.nix:

{
  packageOverrides = super: let self = super.pkgs; in
  {
    profiledHaskellPackages = self.haskellPackages.override {
      overrides = self: super: {
        mkDerivation = args: super.mkDerivation (args // {
          enableLibraryProfiling = true;
        });
      };
    };
  };
}

Now in the project we want to profile, we create a new profiling-shell.nix:

let nixpkgs = import <nixpkgs> {};
    orig = nixpkgs.pkgs.profiledHaskellPackages.callPackage ./default.nix {};
in (nixpkgs.pkgs.haskell.lib.doBenchmark orig).env

Almost identical to our normal shell.nix, except for the usage of profiledHaskellPackages, which we just defined globally. Now, an invocation of nix-shell profiling-shell.nix will rebuild every dependency in our project with profiling enabled. The first time this is done it will take quite a long time. Luckily this doesn't corrupt our Nix store - a vanilla nix-shell does seem to present us with our regular dependencies again, without redownloading or rebuilding.

WARNING: A nix-collect-garbage -d will wipe away all the custom-built libs from our Nix Store, and we'd have to build them again if they're needed.

If we're writing a library, the closest executable on hand that we could profile would be our benchmark suite. To do that:

  • Add -prof and -fprof-auto to our benchmark's GHC options
  • Regenerate default.nix
  • Enter our profiling shell: nix-shell profiling-shell.nix
  • cabal configure --enable-library-profiling --enable-benchmarks
  • cabal build
  • dist/build/projname/projname-bench +RTS -p
  • Look at the produced projname-bench.prof file

Based on the results, we can make code changes, remove the profiling options, regenerate default.nix, and benchmark as usual in our normal Nix Shell.

Upvotes: 3

Wizek
Wizek

Reputation: 4993

With further snooping around in nix-repl and some helpful pointers from ElvishJerricco on #reflex-frp at FreeNode, I was able to construct this, which seems to work:

$ nix-shell -p "(haskell.packages.ghc821.extend (self: super: {mkDerivation = expr: super.mkDerivation (expr // { enableLibraryProfiling = true; });})).ghcWithPackages (p: with p; [text hspec lens])" -j4 --run 'ghc Main.hs -prof'

Upvotes: 3

Related Questions