如何监视Python事件循环的繁忙程度?

时间:2019-01-16 17:58:57

标签: python python-asyncio

我有一个异步应用程序,该应用程序通过aiohttp服务请求并执行其他异步任务(与数据库进行交互,处理消息,将请求本身作为HTTP客户端进行处理)。我想监视事件循环的繁忙程度,也许是花费多少时间执行代码而不是等待select完成。

是否可以使用标准库事件循环或其他第三方循环(uvloop)来衡量?

具体来说,我想要一种持续的百分比饱和度度量,而不仅仅是this question似乎要解决的二进制“正忙”。

2 个答案:

答案 0 :(得分:5)

挖掘源代码显示如下:

  1. 事件循环基本上是_run_once循环中的executing while True
  2. _run_once完成including的所有工作,等待select完成
  3. timeout等待select isn't存储在_run_once之外的任何地方

没有什么能阻止我们在需要的时间重新实现_run_once

我们可以假设_run_once开始select之前(因为在_run_once以上没有耗时)和之后的时间来代替完整的select实现select is when _process_events开始了。

从言语到行动:

import asyncio


class MeasuredEventLoop(asyncio.SelectorEventLoop):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._total_time = 0
        self._select_time = 0

        self._before_select = None

    # TOTAL TIME:
    def run_forever(self):
        started = self.time()
        try:
            super().run_forever()
        finally:
            finished = self.time()
            self._total_time = finished - started

    # SELECT TIME:
    def _run_once(self):
        self._before_select = self.time()
        super()._run_once()

    def _process_events(self, *args, **kwargs):
        after_select = self.time()
        self._select_time += after_select - self._before_select
        super()._process_events(*args, **kwargs)

    # REPORT:
    def close(self, *args, **kwargs):
        super().close(*args, **kwargs)

        select = self._select_time
        cpu = self._total_time - self._select_time
        total = self._total_time

        print(f'Waited for select: {select:.{3}f}')
        print(f'Did other stuff: {cpu:.{3}f}')
        print(f'Total time: {total:.{3}f}')

让我们测试一下:

import time


async def main():
    await asyncio.sleep(1)  # simulate I/O, will be handled by selectors
    time.sleep(0.01)        # CPU job, executed here, outside event loop
    await asyncio.sleep(1)
    time.sleep(0.01)


loop = MeasuredEventLoop()
asyncio.set_event_loop(loop)
try:
    loop.run_until_complete(main())
finally:
    loop.close()

结果:

Waited for select: 2.000
Did other stuff: 0.032
Total time: 2.032

让我们针对具有真实I / O的代码进行测试:

import aiohttp


async def io_operation(delay):
    async with aiohttp.ClientSession() as session:
        async with session.get(f'http://httpbin.org/delay/{delay}') as resp:
            await resp.text()


async def main():
    await asyncio.gather(*[
        io_operation(delay=1),
        io_operation(delay=2),
        io_operation(delay=3),
    ])

结果:

Waited for select: 3.250
Did other stuff: 0.016
Total time: 3.266

答案 1 :(得分:0)

衡量CPU繁忙时间(适用于各种IO绑定情况,而不仅仅是异步)的一种方法是定期检查time.process_time()

例如,这是我要实现的方法,该方法偶尔会打印出循环“繁忙”的时间百分比,因为进程正在CPU上运行代码。

import asyncio
import time

async def monitor():
    while True:
        before = time.process_time()
        await asyncio.sleep(10)
        after = time.process_time()
        print('{:.2%}'.format((after - before) / 10))


async def busy_wait():
    while True:
        await asyncio.sleep(.001)

loop = asyncio.get_event_loop()
loop.create_task(monitor())
loop.create_task(busy_wait())
loop.run_forever()

monitor协程会测量每10秒流逝的处理时间,并将其打印为整个10秒的百分比。

busy_wait协程通过短时重复睡眠而在CPU上产生了人为的负载。通过增加睡眠时间,可以使处理时间比率任意降低,通过将睡眠时间减少为0,可以使处理时间接近壁钟时间的100%。

一个警告:这只会告诉您python事件循环在“运行python代码”上花费的时间(在CPU绑定的意义上)有多忙。如果有人通过调用time.sleep()(而不是asyncio.sleep()来阻止事件循环,则我的方法将显示该循环为空闲循环,但实际上它是“忙”的意思是被一个系统级睡眠。多数正确编写的异步代码都不应调用time.sleep()或阻塞IO,但如果这样做,则无法通过此类监视正确反映。

相关问题