├── .gitignore ├── LICENSE.md ├── README.md ├── requirements.txt └── timers.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | __pycache__ 21 | 22 | # Installer logs 23 | pip-log.txt 24 | 25 | # Unit test / coverage reports 26 | .coverage 27 | .tox 28 | nosetests.xml 29 | 30 | # Translations 31 | *.mo 32 | 33 | # Mr Developer 34 | .mr.developer.cfg 35 | .project 36 | .pydevproject 37 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Zapier Inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * Neither the name of the Zapier Inc. nor the 13 | names of its contributors may be used to endorse or promote products 14 | derived from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Profiling Python like a Boss 2 | ============================ 3 | 4 | A collection of code to accompany the blog post [Profiling Python like a Boss](http://zapier.com/engineering/profiling-python-boss/). -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | line-profiler==1.0b3 2 | -------------------------------------------------------------------------------- /timers.py: -------------------------------------------------------------------------------- 1 | import time 2 | import cProfile 3 | 4 | 5 | def timefunc(f): 6 | def f_timer(*args, **kwargs): 7 | start = time.time() 8 | result = f(*args, **kwargs) 9 | end = time.time() 10 | print f.__name__, 'took', end - start, 'time' 11 | return result 12 | return f_timer 13 | 14 | 15 | class timewith(): 16 | def __init__(self, name=''): 17 | self.name = name 18 | self.start = time.time() 19 | 20 | @property 21 | def elapsed(self): 22 | return time.time() - self.start 23 | 24 | def checkpoint(self, name=''): 25 | print '{timer} {checkpoint} took {elapsed} seconds'.format( 26 | timer=self.name, 27 | checkpoint=name, 28 | elapsed=self.elapsed, 29 | ).strip() 30 | 31 | def __enter__(self): 32 | return self 33 | 34 | def __exit__(self, type, value, traceback): 35 | self.checkpoint('finished') 36 | pass 37 | 38 | def do_cprofile(func): 39 | def profiled_func(*args, **kwargs): 40 | profile = cProfile.Profile() 41 | try: 42 | profile.enable() 43 | result = func(*args, **kwargs) 44 | profile.disable() 45 | return result 46 | finally: 47 | profile.print_stats() 48 | return profiled_func 49 | 50 | try: 51 | from line_profiler import LineProfiler 52 | 53 | def do_profile(follow=[]): 54 | """ 55 | Simply decorate an expensive function and optionally 56 | pass other functions to follow. For example: 57 | 58 | def get_number(): 59 | for x in xrange(5000000): 60 | yield x 61 | 62 | @do_profile(follow=[get_number]) 63 | def expensive_function(): 64 | for x in get_number(5000000): 65 | i = x ^ x ^ x 66 | return 'some result!' 67 | 68 | """ 69 | def inner(func): 70 | def profiled_func(*args, **kwargs): 71 | try: 72 | profiler = LineProfiler() 73 | profiler.add_function(func) 74 | for f in follow: 75 | profiler.add_function(f) 76 | profiler.enable_by_count() 77 | return func(*args, **kwargs) 78 | finally: 79 | profiler.print_stats() 80 | return profiled_func 81 | return inner 82 | 83 | except ImportError: 84 | def do_profile(follow=[]): 85 | "Helpful if you accidentally leave in production!" 86 | def inner(func): 87 | def nothing(*args, **kwargs): 88 | return func(*args, **kwargs) 89 | return nothing 90 | return inner 91 | 92 | def get_number(): 93 | for x in xrange(5000000): 94 | yield x 95 | 96 | def expensive_function(): 97 | for x in get_number(): 98 | i = x ^ x ^ x 99 | return 'some result!' 100 | 101 | 102 | if __name__ == '__main__': 103 | # the same as... 104 | # @timefunc 105 | # def expensive_function(): ... 106 | result = timefunc(expensive_function)() 107 | 108 | # as a context manager 109 | with timewith('fancy thing') as timer: 110 | expensive_function() 111 | timer.checkpoint('done with something') 112 | expensive_function() 113 | expensive_function() 114 | timer.checkpoint('done with something else') 115 | 116 | # or directly 117 | timer = timewith('fancy thing') 118 | expensive_function() 119 | timer.checkpoint('done with something') 120 | 121 | # built in profiler 122 | result = do_cprofile(expensive_function)() 123 | 124 | # prints a nice output of line-by-line execution times 125 | result = do_profile(follow=[get_number])(expensive_function)() 126 | --------------------------------------------------------------------------------