Coverage for gramex\debug.py : 24%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1'''
2Debugging and profiling tools for Gramex
3'''
4import os
5import gc
6import six
7import sys
8import pprint
9import timeit
10import inspect
11import logging
12import functools
13from trace import Trace
14try:
15 import line_profiler
16except ImportError:
17 line_profiler = None
18from gramex.config import app_log
21def _indent(text, prefix, predicate=None):
22 '''Backport of textwrap.indent for Python 2.7'''
23 if predicate is None:
24 def predicate(line):
25 return line.strip()
27 def prefixed_lines():
28 for line in text.splitlines(True):
29 yield (prefix + line if predicate(line) else line)
30 return ''.join(prefixed_lines())
33def _caller():
34 '''_caller() returns the "file:function:line" of the calling function'''
35 parent = inspect.getouterframes(inspect.currentframe())[2]
36 return '[%s:%s:%d]' % (parent[1], parent[3], parent[2])
39class Timer(object):
40 '''
41 Find how long a code blocks takes to execute. Wrap any code block like this::
43 >>> from gramex.debug import Timer
44 >>> with Timer('optional message'):
45 >>> slow_running_code()
46 WARNING:gramex:1.000s optional message [<file>:<func>:line]
47 '''
48 def __init__(self, msg='', level=logging.WARNING):
49 self.msg = msg
50 self.level = logging.WARNING
52 def __enter__(self):
53 self.start = timeit.default_timer()
54 self.gc_old = gc.isenabled()
56 def __exit__(self, type, value, traceback):
57 end = timeit.default_timer()
58 if self.gc_old:
59 gc.enable()
60 app_log.log(self.level, '%0.3fs %s %s', end - self.start, self.msg, _caller())
63def _write(obj, prefix=None, stream=sys.stdout):
64 text = pprint.pformat(obj, indent=4)
65 if prefix is None:
66 stream.write(_indent(text, ' .. '))
67 else:
68 text = _indent(text, ' .. ' + ' ' * len(prefix) + ' ')
69 stream.write(' .. ' + prefix + ' = ' + text[7 + len(prefix):])
70 stream.write('\n')
73def print(*args, **kwargs): # noqa
74 '''
75 A replacement for the ``print`` function that also logs the (file, function,
76 line, msg) from where it is called. For example::
78 >>> from gramex.debug import print # import print function
79 >>> print('hello world') # It works like the print function
80 <file>(line).<function>: hello world
81 >>> print(x=1, y=2) # Use kwargs to print variable names
82 <file>(line).<function>:
83 .. x = 1
84 .. y = 2
86 It automatically pretty-prints complex variables.
87 '''
88 stream = kwargs.pop('stream', sys.stdout)
89 parent = inspect.getouterframes(inspect.currentframe())[1]
90 file, line, function = parent[1:4]
91 if len(args) == 1 and not kwargs:
92 stream.write('{}({}).{}: {}\n'.format(file, line, function, args[0]))
93 else:
94 stream.write('\n{}({}).{}:\n'.format(file, line, function))
95 for val in args:
96 _write(val, stream=stream)
97 for key, val in kwargs.items():
98 _write(val, key, stream=stream)
99 stream.write('\n')
102def trace(trace=True, exclude=None, **kwargs):
103 '''
104 Decorator to trace line execution. Usage::
106 @trace()
107 def method(...):
108 ...
110 When ``method()`` is called, every line of execution is traced.
111 '''
112 if exclude is None:
113 ignoredirs = (sys.prefix, )
114 elif isinstance(exclude, six.string_types):
115 ignoredirs = (sys.prefix, os.path.abspath(exclude))
116 elif isinstance(exclude, (list, tuple)):
117 ignoredirs = [sys.prefix] + [os.path.abspath(path) for path in exclude]
118 tracer = Trace(trace=trace, ignoredirs=ignoredirs, **kwargs)
120 def decorator(func):
121 @functools.wraps(func)
122 def wrapper(*args, **kwargs):
123 return tracer.runfunc(func, *args, **kwargs)
124 return wrapper
126 return decorator
129if line_profiler is None: 129 ↛ 130line 129 didn't jump to line 130, because the condition on line 129 was never true
130 def lineprofile(func):
131 app_log.warning('@lineprofile requires line_profiler module')
132 return func
133else:
134 def lineprofile(func):
135 '''
136 A decorator that prints the time taken for each line of a function every
137 time it is called. This example prints each line's performance::
139 >>> from gramex.debug import lineprofile
140 >>> @lineprofile
141 >>> def calc():
142 >>> ...
143 '''
144 profile = line_profiler.LineProfiler(func)
146 @functools.wraps(func)
147 def wrapper(*args, **kwds):
148 profile.enable_by_count()
149 try:
150 result = func(*args, **kwds)
151 finally:
152 profile.disable_by_count()
153 profile.print_stats(stripzeros=True)
154 return result
155 return wrapper
158# Windows
159if os.name == 'nt': 159 ↛ 172line 159 didn't jump to line 172, because the condition on line 159 was never false
160 import msvcrt
162 def getch():
163 '''
164 Return character if something was typed on the console, else None.
165 Used internally by Gramex on the command line.
166 '''
167 # TODO: flush the buffer
168 return msvcrt.getch() if msvcrt.kbhit() else None
170# Posix (Linux, OS X)
171else:
172 import sys
173 import termios
174 import atexit
175 from select import select
177 if sys.__stdin__.isatty():
178 def _init_non_blocking_terminal():
179 fd = sys.__stdin__.fileno()
180 old_term = termios.tcgetattr(fd)
181 # Support normal-terminal reset at exit
182 atexit.register(lambda: termios.tcsetattr(fd, termios.TCSAFLUSH, old_term))
184 # New terminal setting unbuffered
185 new_term = termios.tcgetattr(fd)
186 new_term[3] = (new_term[3] & ~termios.ICANON & ~termios.ECHO)
187 termios.tcsetattr(fd, termios.TCSAFLUSH, new_term)
189 def getch():
190 '''
191 Return character if something was typed on the console, else None.
192 TODO: flush the buffer
193 '''
194 dr, dw, de = select([sys.stdin], [], [], 0)
195 if dr != []:
196 return sys.stdin.read(1)
197 else:
198 return None
200 _init_non_blocking_terminal()
202 else:
203 def getch():
204 return None
207def _make_timer():
208 '''
209 ``timer("msg")`` prints the time elapsed since the last timer call::
211 >>> from gramex.debug import timer
212 >>> gramex.debug.timer('abc')
213 WARNING:gramex:7.583s abc [<file>:<function>:1] # Time since Gramex start
214 >>> gramex.debug.timer('def')
215 WARNING:gramex:3.707s def [<file>:<function>:1] # Time since last call
216 '''
217 class Context:
218 start = timeit.default_timer()
220 def timer(msg, level=logging.WARNING):
221 end = timeit.default_timer()
222 app_log.log(level, '%0.3fs %s %s', end - Context.start, msg, _caller())
223 Context.start = end
225 timer.__doc__ = _make_timer.__doc__
226 return timer
229timer = _make_timer()