-
Notifications
You must be signed in to change notification settings - Fork 0
/
tornadotiming.py
123 lines (106 loc) · 4.06 KB
/
tornadotiming.py
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
import time
import functools
import logging
import tornado.gen
import tornado.web
logger = logging.getLogger(__name__)
original_coroutine = tornado.gen.coroutine
original_RequestHandler_init = tornado.web.RequestHandler.__init__
def formatargs(args, kwargs):
ret = []
if args:
ret.append(", ".join(map(repr, args)))
if kwargs:
ret.append(["%r=%r" for k,v in kwargs.iteritems()])
return ", ".join(ret)
def timingwrapper(f):
"""Function decorator.
Measures how long it took a function to run. If it was longer than 1 second,
it issues a critical logging message.
"""
@functools.wraps(f)
def wrapper(*args, **kwargs):
_t = time.time()
result = f(*args, **kwargs)
_t = time.time() - _t
if _t > 1:
logger.critical(
"Slow function call took %f seconds on %s line %d %s(%s) returned %r",
_t,
f.func_code.co_filename,
f.func_code.co_firstlineno,
f.func_name,
formatargs(args, kwargs),
result
)
return result
return wrapper
def timingwrapper_gen(f):
"""Function decorator.
Measures how long it took the generator to yield every result.
If it was longer than 1 second, it issues a critical logging message.
"""
@functools.wraps(f)
def wrapper(*args, **kwargs):
nextValue = None
result = None
gen = f(*args, **kwargs)
while True:
_t = time.time()
if nextValue:
result = gen.send(nextValue)
else:
result = gen.next()
_t = time.time() - _t
# log calls slower than 1 second
if _t > 1:
logger.critical(
"Slow generator function iteration took %f seconds on %s line %d %s(%s) returned %r",
_t,
gen.gi_code.co_filename,
gen.gi_frame.f_lineno,
f.func_name,
formatargs(args, kwargs),
result
)
nextValue = yield result
return wrapper
@functools.wraps(original_RequestHandler_init)
def monkeypatched_RequestHandler_init(self, *args, **kwargs):
"""tornado.web.RequestHandler.__init__ functino replacement
I couldn't replace the entire class because it caused an issue with the
original constructor. When replacing RequestHandler with TimingRequestHandler,
the original RequestHandler code is expecting `object` when calling
super(RequestHandler, self), but is getting itself instead
The solution I found is to only monkeypatch the constructor
"""
original_RequestHandler_init(self, *args, **kwargs)
for method in self.SUPPORTED_METHODS:
# functio names are method in lower case
method = method.lower()
# if it has implementation for that function
if getattr(self, method, None):
func = getattr(self, method)
# only if it's not a generator function
if not func.func_code.co_flags & 0x20:
# wrap it!
setattr(self, method, timingwrapper(func))
class TimingRequestHandler(tornado.web.RequestHandler):
"""If you don't want to monkeypatch tornado.web.RequestHandler, use this
class instead in your code
"""
@functools.wraps(original_RequestHandler_init)
def __init__(self, *args, **kwargs):
monkeypatched_RequestHandler_init(self, *args, **kwargs)
@functools.wraps(tornado.gen.coroutine)
def coroutine(f):
"""One decorator for both tornado.gen.coroutine and timingwrapper_gen.
Use it if you don't want to monkeypatch tornado"""
return original_coroutine(timingwrapper_gen(f))
def monkeypatch():
"""Replaces the original tornado.gen.coroutine with our coroutine,
and makes RequestHandler slowiness aware
"""
tornado.gen.coroutine = coroutine
tornado.web.RequestHandler.__init__ = monkeypatched_RequestHandler_init
__all__ = ['monkeypatch', 'timingwrapper', 'coroutine', 'TimingRequestHandler']