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

时间:2015-10-26 20:13:20

标签: python unix signals

我正在尝试使用SIGVTALRM对我的Python代码进行快照配置文件,但它似乎没有触发阻塞操作,如time.sleep()和套接字操作。

为什么?有没有办法解决这个问题,所以我可以在阻止操作时收集样本?

我也尝试过使用ITIMER_PROF / SIGPROFITIMER_REAL / SIGALRM,两者似乎都会产生类似的结果。

我正在测试的代码如下,输出类似于:

$ 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

请注意,timesleep功能根本不显示。

测试代码:

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()

3 个答案:

答案 0 :(得分:1)

不确定为什么time.sleep会以这种方式工作(它可以使用SIGALRM来知道何时恢复吗?)但是Popen.wait不会阻止信号,所以最糟糕的情况是你可以调用OS睡眠。

答案 1 :(得分:0)

另一种方法是使用单独的线程来触发采样:

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()

这样做的结果是:

$ 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

仍然不完全公平,但在睡眠期间完全没有样本。

答案 2 :(得分:0)

sleep()期间缺少SIGVTALRM并不会让我感到惊讶,因为ITIMER_VIRTUAL "runs only when the process is executing." (另外,非Windows平台上的CPython在time.sleep()方面实现了select()。)

然而,使用普通的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

我稍微改了一下代码,但是你明白了:

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
    ....