Source code for pisa.utils.profiler
"""
Tools for profiling code
"""
from __future__ import absolute_import
from functools import wraps
from time import time
from line_profiler import LineProfiler
# Note this relative import (might be) necessary to avoid circular imports
from pisa.utils import log
__all__ = ['line_profile', 'test_line_profile', 'profile', 'test_profile']
__license__ = '''Copyright (c) 2014-2017, The IceCube Collaboration
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.'''
class Log(object):
"""Class to redirect output into our logging stream."""
def write(self, string):
string = string.rstrip('\n')
if string:
log.tprofile.debug(string)
def flush(self):
pass
TLOG = Log()
"""Instance of a global timing logger"""
LINE_PROFILER = LineProfiler()
"""Instance of a global LineProfiler"""
[docs]
def line_profile(func):
"""Use as `@line_profile` decorator for a function or class method to log
how long each line in the function/method takes to run.
Note that timings can be skewed by overhead from the line_profiler module,
which is used as the core timing mechanism for this function.
Parameters
----------
func : callable
Function or method to be profiled
Returns
-------
new_func : callable
New version of `func` that is callable just like `func` but that logs
the time spent in each line of code in `func`.
"""
@wraps(func)
def profiled_func(*args, **kwargs):
"""<< docstring will be inherited from wrapped `func` >>"""
try:
LINE_PROFILER.enable_by_count()
LINE_PROFILER.add_function(func)
return func(*args, **kwargs)
finally:
LINE_PROFILER.disable_by_count()
# Only print if it is the outermost function
if LINE_PROFILER.functions[0] == func:
LINE_PROFILER.print_stats(stream=TLOG)
return profiled_func
[docs]
def test_line_profile():
"""Unit tests for `line_profile` functional (decorator)"""
@line_profile
def get_number():
log.logging.trace('hello, i am get_number')
for x in range(500000):
yield x
@line_profile
def expensive_function():
log.logging.trace('hello, i am expensive fun')
for x in get_number():
_ = x ^ x ^ x
return 'some result!'
_ = expensive_function()
log.logging.info('<< ??? : test_line_profile >> Inspect above outputs')
[docs]
def profile(func):
"""Use as `@profile` decorator for a function or class method to log the
time that it takes to complete.
Parameters
----------
func : callable
Function or method to profile
Returns
-------
new_func : callable
New version of `func` that is callable just like `func` but that logs
the total time spent in `func`.
"""
@wraps(func)
def profiled_func(*args, **kwargs):
"""<< docstring will be inherited from wrapped `func` >>"""
try:
start_t = time()
return func(*args, **kwargs)
finally:
end_t = time()
log.tprofile.debug(
'module %s, function %s: %.4f ms',
func.__module__, func.__name__, (end_t - start_t)*1000
)
return profiled_func
[docs]
def test_profile():
"""Unit tests for `profile` functional (decorator)"""
@profile
def get_number():
log.logging.trace('hello, i am get_number')
for x in range(500000):
yield x
@profile
def expensive_function():
log.logging.trace('hello, i am expensive fun')
for x in get_number():
_ = x ^ x ^ x
return 'some result!'
_ = expensive_function()
log.logging.info('<< ??? : test_profile >> inspect above outputs')
if __name__ == '__main__':
log.set_verbosity(2)
test_line_profile()
test_profile()