forked from ionelmc/python-hunter
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtracer.py
More file actions
150 lines (126 loc) · 4.75 KB
/
tracer.py
File metadata and controls
150 lines (126 loc) · 4.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
from __future__ import absolute_import
import sys
import threading
import traceback
import hunter
from .event import Event
__all__ = 'Tracer',
class Tracer(object):
"""
Tracer object.
Args:
threading_support (bool): Hooks the tracer into ``threading.settrace`` as well if True.
"""
def __init__(self, threading_support=None, profiling_mode=False):
self._handler = None
self._previous = None
self._threading_previous = None
#: True if threading support was enabled. Should be considered read-only.
#:
#: :type: bool
self.threading_support = threading_support
#: True if profiling mode was enabled. Should be considered read-only.
#:
#: :type: bool
self.profiling_mode = profiling_mode
#: Tracing depth (increases on calls, decreases on returns)
#:
#: :type: int
self.depth = 0
#: A counter for total number of 'call' frames that this Tracer went through.
#:
#: :type: int
self.calls = 0
@property
def handler(self):
"""
The current predicate. Set via :func:`hunter.Tracer.trace`.
"""
return self._handler
@property
def previous(self):
"""
The previous tracer, if any (whatever ``sys.gettrace()`` returned prior to :func:`hunter.Tracer.trace`).
"""
return self._previous
def __repr__(self):
return '<hunter.tracer.Tracer at 0x%x: threading_support=%s, %s%s%s%s>' % (
id(self),
self.threading_support,
'<stopped>' if self._handler is None else 'handler=',
'' if self._handler is None else repr(self._handler),
'' if self._previous is None else ', previous=',
'' if self._previous is None else repr(self._previous),
)
def __call__(self, frame, kind, arg):
"""
The settrace function.
.. note::
This always returns self (drills down) - as opposed to only drilling down when ``predicate(event)`` is True
because it might match further inside.
"""
if self._handler is not None:
if kind == 'return' and self.depth > 0:
self.depth -= 1
try:
self._handler(Event(frame, kind, arg, self))
except Exception as exc:
traceback.print_exc(file=hunter._default_stream)
hunter._default_stream.write('Disabling tracer because handler {} failed ({!r}).\n\n'.format(
self._handler, exc))
self.stop()
return
if kind == 'call':
self.depth += 1
self.calls += 1
return self
def trace(self, predicate):
"""
Starts tracing with the given callable.
Args:
predicate (callable that accepts a single :obj:`~hunter.event.Event` argument):
Return:
self
"""
self._handler = predicate
if self.profiling_mode:
if self.threading_support is None or self.threading_support:
self._threading_previous = getattr(threading, '_profile_hook', None)
threading.setprofile(self)
self._previous = sys.getprofile()
sys.setprofile(self)
else:
if self.threading_support is None or self.threading_support:
self._threading_previous = getattr(threading, '_trace_hook', None)
threading.settrace(self)
self._previous = sys.gettrace()
sys.settrace(self)
return self
def stop(self):
"""
Stop tracing. Reinstalls the :attr:`~hunter.tracer.Tracer.previous` tracer.
"""
if self._handler is not None:
if self.profiling_mode:
sys.setprofile(self._previous)
self._handler = self._previous = None
if self.threading_support is None or self.threading_support:
threading.setprofile(self._threading_previous)
self._threading_previous = None
else:
sys.settrace(self._previous)
self._handler = self._previous = None
if self.threading_support is None or self.threading_support:
threading.settrace(self._threading_previous)
self._threading_previous = None
def __enter__(self):
"""
Does nothing. Users are expected to call :meth:`~hunter.tracer.Tracer.trace`.
Returns: self
"""
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""
Wrapper around :meth:`~hunter.tracer.Tracer.stop`. Does nothing with the arguments.
"""
self.stop()