[英]Why doesn't SIGVTALRM trigger inside time.sleep()?
I'm trying to use SIGVTALRM
to snapshot profile my Python code, but it doesn't seem to be firing inside blocking operations like time.sleep()
and socket operations. 我正在尝试使用
SIGVTALRM
快照配置文件我的Python代码,但它似乎没有触发阻塞操作,如time.sleep()
和套接字操作。
Why is that? 这是为什么? And is there any way to address that, so I can collect samples while I'm inside blocking operations?
有没有办法解决这个问题,所以我可以在阻止操作时收集样本?
I've also tried using ITIMER_PROF
/ SIGPROF
and ITIMER_REAL
/ SIGALRM
and both seem to produce similar results. 我也尝试过使用
ITIMER_PROF
/ SIGPROF
和ITIMER_REAL
/ SIGALRM
,两者似乎都产生了类似的结果。
The code I'm testing with follows, and the output is something like: 我正在测试的代码如下,输出类似于:
$ python profiler-test.py
<module>(__main__:1);test_sampling_profiler(__main__:53): 1
<module>(__main__:1);test_sampling_profiler(__main__:53);busyloop(__main__:48): 1509
Note that the timesleep
function isn't shown at all. 请注意,
timesleep
不显示时间timesleep
功能。
Test code: 测试代码:
import time
import signal
import collections
class SamplingProfiler(object):
def __init__(self, interval=0.001, logger=None):
self.interval = interval
self.running = False
self.counter = collections.Counter()
def _sample(self, signum, frame):
if not self.running:
return
stack = []
while frame is not None:
formatted_frame = "%s(%s:%s)" %(
frame.f_code.co_name,
frame.f_globals.get('__name__'),
frame.f_code.co_firstlineno,
)
stack.append(formatted_frame)
frame = frame.f_back
formatted_stack = ';'.join(reversed(stack))
self.counter[formatted_stack] += 1
signal.setitimer(signal.ITIMER_VIRTUAL, self.interval, 0)
def start(self):
if self.running:
return
signal.signal(signal.SIGVTALRM, self._sample)
signal.setitimer(signal.ITIMER_VIRTUAL, self.interval, 0)
self.running = True
def stop(self):
if not self.running:
return
self.running = False
signal.signal(signal.SIGVTALRM, signal.SIG_IGN)
def flush(self):
res = self.counter
self.counter = collections.Counter()
return res
def busyloop():
start = time.time()
while time.time() - start < 5:
pass
def timesleep():
time.sleep(5)
def test_sampling_profiler():
p = SamplingProfiler()
p.start()
busyloop()
timesleep()
p.stop()
print "\n".join("%s: %s" %x for x in sorted(p.flush().items()))
if __name__ == "__main__":
test_sampling_profiler()
不知道为什么time.sleep会以这种方式工作(它可以使用SIGALRM来知道何时恢复吗?)但是Popen.wait不会阻止信号,所以最糟糕的情况是你可以调用OS睡眠。
Another approach is to use a separate thread to trigger the sampling: 另一种方法是使用单独的线程来触发采样:
import sys
import threading
import time
import collections
class SamplingProfiler(object):
def __init__(self, interval=0.001):
self.interval = interval
self.running = False
self.counter = collections.Counter()
self.thread = threading.Thread(target=self._sample)
def _sample(self):
while self.running:
next_wakeup_time = time.time() + self.interval
for thread_id, frame in sys._current_frames().items():
if thread_id == self.thread.ident:
continue
stack = []
while frame is not None:
formatted_frame = "%s(%s:%s)" % (
frame.f_code.co_name,
frame.f_globals.get('__name__'),
frame.f_code.co_firstlineno,
)
stack.append(formatted_frame)
frame = frame.f_back
formatted_stack = ';'.join(reversed(stack))
self.counter[formatted_stack] += 1
sleep_time = next_wakeup_time - time.time()
if sleep_time > 0:
time.sleep(sleep_time)
def start(self):
if self.running:
return
self.running = True
self.thread.start()
def stop(self):
if not self.running:
return
self.running = False
def flush(self):
res = self.counter
self.counter = collections.Counter()
return res
def busyloop():
start = time.time()
while time.time() - start < 5:
pass
def timesleep():
time.sleep(5)
def test_sampling_profiler():
p = SamplingProfiler()
p.start()
busyloop()
timesleep()
p.stop()
print "\n".join("%s: %s" %x for x in sorted(p.flush().items()))
if __name__ == "__main__":
test_sampling_profiler()
When doing it this way the result is: 这样做的结果是:
$ python profiler-test.py
<module>(__main__:1);test_sampling_profiler(__main__:62);busyloop(__main__:54): 2875
<module>(__main__:1);test_sampling_profiler(__main__:62);start(__main__:37);start(threading:717);wait(threading:597);wait(threading:309): 1
<module>(__main__:1);test_sampling_profiler(__main__:62);timesleep(__main__:59): 4280
Still not totally fair, but better than no samples at all during sleep. 仍然不完全公平,但在睡眠期间完全没有样品。
The absence of SIGVTALRM during a sleep()
doesn't surprise me, since ITIMER_VIRTUAL " runs only when the process is executing. " (As an aside, CPython on non-Windows platforms implements time.sleep()
in terms of select()
.) sleep()
期间缺少SIGVTALRM并不让我感到惊讶,因为ITIMER_VIRTUAL “ 仅在进程执行时才运行。 ” (另外,非Windows平台上的CPython在select()
方面实现time.sleep()
select()
。)
With a plain SIGALRM, however, I expect a signal interruption and indeed I observe one: 然而,使用普通的SIGALRM,我预计会出现信号中断,实际上我会观察到一个:
<module>(__main__:1);test_sampling_profiler(__main__:62);busyloop(__main__:54): 4914
<module>(__main__:1);test_sampling_profiler(__main__:62);timesleep(__main__:59): 1
I changed the code somewhat, but you get the idea: 我有点改变了代码,但是你明白了:
class SamplingProfiler(object):
TimerSigs = {
signal.ITIMER_PROF : signal.SIGPROF,
signal.ITIMER_REAL : signal.SIGALRM,
signal.ITIMER_VIRTUAL : signal.SIGVTALRM,
}
def __init__(self, interval=0.001, timer = signal.ITIMER_REAL): # CHANGE
self.interval = interval
self.running = False
self.counter = collections.Counter()
self.timer = timer # CHANGE
self.signal = self.TimerSigs[timer] # CHANGE
....
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.