aboutsummaryrefslogtreecommitdiffstats
path: root/python/gevent/_tracer.py
blob: 98307ab585f00d18bd873d88da82231af9641069 (plain)
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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# Copyright (c) 2018 gevent. See LICENSE for details.
# cython: auto_pickle=False,embedsignature=True,always_allow_keywords=False
from __future__ import print_function, absolute_import, division

import sys
import traceback

from greenlet import settrace
from greenlet import getcurrent

from gevent.util import format_run_info

from gevent._compat import perf_counter
from gevent._util import gmctime


__all__ = [
    'GreenletTracer',
    'HubSwitchTracer',
    'MaxSwitchTracer',
]

# Recall these classes are cython compiled, so
# class variable declarations are bad.


class GreenletTracer(object):
    def __init__(self):
        # A counter, incremented by the greenlet trace function
        # we install on every greenlet switch. This is reset when the
        # periodic monitoring thread runs.

        self.greenlet_switch_counter = 0

        # The greenlet last switched to.
        self.active_greenlet = None

        # The trace function that was previously installed,
        # if any.
        # NOTE: Calling a class instance is cheaper than
        # calling a bound method (at least when compiled with cython)
        # even when it redirects to another function.
        prev_trace = settrace(self)

        self.previous_trace_function = prev_trace

        self._killed = False

    def kill(self):
        # Must be called in the monitored thread.
        if not self._killed:
            self._killed = True
            settrace(self.previous_trace_function)
            self.previous_trace_function = None

    def _trace(self, event, args):
        # This function runs in the thread we are monitoring.
        self.greenlet_switch_counter += 1
        if event in ('switch', 'throw'):
            # args is (origin, target). This is the only defined
            # case
            self.active_greenlet = args[1]
        else:
            self.active_greenlet = None
        if self.previous_trace_function is not None:
            self.previous_trace_function(event, args)

    def __call__(self, event, args):
        return self._trace(event, args)

    def did_block_hub(self, hub):
        # Check to see if we have blocked since the last call to this
        # method. Returns a true value if we blocked (not in the hub),
        # a false value if everything is fine.

        # This may be called in the same thread being traced or a
        # different thread; if a different thread, there is a race
        # condition with this being incremented in the thread we're
        # monitoring, but probably not often enough to lead to
        # annoying false positives.

        active_greenlet = self.active_greenlet
        did_switch = self.greenlet_switch_counter != 0
        self.greenlet_switch_counter = 0

        if did_switch or active_greenlet is None or active_greenlet is hub:
            # Either we switched, or nothing is running (we got a
            # trace event we don't know about or were requested to
            # ignore), or we spent the whole time in the hub, blocked
            # for IO. Nothing to report.
            return False
        return True, active_greenlet

    def ignore_current_greenlet_blocking(self):
        # Don't pay attention to the current greenlet.
        self.active_greenlet = None

    def monitor_current_greenlet_blocking(self):
        self.active_greenlet = getcurrent()

    def did_block_hub_report(self, hub, active_greenlet, format_kwargs):
        report = ['=' * 80,
                  '\n%s : Greenlet %s appears to be blocked' %
                  (gmctime(), active_greenlet)]
        report.append("    Reported by %s" % (self,))
        try:
            frame = sys._current_frames()[hub.thread_ident]
        except KeyError:
            # The thread holding the hub has died. Perhaps we shouldn't
            # even report this?
            stack = ["Unknown: No thread found for hub %r\n" % (hub,)]
        else:
            stack = traceback.format_stack(frame)
        report.append('Blocked Stack (for thread id %s):' % (hex(hub.thread_ident),))
        report.append(''.join(stack))
        report.append("Info:")
        report.extend(format_run_info(**format_kwargs))

        return report


class _HubTracer(GreenletTracer):
    def __init__(self, hub, max_blocking_time):
        GreenletTracer.__init__(self)
        self.max_blocking_time = max_blocking_time
        self.hub = hub

    def kill(self):
        self.hub = None
        GreenletTracer.kill(self)


class HubSwitchTracer(_HubTracer):
    # A greenlet tracer that records the last time we switched *into* the hub.

    def __init__(self, hub, max_blocking_time):
        _HubTracer.__init__(self, hub, max_blocking_time)
        self.last_entered_hub = 0

    def _trace(self, event, args):
        GreenletTracer._trace(self, event, args)
        if self.active_greenlet is self.hub:
            self.last_entered_hub = perf_counter()

    def did_block_hub(self, hub):
        if perf_counter() - self.last_entered_hub > self.max_blocking_time:
            return True, self.active_greenlet


class MaxSwitchTracer(_HubTracer):
    # A greenlet tracer that records the maximum time between switches,
    # not including time spent in the hub.

    def __init__(self, hub, max_blocking_time):
        _HubTracer.__init__(self, hub, max_blocking_time)
        self.last_switch = perf_counter()
        self.max_blocking = 0

    def _trace(self, event, args):
        old_active = self.active_greenlet
        GreenletTracer._trace(self, event, args)
        if old_active is not self.hub and old_active is not None:
            # If we're switching out of the hub, the blocking
            # time doesn't count.
            switched_at = perf_counter()
            self.max_blocking = max(self.max_blocking,
                                    switched_at - self.last_switch)

    def did_block_hub(self, hub):
        if self.max_blocking == 0:
            # We never switched. Check the time now
            self.max_blocking = perf_counter() - self.last_switch

        if self.max_blocking > self.max_blocking_time:
            return True, self.active_greenlet


from gevent._util import import_c_accel
import_c_accel(globals(), 'gevent.__tracer')