Tornado IOLoop性能分析:统计指定请求处理器的await执行次数
统计异步请求处理器交还IOLoop的次数
下面提供几种适配asyncio/Tornado的实现方案,可覆盖自研及第三方嵌套协程的统计需求:
方案1:全局Task跟踪(覆盖所有协程)
通过修改asyncio的Task工厂,注入统计逻辑,每次协程因await交还控制权时计数:
import asyncio from tornado.web import RequestHandler, Application # 全局计数变量,每个请求开始时重置 request_yield_count = 0 def counting_task_factory(loop, coro): async def coro_wrapper(): nonlocal request_yield_count try: while True: # 每次await触发协程暂停,计数+1 request_yield_count += 1 result = await coro.__anext__() yield result except StopAsyncIteration: pass return asyncio.Task(coro_wrapper(), loop=loop) class StatsHandler(RequestHandler): async def get(self): global request_yield_count request_yield_count = 0 # 重置请求专属计数 # 模拟业务逻辑:嵌套自研协程+第三方协程 await self.process_data() await self.call_third_party_api() self.write(f"本次请求交还IOLoop次数:{request_yield_count}") async def process_data(self): await asyncio.sleep(0.05) await self.parse_detail() async def parse_detail(self): await asyncio.sleep(0.02) async def call_third_party_api(self): # 模拟第三方协程调用 await asyncio.sleep(0.03) if __name__ == "__main__": loop = asyncio.get_event_loop() loop.set_task_factory(counting_task_factory) app = Application([(r"/stats", StatsHandler)]) app.listen(8888) loop.run_forever()
说明:该方案会统计所有Task的await触发的控制权交还,包括框架内部、自研及第三方协程。若需仅统计当前请求相关的协程,可在请求开始时记录当前Task ID,在计数逻辑中过滤非当前请求的Task。
方案2:协程装饰器(灵活统计指定协程)
如果不需要全局统计,可给目标协程添加装饰器,针对性跟踪其内部的控制权交还次数:
import asyncio from tornado.web import RequestHandler, Application def count_yields(func): async def wrapper(*args, **kwargs): yield_count = 0 coro_iter = func(*args, **kwargs) try: while True: yield_count += 1 result = await coro_iter.__anext__() yield result except StopAsyncIteration: pass print(f"协程 {func.__name__} 交还控制权 {yield_count} 次") return yield_count return wrapper class StatsHandler(RequestHandler): async def get(self): total_count = 0 # 统计自研嵌套协程 total_count += await self.process_data() # 统计第三方协程(通过包装实现) total_count += await self.wrap_third_party() self.write(f"本次请求总交还次数:{total_count}") @count_yields async def process_data(self): await asyncio.sleep(0.05) await self.parse_detail() @count_yields async def parse_detail(self): await asyncio.sleep(0.02) async def wrap_third_party(self): # 包装第三方协程以统计 @count_yields async def wrapped_api_call(): await asyncio.sleep(0.03) # 模拟第三方协程 return await wrapped_api_call() if __name__ == "__main__": app = Application([(r"/stats", StatsHandler)]) app.listen(8888) asyncio.get_event_loop().run_forever()
说明:该方案灵活性高,可按需选择要统计的协程,但需要手动包裹所有目标协程(包括第三方的)。
注意事项
- 统计的是每次
await导致的协程暂停,即交还执行权给IOLoop的次数,与需求完全匹配。 - 生产环境使用时需注意性能开销,调试完成后建议移除或关闭统计逻辑。
- 若需区分不同类型的交还(如IO等待、CPU密集型暂停),可在计数逻辑中结合
asyncio的事件钩子做进一步过滤。
内容的提问来源于stack exchange,提问作者Eric
相关产品推荐
相关产品推荐

