简体   繁体   English

为什么SIGVTALRM在time.sleep()中没有触发?

[英]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 / SIGPROFITIMER_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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM