Coverage for gramex\debug.py: 24%

# Coverage for gramex\debug.py : 24%

## 118 statements 34 run 84 missing 0 excluded 2 partial

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'''

2 Debugging and profiling tools for Gramex

3'''

4 import os

5 import gc

6 import six

7 import sys

8 import pprint

9 import timeit

10 import inspect

11 import logging

12 import functools

13 from trace import Trace

14 try:

15 import line_profiler

16 except ImportError:

17 line_profiler = None

18 from gramex.config import app_log

19

20

21 def _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()

26

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())

31

32

33 def _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])

37

38

39 class Timer(object):

40 '''

41 Find how long a code blocks takes to execute. Wrap any code block like this::

42

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

51

52 def __enter__(self):

53 self.start = timeit.default_timer()

54 self.gc_old = gc.isenabled()

55

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())

61

62

63 def _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')

71

72

73 def 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::

77

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

85

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')

100

101

102 def trace(trace=True, exclude=None, **kwargs):

103 '''

104 Decorator to trace line execution. Usage::

105

106 @trace()

107 def method(...):

108 ...

109

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)

119

120 def decorator(func):

121 @functools.wraps(func)

122 def wrapper(*args, **kwargs):

123 return tracer.runfunc(func, *args, **kwargs)

124 return wrapper

125

126 return decorator

127

128

129 if 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

133 else:

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::

138

139 >>> from gramex.debug import lineprofile

140 >>> @lineprofile

141 >>> def calc():

142 >>> ...

143 '''

144 profile = line_profiler.LineProfiler(func)

145

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

156

157

158# Windows

159 if os.name == 'nt': 159 ↛ 172line 159 didn't jump to line 172, because the condition on line 159 was never false

160 import msvcrt

161

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

169

170# Posix (Linux, OS X)

171 else:

172 import sys

173 import termios

174 import atexit

175 from select import select

176

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))

183

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)

188

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

199

200 _init_non_blocking_terminal()

201

202 else:

203 def getch():

204 return None

205

206

207 def _make_timer():

208 '''

209 ``timer("msg")`` prints the time elapsed since the last timer call::

210

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()

219

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

224

225 timer.__doc__ = _make_timer.__doc__

226 return timer

227

228

229 timer = _make_timer()