The GHC approach to profiling is very simple: annotate the expressions you consider "interesting" with cost centre labels (strings); so, for example, you might have:
f x y = let output1 = _scc_ "Pass1" ( pass1 x ) output2 = _scc_ "Pass2" ( pass2 output1 y ) output3 = _scc_ "Pass3" ( pass3 (output2 `zip` [1 .. ]) ) in concat output3
The costs of the evaluating the expressions bound to `output1', `output2' and `output3' will be attributed to the "cost centres" `Pass1', `Pass2' and `Pass3', respectively.
The costs of evaluating other expressions, e.g., `concat output4', will be inherited by the scope which referenced the function `f'.
You can put in cost-centres via `_scc_' constructs by hand, as in the example above. Perfectly cool. That's probably what you would do if your program divided into obvious "passes" or "phases", or whatever.
If your program is large or you have no clue what might be gobbling all the time, you can get GHC to mark all functions with `_scc_' constructs, automagically. Add an `-auto' compilation flag to the usual `-prof' option.
Once you start homing in on the Guilty Suspects, you may well switch from automagically-inserted cost-centres to a few well-chosen ones of your own.
To use profiling, you must compile and run with special options. (We usually forget the "run" magic! -- Do as we say, not as we do...) Details follow.
If you're serious about this profiling game, you should probably read one or more of the Sansom/Peyton Jones papers about the GHC profiling system. Just visit the Glasgow FP Web page...