Skip to content

Commit

Permalink
Add support to filter by function or thread
Browse files Browse the repository at this point in the history
  • Loading branch information
evanhempel committed Apr 3, 2015
1 parent 95ef13e commit fa4db0e
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 16 deletions.
39 changes: 32 additions & 7 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,46 @@ Enjoy the output:

**Filtering**

Sometimes you may want to exclude a method
Sometimes you may want to exclude a method
(for example in a server the method that waits for a new request)
or you may want to profile only a subset of your code
(a particular method and its children which are performance critical).
Since the output is stackframes each on a line by itself,
this can simply be done with a simple grep filter.

Exclude::
Filtering can be done by passing a python regular expression to the
``-f`` or ``--filter`` command line option
which will restrict output to only those lines which match.
Filtering is done against the entire line so you can filter by
function name, thread name, both, or even by
more complex filters such as function ABC calls DEF (``ABC.*DEF``).

grep -v waiting_method perf.log > removed_waiting.log
Alternatively since the output is stackframes each on a line by itself,
this can simply be done with a simple grep filter.::

Include::
Exclude:

grep function_name perf.log > filtered.log
grep -v waiting_method perf.log > removed_waiting.log

Include:

grep function_name perf.log > filtered.log

Then run the flamegraph.pl script against the filtered file.

.. figure:: docs/ycanta-full.png
:alt: yCanta webapp full profile of PDF export
:align: center

Full profile output of yCanta_ webapp PDF export. Most time is
spent in wait state and graph is not very helpful.

.. figure:: docs/ycanta-pdf.png
:alt: yCanta webapp filtered for PDF export format function.
:align: center

Filtered profile output of yCanta_ webapp PDF export. Filtering was on the
pdf format function so time spent in wait state has been excluded and the
graph is now helpful.

.. _FlameGraph: http://www.brendangregg.com/flamegraphs.html

.. _yCanta: https://github.com/yCanta/yCanta
Binary file added docs/ycanta-full.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/ycanta-pdf.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 19 additions & 9 deletions flamegraph/flamegraph.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import re
import sys
import time
import os.path
Expand Down Expand Up @@ -29,29 +30,34 @@ def create_flamegraph_entry(thread_id, frame, collapse_recursion=False):
for fn, ln, fun, text in traceback.extract_stack(frame)[1:])

class ProfileThread(threading.Thread):
def __init__(self, fd, interval, collapse_recursion=False):
def __init__(self, fd, interval, filter, collapse_recursion=False):
threading.Thread.__init__(self, name="FlameGraph Thread")
self.daemon = False

self._fd = fd
self._interval = interval
self._collapse_recursion = collapse_recursion
if filter is not None:
self._filter = re.compile(filter)
else:
self._filter = None

self._stats = collections.defaultdict(int)

self._keeprunning = True
self._stopevent = threading.Event()

def run(self):
my_thread = threading.current_thread().ident
while self._keeprunning:
for thread_id, frame in sys._current_frames().items():
if thread_id == my_thread:
continue
#traceback.print_stack(frame, file=self._fd)

entry = create_flamegraph_entry(thread_id, frame, self._collapse_recursion)
self._stats[entry] += 1
#self._fd.write('%f %s\n' % (time.clock(), entry))
if self._filter is None or self._filter.search(entry):
self._stats[entry] += 1

self._stopevent.wait(self._interval) # basically a sleep for x seconds unless someone asked to stop

for key in sorted(self._stats.keys()):
Expand All @@ -78,13 +84,17 @@ def main():
help='Save stats to file. If not specified default is to stderr')
parser.add_argument('-i', '--interval', type=float, nargs='?', default=0.001,
help='Interval in seconds for collection of stackframes (default: %(default)ss)')
parser.add_argument('-c', '--collapse-recursion', action='store_true',
parser.add_argument('-c', '--collapse-recursion', action='store_true',
help='Collapse simple recursion (function calls itself) into one stack frame in output')

parser.add_argument('-f', '--filter', type=str, nargs='?', default=None,
help='Regular expression to filter which stack frames are profiled. The
regular expression is run against each entire line of output so you can
filter by function or thread or both.')

args = parser.parse_args()
print(args)

thread = ProfileThread(args.output, args.interval, args.collapse_recursion)
thread = ProfileThread(args.output, args.interval, args.filter, args.collapse_recursion)

if not os.path.isfile(args.script_file):
parser.error('Script file does not exist: ' + args.script_file)
Expand All @@ -99,7 +109,7 @@ def main():

try:
# exec docs say globals and locals should be same dictionary else treated as class context
exec(script_compiled, script_globals, script_globals)
exec(script_compiled, script_globals, script_globals)
finally:
thread.stop()
thread.join()
Expand Down

0 comments on commit fa4db0e

Please sign in to comment.