如何监视Python事件循环的繁忙程度?
我有一个异步应用程序,它通过aiohttp为请求提供服务,并执行其他异步任务(与数据库交互、处理消息、将请求本身作为HTTP客户机)。我想监控事件循环有多忙,可能是执行代码和等待如何监视Python事件循环的繁忙程度?,python,python-asyncio,Python,Python Asyncio,我有一个异步应用程序,它通过aiohttp为请求提供服务,并执行其他异步任务(与数据库交互、处理消息、将请求本身作为HTTP客户机)。我想监控事件循环有多忙,可能是执行代码和等待select完成所花费的时间 有没有办法用标准库事件循环或其他第三方循环(uvloop)来衡量这一点 具体地说,我想要一个持续的饱和度百分比度量,而不仅仅是一个似乎要解决的二进制“它忙吗?” 事件循环基本上是\u在中运行一次,而True则是循环 \u运行一次是否所有等待选择完成的内容 超时等待选择存储在之外的任何位置运行
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)
放入某个调用中,它将显示事件循环非常自由。我不太熟悉内核如何分割任务,但是如果一个进程获取了一些未缓存的内存,那么获取这些内存需要一段时间,而其他作业可能会同时运行?不知道这将如何显示为处理时间。。。