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

如何监视Python事件循环的繁忙程度?,python,python-asyncio,Python,Python Asyncio,我有一个异步应用程序,它通过aiohttp为请求提供服务,并执行其他异步任务(与数据库交互、处理消息、将请求本身作为HTTP客户机)。我想监控事件循环有多忙,可能是执行代码和等待select完成所花费的时间 有没有办法用标准库事件循环或其他第三方循环(uvloop)来衡量这一点 具体地说,我想要一个持续的饱和度百分比度量,而不仅仅是一个似乎要解决的二进制“它忙吗?” 事件循环基本上是\u在中运行一次,而True则是循环 \u运行一次是否所有等待选择完成的内容 超时等待选择存储在之外的任何位置运行

我有一个异步应用程序,它通过aiohttp为请求提供服务,并执行其他异步任务(与数据库交互、处理消息、将请求本身作为HTTP客户机)。我想监控事件循环有多忙,可能是执行代码和等待
select
完成所花费的时间

有没有办法用标准库事件循环或其他第三方循环(uvloop)来衡量这一点


具体地说,我想要一个持续的饱和度百分比度量,而不仅仅是一个似乎要解决的二进制“它忙吗?”

  • 事件循环基本上是
    \u在
    中运行一次
    ,而True则是
    循环
  • \u运行一次
    是否所有等待
    选择
    完成的内容
  • 超时
    等待
    选择
    存储在
    之外的任何位置
    运行一次
  • 没有什么能阻止我们重新实现
    \u运行一次
    ,这样我们就可以根据需要计时

    与复制整个
    \u run\u once
    实现不同,我们可以在
    选择
    之前计算时间,即
    \u run\u once
    启动时(因为在
    选择之前
    没有任何耗时的事情发生),在
    选择
    处理事件之后计算时间

    从言语到行动:

    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
    
    Waited for select: 3.250
    Did other stuff: 0.016
    Total time: 3.266
    

    让我们用真实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: 2.000
    Did other stuff: 0.032
    Total time: 2.032
    
    Waited for select: 3.250
    Did other stuff: 0.016
    Total time: 3.266
    

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

    例如,以下是我将如何实现某些东西,它偶尔会打印循环“繁忙”的时间百分比,因为进程正在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()
    
    监视器
    协同程序测量每10秒经过的处理时间,并将其打印为整个10秒的百分比

    busy\u wait
    corroutine通过短时间反复休眠,在CPU上产生人为负载。通过增加睡眠时间长度,您可以使处理时间比率任意减少,通过将睡眠时间长度减少到0,您可以使处理时间接近挂钟时间的100%


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

    这假设事件循环用于执行非常CPU密集的任务。在一个极端情况下,如果将
    time.sleep(x)
    放入某个调用中,它将显示事件循环非常自由。我不太熟悉内核如何分割任务,但是如果一个进程获取了一些未缓存的内存,那么获取这些内存需要一段时间,而其他作业可能会同时运行?不知道这将如何显示为处理时间。。。