Other approaches taken have been the more Erlang style "let it fail" methodology which is fine for newer projects but represents a rewrite for most systems in practice and is thus far, far beyond profiling discussions.
gdb -p $PID
ctrl-c bt c ctrl-c bt c crl-c bt c ...
While your code may look all nice and sane, not have any glaring performance issues - what you may not realise is that you're using a framework that does things in unexpected ways.
The devs I work with are running into the reality of this at the moment - they blame the servers/network/database/whatever as being slow/shit/etc.
I go take a look and see that network traffic is at capacity. They've got some 'magic' caching framework that promises to speed things up by storing data in Redis. What's happening in the background though is that this caching framework is doing dumb things like asking for the same key over and over again - so we've got a multi-Gbit stream of the same handful of keys being asked for thousands of times a second.
These are the sorts of things profiling can tell you just by looking at the amount of calls to Redis.
Eventually we got them online to do a screen share, and sure enough it took over a minute to render the page (it was a map). They were using an 8 year old CPU.
Switching them from IE6 to Firefox fixed the issue.