plugins: containers: Refactor

1. update()
Two branches share the same logic for updating stats. Moreover, intermediate
values where used in nested loops: for filtering keys, and for extending stats.

This could be done at once with chain.from_iterable().

Therefore four helpers are introduced:
- add_engine_into_container
- is_key_in_container_and_not_hidden
- is_key_absent_in_container
- get_containers_from_updated_watcher

A profiling shows the stats build time is the same after the refactor,

$ sudo make run-webserver 2>&1 | grep primitive
         31631 function calls (31566 primitive calls) in 0.028 seconds
         23537 function calls (23487 primitive calls) in 0.025 seconds
         23559 function calls (23509 primitive calls) in 0.014 seconds
         23549 function calls (23499 primitive calls) in 0.024 seconds
         23549 function calls (23499 primitive calls) in 0.026 seconds
         23559 function calls (23509 primitive calls) in 0.016 seconds
         23559 function calls (23509 primitive calls) in 0.022 seconds
         23544 function calls (23494 primitive calls) in 0.015 seconds
         23549 function calls (23499 primitive calls) in 0.023 seconds
         23544 function calls (23494 primitive calls) in 0.018 seconds

[snapshot of top three calls]
         31881 function calls (31816 primitive calls) in 0.029 seconds

   Ordered by: cumulative time
   List reduced from 536 to 3 due to restriction <3>

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        3    0.000    0.000    0.029    0.010 __init__.py:250(<genexpr>)
        2    0.000    0.000    0.029    0.015 __init__.py:242(get_containers_from_updated_watcher)
        1    0.000    0.000    0.029    0.029 docker.py:248(update)

And before,

$ sudo make run-webserver 2>&1 | grep primitive
         31620 function calls (31555 primitive calls) in 0.021 seconds
         23526 function calls (23476 primitive calls) in 0.019 seconds
         23533 function calls (23483 primitive calls) in 0.024 seconds
         23538 function calls (23488 primitive calls) in 0.015 seconds
         23528 function calls (23478 primitive calls) in 0.023 seconds
         23528 function calls (23478 primitive calls) in 0.022 seconds
         23533 function calls (23483 primitive calls) in 0.016 seconds
         23538 function calls (23488 primitive calls) in 0.025 seconds
         23538 function calls (23488 primitive calls) in 0.029 seconds
         23538 function calls (23488 primitive calls) in 0.013 seconds

[snapshot of top three calls]
         31865 function calls (31800 primitive calls) in 0.024 seconds

   Ordered by: cumulative time
   List reduced from 531 to 3 due to restriction <3>

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.024    0.024 docker.py:248(update)
       13    0.000    0.000    0.022    0.002 decorators.py:38(inner)
       13    0.000    0.000    0.022    0.002 client.py:244(_get)

2. msg_curse()
The conditions are directly used in any().

3. exit()
Minimum supported version is Python 3.9; loops use iterators by default.

itervalues() was introduced for compatility with Python 2. Commit 76ea71f2
("Remove Python 2 in docs and README files") removed Python 2 support.

Part-of: #2801
Link: https://docs.python.org/3/library/itertools.html#itertools.chain.from_iterable
Link: https://docs.python.org/3/library/profile.html#profile.Profile
Signed-off-by: Ariel Otilibili <otilibil@eurecom.fr>
This commit is contained in:
Ariel Otilibili 2025-05-23 17:49:38 +02:00
parent e732ba37be
commit 445d20dcad
No known key found for this signature in database
GPG Key ID: 0A69BD35A1DA4DE6
1 changed files with 27 additions and 16 deletions

View File

@ -10,9 +10,10 @@
from copy import deepcopy
from functools import partial, reduce
from itertools import chain
from typing import Any, Optional
from glances.globals import iteritems, itervalues, nativestr
from glances.globals import nativestr
from glances.logger import logger
from glances.plugins.containers.engines import ContainersExtension
from glances.plugins.containers.engines.docker import DockerExtension, disable_plugin_docker
@ -174,7 +175,7 @@ class ContainersPlugin(GlancesPluginModel):
def exit(self) -> None:
"""Overwrite the exit method to close threads."""
for watcher in itervalues(self.watchers):
for watcher in self.watchers.values():
watcher.stop()
# Call the father class
@ -226,19 +227,30 @@ class ContainersPlugin(GlancesPluginModel):
if self.input_method != 'local':
return self.get_init_value()
# Update stats
stats = []
for engine, watcher in iteritems(self.watchers):
def is_key_in_container_and_not_hidden(container):
return (key := container.get('key')) in container and not self.is_hide(nativestr(container.get(key)))
def is_key_absent_in_container(container):
return 'key' not in container or container.get('key') not in container
def add_engine_into_container(engine, container):
return container | {"engine": engine}
def get_containers_from_updated_watcher(watcher):
_, containers = watcher.update(all_tag=self._all_tag())
containers_filtered = []
for container in containers:
container["engine"] = engine
if 'key' in container and container['key'] in container:
if not self.is_hide(nativestr(container[container['key']])):
containers_filtered.append(container)
else:
containers_filtered.append(container)
stats.extend(containers_filtered)
return containers
# Update stats
stats = list(
chain.from_iterable(
(
add_engine_into_container(engine, container)
for container in get_containers_from_updated_watcher(watcher)
if is_key_in_container_and_not_hidden(container) or is_key_absent_in_container(container)
)
for engine, watcher in self.watchers.items()
)
)
# Sort and update the stats
# @TODO: Have a look because sort did not work for the moment (need memory stats ?)
@ -488,8 +500,7 @@ class ContainersPlugin(GlancesPluginModel):
init = []
# Only process if stats exist (and non null) and display plugin enable...
conditions = [not self.stats, len(self.stats) == 0, self.is_disabled()]
if any(conditions):
if any([not self.stats, len(self.stats) == 0, self.is_disabled()]):
return init
# Build the string message