aio-libs / aiodocker

Python Docker API client based on asyncio and aiohttp

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Lists not returned with __iter__ and not compatible with an async for loop

SelfhostedPro opened this issue · comments

Long story short

I'm attempting to gather stats on all of my running docker containers then sending them (via a websocket) back to my frontend. I can't get an async for loop going using docker.containers.list() as it returns a standard list without an aiter method.

  • Expected behaviour: Returns a list that is iterable in an async for loop
  • Actual behaviour: Returns a standard list that is blocking

How to reproduce (Ignore the fastapi/websocket stuff)

Running the following (without websocket/fastapi stuff) will only stream stats for a single container (if you remove async from the first for loop), not all of them at the same time.

@router.websocket("/stats")
async def dashboard(websocket: WebSocket):
    # auth_success = await websocket_auth(websocket=websocket)
    # if auth_success:
    await websocket.accept()
    dclient = aiodocker.Docker()
    async for app in await dclient.containers.list():
        async with aiodocker.Docker() as docker:
            cpu_total = 0.0
            cpu_system = 0.0
            cpu_percent = 0.0

            _name = app._container['Names'][0][1:]
            container: DockerContainer = await docker.containers.get(_name)
            stats = container.stats(stream=True)
            async for line in stats:
                mem_current = line["memory_stats"]["usage"]
                mem_total = line["memory_stats"]["limit"]

                try:
                    cpu_percent, cpu_system, cpu_total = await calculate_cpu_percent2(line, cpu_total, cpu_system)
                except KeyError as e:
                    print("error while getting new CPU stats: %r, falling back")
                    cpu_percent = await calculate_cpu_percent(line)

                full_stats = {
                    "name": _name,
                    "time": line['read'],
                    "cpu_percent": cpu_percent,
                    "mem_current": mem_current,
                    "mem_total": line["memory_stats"]["limit"],
                    "mem_percent": (mem_current / mem_total) * 100.0,
                }
                await websocket.send_text(json.dumps(full_stats))

Your environment

Running the latest version of Manjaro, Python 3.8.3, FastAPI. Not sure if anything in my env is going to be useful but if you need anything specifically feel free to ask.

Tried changing things around some but still am not having any luck:

@router.websocket("/stats")
async def dashboard(websocket: WebSocket):
    # auth_success = await websocket_auth(websocket=websocket)
    # if auth_success:
    await websocket.accept()
    tasks = []
    async with aiodocker.Docker() as docker: 
        containers = await docker.containers.list()
        for app in containers:
            _name = app._container['Names'][0][1:]
            container: DockerContainer = await docker.containers.get(_name)
            stats = container.stats(stream=True)
            tasks.append(process_container(_name, stats))
    await asyncio.gather(*tasks)

async def process_container(name, stats):
    cpu_total = 0.0
    cpu_system = 0.0
    cpu_percent = 0.0
    async for line in stats:
        mem_current = line["memory_stats"]["usage"]
        mem_total = line["memory_stats"]["limit"]

        try:
            cpu_percent, cpu_system, cpu_total = await calculate_cpu_percent2(line, cpu_total, cpu_system)
        except KeyError as e:
            print("error while getting new CPU stats: %r, falling back")
            cpu_percent = await calculate_cpu_percent(line)

        full_stats = {
            "name": _name,
            "time": line['read'],
            "cpu_percent": cpu_percent,
            "mem_current": mem_current,
            "mem_total": line["memory_stats"]["limit"],
            "mem_percent": (mem_current / mem_total) * 100.0,
        }
        await websocket.send_text(json.dumps(full_stats))
ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "/home/user/dev/Yacht-work/Yacht/backend/venv/lib/python3.8/site-packages/uvicorn/protocols/websockets/websockets_impl.py", line 154, in run_asgi
    result = await self.app(self.scope, self.asgi_receive, self.asgi_send)
  File "/home/user/dev/Yacht-work/Yacht/backend/venv/lib/python3.8/site-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__
    return await self.app(scope, receive, send)
  File "/home/user/dev/Yacht-work/Yacht/backend/venv/lib/python3.8/site-packages/fastapi/applications.py", line 180, in __call__
    await super().__call__(scope, receive, send)
  File "/home/user/dev/Yacht-work/Yacht/backend/venv/lib/python3.8/site-packages/starlette/applications.py", line 111, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/home/user/dev/Yacht-work/Yacht/backend/venv/lib/python3.8/site-packages/starlette/middleware/errors.py", line 146, in __call__
    await self.app(scope, receive, send)
  File "/home/user/dev/Yacht-work/Yacht/backend/venv/lib/python3.8/site-packages/starlette/exceptions.py", line 58, in __call__
    await self.app(scope, receive, send)
  File "/home/user/dev/Yacht-work/Yacht/backend/venv/lib/python3.8/site-packages/starlette/routing.py", line 566, in __call__
    await route.handle(scope, receive, send)
  File "/home/user/dev/Yacht-work/Yacht/backend/venv/lib/python3.8/site-packages/starlette/routing.py", line 283, in handle
    await self.app(scope, receive, send)
  File "/home/user/dev/Yacht-work/Yacht/backend/venv/lib/python3.8/site-packages/starlette/routing.py", line 57, in app
    await func(session)
  File "/home/user/dev/Yacht-work/Yacht/backend/venv/lib/python3.8/site-packages/fastapi/routing.py", line 242, in app
    await dependant.call(**values)
  File "./backend/api/routers/apps.py", line 133, in dashboard
    await asyncio.gather(*tasks)
  File "./backend/api/routers/apps.py", line 139, in process_container
    async for line in stats:
  File "/home/user/dev/Yacht-work/Yacht/backend/venv/lib/python3.8/site-packages/aiodocker/containers.py", line 299, in _stats_stream
    async with cm as response:
  File "/home/user/dev/Yacht-work/Yacht/backend/venv/lib/python3.8/site-packages/aiodocker/utils.py", line 309, in __aenter__
    resp = await self._coro
  File "/home/user/dev/Yacht-work/Yacht/backend/venv/lib/python3.8/site-packages/aiodocker/docker.py", line 241, in _do_query
    response = await self.session.request(
  File "/home/user/dev/Yacht-work/Yacht/backend/venv/lib/python3.8/site-packages/aiohttp/client.py", line 357, in _request
    raise RuntimeError('Session is closed')
RuntimeError: Session is closed

This fixed it (indentation error)

@router.websocket("/stats")
async def dashboard(websocket: WebSocket):
    # auth_success = await websocket_auth(websocket=websocket)
    # if auth_success:
    await websocket.accept()
    tasks = []
    async with aiodocker.Docker() as docker: 
        containers = await docker.containers.list()
        for app in containers:
            _name = app._container['Names'][0][1:]
            container: DockerContainer = await docker.containers.get(_name)
            stats = container.stats(stream=True)
            tasks.append(process_container(_name, stats, websocket))
        await asyncio.gather(*tasks)

async def process_container(name, stats, websocket):
    cpu_total = 0.0
    cpu_system = 0.0
    cpu_percent = 0.0
    async for line in stats:
        mem_current = line["memory_stats"]["usage"]
        mem_total = line["memory_stats"]["limit"]

        try:
            cpu_percent, cpu_system, cpu_total = await calculate_cpu_percent2(line, cpu_total, cpu_system)
        except KeyError as e:
            print("error while getting new CPU stats: %r, falling back")
            cpu_percent = await calculate_cpu_percent(line)

        full_stats = {
            "name": name,
            "time": line['read'],
            "cpu_percent": cpu_percent,
            "mem_current": mem_current,
            "mem_total": line["memory_stats"]["limit"],
            "mem_percent": (mem_current / mem_total) * 100.0,
        }
        await websocket.send_text(json.dumps(full_stats))