| Total Complexity | 99 |
| Total Lines | 574 |
| Duplicated Lines | 4.01 % |
| Changes | 0 | ||
Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like glances.plugins.containers often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
| 1 | # |
||
| 2 | # This file is part of Glances. |
||
| 3 | # |
||
| 4 | # SPDX-FileCopyrightText: 2022 Nicolas Hennion <[email protected]> |
||
| 5 | # |
||
| 6 | # SPDX-License-Identifier: LGPL-3.0-only |
||
| 7 | # |
||
| 8 | |||
| 9 | """Containers plugin.""" |
||
| 10 | |||
| 11 | from copy import deepcopy |
||
| 12 | from functools import partial, reduce |
||
| 13 | from typing import Any, Optional |
||
| 14 | |||
| 15 | from glances.globals import iteritems, itervalues, nativestr |
||
| 16 | from glances.logger import logger |
||
| 17 | from glances.plugins.containers.engines import ContainersExtension |
||
| 18 | from glances.plugins.containers.engines.docker import DockerExtension, disable_plugin_docker |
||
| 19 | from glances.plugins.containers.engines.podman import PodmanExtension, disable_plugin_podman |
||
| 20 | from glances.plugins.plugin.model import GlancesPluginModel |
||
| 21 | from glances.processes import glances_processes |
||
| 22 | from glances.processes import sort_stats as sort_stats_processes |
||
| 23 | |||
| 24 | # Fields description |
||
| 25 | # description: human readable description |
||
| 26 | # short_name: shortname to use un UI |
||
| 27 | # unit: unit type |
||
| 28 | # rate: is it a rate ? If yes, // by time_since_update when displayed, |
||
| 29 | # min_symbol: Auto unit should be used if value > than 1 'X' (K, M, G)... |
||
| 30 | fields_description = { |
||
| 31 | 'name': { |
||
| 32 | 'description': 'Container name', |
||
| 33 | }, |
||
| 34 | 'id': { |
||
| 35 | 'description': 'Container ID', |
||
| 36 | }, |
||
| 37 | 'image': { |
||
| 38 | 'description': 'Container image', |
||
| 39 | }, |
||
| 40 | 'status': { |
||
| 41 | 'description': 'Container status', |
||
| 42 | }, |
||
| 43 | 'created': { |
||
| 44 | 'description': 'Container creation date', |
||
| 45 | }, |
||
| 46 | 'command': { |
||
| 47 | 'description': 'Container command', |
||
| 48 | }, |
||
| 49 | 'cpu_percent': { |
||
| 50 | 'description': 'Container CPU consumption', |
||
| 51 | 'unit': 'percent', |
||
| 52 | }, |
||
| 53 | 'memory_usage': { |
||
| 54 | 'description': 'Container memory usage', |
||
| 55 | 'unit': 'byte', |
||
| 56 | }, |
||
| 57 | 'io_rx': { |
||
| 58 | 'description': 'Container IO bytes read rate', |
||
| 59 | 'unit': 'bytepersecond', |
||
| 60 | }, |
||
| 61 | 'io_wx': { |
||
| 62 | 'description': 'Container IO bytes write rate', |
||
| 63 | 'unit': 'bytepersecond', |
||
| 64 | }, |
||
| 65 | 'network_rx': { |
||
| 66 | 'description': 'Container network RX bitrate', |
||
| 67 | 'unit': 'bitpersecond', |
||
| 68 | }, |
||
| 69 | 'network_tx': { |
||
| 70 | 'description': 'Container network TX bitrate', |
||
| 71 | 'unit': 'bitpersecond', |
||
| 72 | }, |
||
| 73 | 'uptime': { |
||
| 74 | 'description': 'Container uptime', |
||
| 75 | }, |
||
| 76 | 'engine': { |
||
| 77 | 'description': 'Container engine (Docker and Podman are currently supported)', |
||
| 78 | }, |
||
| 79 | 'pod_name': { |
||
| 80 | 'description': 'Pod name (only with Podman)', |
||
| 81 | }, |
||
| 82 | 'pod_id': { |
||
| 83 | 'description': 'Pod ID (only with Podman)', |
||
| 84 | }, |
||
| 85 | } |
||
| 86 | |||
| 87 | # Define the items history list (list of items to add to history) |
||
| 88 | # TODO: For the moment limited to the CPU. Had to change the graph exports |
||
| 89 | # method to display one graph per container. |
||
| 90 | # items_history_list = [{'name': 'cpu_percent', |
||
| 91 | # 'description': 'Container CPU consumption in %', |
||
| 92 | # 'y_unit': '%'}, |
||
| 93 | # {'name': 'memory_usage', |
||
| 94 | # 'description': 'Container memory usage in bytes', |
||
| 95 | # 'y_unit': 'B'}, |
||
| 96 | # {'name': 'network_rx', |
||
| 97 | # 'description': 'Container network RX bitrate in bits per second', |
||
| 98 | # 'y_unit': 'bps'}, |
||
| 99 | # {'name': 'network_tx', |
||
| 100 | # 'description': 'Container network TX bitrate in bits per second', |
||
| 101 | # 'y_unit': 'bps'}, |
||
| 102 | # {'name': 'io_r', |
||
| 103 | # 'description': 'Container IO bytes read per second', |
||
| 104 | # 'y_unit': 'Bps'}, |
||
| 105 | # {'name': 'io_w', |
||
| 106 | # 'description': 'Container IO bytes write per second', |
||
| 107 | # 'y_unit': 'Bps'}] |
||
| 108 | items_history_list = [{'name': 'cpu_percent', 'description': 'Container CPU consumption in %', 'y_unit': '%'}] |
||
| 109 | |||
| 110 | # List of key to remove before export |
||
| 111 | export_exclude_list = ['cpu', 'io', 'memory', 'network'] |
||
| 112 | |||
| 113 | # Sort dictionary for human |
||
| 114 | sort_for_human = { |
||
| 115 | 'io_counters': 'disk IO', |
||
| 116 | 'cpu_percent': 'CPU consumption', |
||
| 117 | 'memory_usage': 'memory consumption', |
||
| 118 | 'cpu_times': 'uptime', |
||
| 119 | 'name': 'container name', |
||
| 120 | None: 'None', |
||
| 121 | } |
||
| 122 | |||
| 123 | |||
| 124 | class PluginModel(GlancesPluginModel): |
||
| 125 | """Glances Docker plugin. |
||
| 126 | |||
| 127 | stats is a dict: {'version': {...}, 'containers': [{}, {}]} |
||
| 128 | """ |
||
| 129 | |||
| 130 | def __init__(self, args=None, config=None): |
||
| 131 | """Init the plugin.""" |
||
| 132 | super().__init__( |
||
| 133 | args=args, config=config, items_history_list=items_history_list, fields_description=fields_description |
||
| 134 | ) |
||
| 135 | |||
| 136 | # The plugin can be disabled using: args.disable_docker |
||
| 137 | self.args = args |
||
| 138 | |||
| 139 | # Default config keys |
||
| 140 | self.config = config |
||
| 141 | |||
| 142 | # We want to display the stat in the curse interface |
||
| 143 | self.display_curse = True |
||
| 144 | |||
| 145 | self.watchers: dict[str, ContainersExtension] = {} |
||
| 146 | |||
| 147 | # Init the Docker API |
||
| 148 | if not disable_plugin_docker: |
||
| 149 | self.watchers['docker'] = DockerExtension() |
||
| 150 | |||
| 151 | # Init the Podman API |
||
| 152 | if not disable_plugin_podman: |
||
| 153 | self.watchers['podman'] = PodmanExtension(podman_sock=self._podman_sock()) |
||
| 154 | |||
| 155 | # Sort key |
||
| 156 | self.sort_key = None |
||
| 157 | |||
| 158 | # Set the key's list be disabled in order to only display specific attribute in the container list |
||
| 159 | self.disable_stats = self.get_conf_value('disable_stats') |
||
| 160 | |||
| 161 | # Force a first update because we need two update to have the first stat |
||
| 162 | self.update() |
||
| 163 | self.refresh_timer.set(0) |
||
| 164 | |||
| 165 | def _podman_sock(self) -> str: |
||
| 166 | """Return the podman sock. |
||
| 167 | Could be desfined in the [docker] section thanks to the podman_sock option. |
||
| 168 | Default value: unix:///run/user/1000/podman/podman.sock |
||
| 169 | """ |
||
| 170 | conf_podman_sock = self.get_conf_value('podman_sock') |
||
| 171 | if not conf_podman_sock: |
||
| 172 | return "unix:///run/user/1000/podman/podman.sock" |
||
| 173 | return conf_podman_sock[0] |
||
| 174 | |||
| 175 | def exit(self) -> None: |
||
| 176 | """Overwrite the exit method to close threads.""" |
||
| 177 | for watcher in itervalues(self.watchers): |
||
| 178 | watcher.stop() |
||
| 179 | |||
| 180 | # Call the father class |
||
| 181 | super().exit() |
||
| 182 | |||
| 183 | def get_key(self) -> str: |
||
| 184 | """Return the key of the list.""" |
||
| 185 | return 'name' |
||
| 186 | |||
| 187 | def get_export(self) -> list[dict]: |
||
| 188 | """Overwrite the default export method. |
||
| 189 | |||
| 190 | - Only exports containers |
||
| 191 | - The key is the first container name |
||
| 192 | """ |
||
| 193 | try: |
||
| 194 | ret = deepcopy(self.stats) |
||
| 195 | except KeyError as e: |
||
| 196 | logger.debug(f"docker plugin - Docker export error {e}") |
||
| 197 | ret = [] |
||
| 198 | |||
| 199 | # Remove fields uses to compute rate |
||
| 200 | for container in ret: |
||
| 201 | for i in export_exclude_list: |
||
| 202 | container.pop(i) |
||
| 203 | |||
| 204 | return ret |
||
| 205 | |||
| 206 | def _all_tag(self) -> bool: |
||
| 207 | """Return the all tag of the Glances/Docker configuration file. |
||
| 208 | |||
| 209 | # By default, Glances only display running containers |
||
| 210 | # Set the following key to True to display all containers |
||
| 211 | all=True |
||
| 212 | """ |
||
| 213 | all_tag = self.get_conf_value('all') |
||
| 214 | if not all_tag: |
||
| 215 | return False |
||
| 216 | return all_tag[0].lower() == 'true' |
||
| 217 | |||
| 218 | @GlancesPluginModel._check_decorator |
||
| 219 | @GlancesPluginModel._log_result_decorator |
||
| 220 | def update(self) -> list[dict]: |
||
| 221 | """Update Docker and podman stats using the input method.""" |
||
| 222 | # Connection should be ok |
||
| 223 | if not self.watchers: |
||
| 224 | return self.get_init_value() |
||
| 225 | |||
| 226 | if self.input_method != 'local': |
||
| 227 | return self.get_init_value() |
||
| 228 | |||
| 229 | # Update stats |
||
| 230 | stats = [] |
||
| 231 | for engine, watcher in iteritems(self.watchers): |
||
| 232 | _, containers = watcher.update(all_tag=self._all_tag()) |
||
| 233 | containers_filtered = [] |
||
| 234 | for container in containers: |
||
| 235 | container["engine"] = engine |
||
| 236 | if 'key' in container and container['key'] in container: |
||
| 237 | if not self.is_hide(nativestr(container[container['key']])): |
||
| 238 | containers_filtered.append(container) |
||
| 239 | else: |
||
| 240 | containers_filtered.append(container) |
||
| 241 | stats.extend(containers_filtered) |
||
| 242 | |||
| 243 | # Sort and update the stats |
||
| 244 | # @TODO: Have a look because sort did not work for the moment (need memory stats ?) |
||
| 245 | self.sort_key, self.stats = sort_docker_stats(stats) |
||
| 246 | return self.stats |
||
| 247 | |||
| 248 | @staticmethod |
||
| 249 | def memory_usage_no_cache(mem: dict[str, float]) -> float: |
||
| 250 | """Return the 'real' memory usage by removing inactive_file to usage""" |
||
| 251 | # Ref: https://github.com/docker/docker-py/issues/3210 |
||
| 252 | return mem['usage'] - (mem['inactive_file'] if 'inactive_file' in mem else 0) |
||
| 253 | |||
| 254 | def update_views(self) -> bool: |
||
| 255 | """Update stats views.""" |
||
| 256 | # Call the father's method |
||
| 257 | super().update_views() |
||
| 258 | |||
| 259 | if not self.stats: |
||
| 260 | return False |
||
| 261 | |||
| 262 | # Add specifics information |
||
| 263 | # Alert |
||
| 264 | for i in self.stats: |
||
| 265 | # Init the views for the current container (key = container name) |
||
| 266 | self.views[i[self.get_key()]] = {'cpu': {}, 'mem': {}} |
||
| 267 | # CPU alert |
||
| 268 | if 'cpu' in i and 'total' in i['cpu']: |
||
| 269 | # Looking for specific CPU container threshold in the conf file |
||
| 270 | alert = self.get_alert(i['cpu']['total'], header=i['name'] + '_cpu', action_key=i['name']) |
||
| 271 | if alert == 'DEFAULT': |
||
| 272 | # Not found ? Get back to default CPU threshold value |
||
| 273 | alert = self.get_alert(i['cpu']['total'], header='cpu') |
||
| 274 | self.views[i[self.get_key()]]['cpu']['decoration'] = alert |
||
| 275 | # MEM alert |
||
| 276 | if 'memory' in i and 'usage' in i['memory']: |
||
| 277 | # Looking for specific MEM container threshold in the conf file |
||
| 278 | alert = self.get_alert( |
||
| 279 | self.memory_usage_no_cache(i['memory']), |
||
| 280 | maximum=i['memory']['limit'], |
||
| 281 | header=i['name'] + '_mem', |
||
| 282 | action_key=i['name'], |
||
| 283 | ) |
||
| 284 | if alert == 'DEFAULT': |
||
| 285 | # Not found ? Get back to default MEM threshold value |
||
| 286 | alert = self.get_alert( |
||
| 287 | self.memory_usage_no_cache(i['memory']), maximum=i['memory']['limit'], header='mem' |
||
| 288 | ) |
||
| 289 | self.views[i[self.get_key()]]['mem']['decoration'] = alert |
||
| 290 | |||
| 291 | # Display Engine and Pod name ? |
||
| 292 | show_pod_name = False |
||
| 293 | if any(ct.get("pod_name") for ct in self.stats): |
||
| 294 | show_pod_name = True |
||
| 295 | self.views['show_pod_name'] = show_pod_name |
||
| 296 | show_engine_name = False |
||
| 297 | if len({ct["engine"] for ct in self.stats}) > 1: |
||
| 298 | show_engine_name = True |
||
| 299 | self.views['show_engine_name'] = show_engine_name |
||
| 300 | |||
| 301 | return True |
||
| 302 | |||
| 303 | def build_title(self, ret): |
||
| 304 | msg = '{}'.format('CONTAINERS') |
||
| 305 | ret.append(self.curse_add_line(msg, "TITLE")) |
||
| 306 | if len(self.stats) > 1: |
||
| 307 | msg = f' {len(self.stats)}' |
||
| 308 | ret.append(self.curse_add_line(msg)) |
||
| 309 | msg = f' sorted by {sort_for_human[self.sort_key]}' |
||
| 310 | ret.append(self.curse_add_line(msg)) |
||
| 311 | if not self.views['show_engine_name']: |
||
| 312 | msg = f' (served by {self.stats[0].get("engine", "")})' |
||
| 313 | ret.append(self.curse_add_line(msg)) |
||
| 314 | ret.append(self.curse_new_line()) |
||
| 315 | return ret |
||
| 316 | |||
| 317 | def maybe_add_engine_name_or_pod_line(self, ret): |
||
| 318 | if self.views['show_engine_name']: |
||
| 319 | ret = self.add_msg_to_line(ret, ' {:{width}}'.format('Engine', width=6)) |
||
| 320 | if self.views['show_pod_name']: |
||
| 321 | ret = self.add_msg_to_line(ret, ' {:{width}}'.format('Pod', width=12)) |
||
| 322 | |||
| 323 | return ret |
||
| 324 | |||
| 325 | def maybe_add_engine_name_or_pod_name(self, ret, container): |
||
| 326 | ret.append(self.curse_new_line()) |
||
| 327 | if self.views['show_engine_name']: |
||
| 328 | ret.append(self.curse_add_line(' {:{width}}'.format(container["engine"], width=6))) |
||
| 329 | if self.views['show_pod_name']: |
||
| 330 | ret.append(self.curse_add_line(' {:{width}}'.format(container.get("pod_id", "-"), width=12))) |
||
| 331 | |||
| 332 | return ret |
||
| 333 | |||
| 334 | def build_container_name(self, name_max_width): |
||
| 335 | def build_for_this_max_length(ret, container): |
||
| 336 | ret.append( |
||
| 337 | self.curse_add_line(' {:{width}}'.format(container['name'][:name_max_width], width=name_max_width)) |
||
| 338 | ) |
||
| 339 | |||
| 340 | return ret |
||
| 341 | |||
| 342 | return build_for_this_max_length |
||
| 343 | |||
| 344 | def build_header(self, ret, name_max_width): |
||
| 345 | ret.append(self.curse_new_line()) |
||
| 346 | |||
| 347 | ret = self.maybe_add_engine_name_or_pod_line(ret) |
||
| 348 | |||
| 349 | if 'name' not in self.disable_stats: |
||
| 350 | msg = ' {:{width}}'.format('Name', width=name_max_width) |
||
| 351 | ret.append(self.curse_add_line(msg, 'SORT' if self.sort_key == 'name' else 'DEFAULT')) |
||
| 352 | |||
| 353 | msgs = [] |
||
| 354 | if 'status' not in self.disable_stats: |
||
| 355 | msgs.append('{:>10}'.format('Status')) |
||
| 356 | if 'uptime' not in self.disable_stats: |
||
| 357 | msgs.append('{:>10}'.format('Uptime')) |
||
| 358 | ret = reduce(self.add_msg_to_line, msgs, ret) |
||
| 359 | |||
| 360 | if 'cpu' not in self.disable_stats: |
||
| 361 | msg = '{:>6}'.format('CPU%') |
||
| 362 | ret.append(self.curse_add_line(msg, 'SORT' if self.sort_key == 'cpu_percent' else 'DEFAULT')) |
||
| 363 | |||
| 364 | msgs = [] |
||
| 365 | if 'mem' not in self.disable_stats: |
||
| 366 | msg = '{:>7}'.format('MEM') |
||
| 367 | ret.append(self.curse_add_line(msg, 'SORT' if self.sort_key == 'memory_usage' else 'DEFAULT')) |
||
| 368 | msgs.append('/{:<7}'.format('MAX')) |
||
| 369 | |||
| 370 | if 'diskio' not in self.disable_stats: |
||
| 371 | msgs.extend(['{:>7}'.format('IOR/s'), ' {:<7}'.format('IOW/s')]) |
||
| 372 | |||
| 373 | if 'networkio' not in self.disable_stats: |
||
| 374 | msgs.extend(['{:>7}'.format('Rx/s'), ' {:<7}'.format('Tx/s')]) |
||
| 375 | |||
| 376 | if 'command' not in self.disable_stats: |
||
| 377 | msgs.append(' {:8}'.format('Command')) |
||
| 378 | |||
| 379 | return reduce(self.add_msg_to_line, msgs, ret) |
||
| 380 | |||
| 381 | def add_msg_to_line(self, ret, msg): |
||
| 382 | ret.append(self.curse_add_line(msg)) |
||
| 383 | |||
| 384 | return ret |
||
| 385 | |||
| 386 | def get_max_of_container_names(self): |
||
| 387 | return min( |
||
| 388 | self.config.get_int_value('containers', 'max_name_size', default=20) if self.config is not None else 20, |
||
| 389 | len(max(self.stats, key=lambda x: len(x['name']))['name']), |
||
| 390 | ) |
||
| 391 | |||
| 392 | def build_status_name(self, ret, container): |
||
| 393 | status = self.container_alert(container['status']) |
||
| 394 | msg = '{:>10}'.format(container['status'][0:10]) |
||
| 395 | ret.append(self.curse_add_line(msg, status)) |
||
| 396 | |||
| 397 | return ret |
||
| 398 | |||
| 399 | def build_uptime_line(self, ret, container): |
||
| 400 | if container['uptime']: |
||
| 401 | msg = '{:>10}'.format(container['uptime']) |
||
| 402 | else: |
||
| 403 | msg = '{:>10}'.format('_') |
||
| 404 | |||
| 405 | return self.add_msg_to_line(ret, msg) |
||
| 406 | |||
| 407 | def build_cpu_line(self, ret, container): |
||
| 408 | try: |
||
| 409 | msg = '{:>6.1f}'.format(container['cpu']['total']) |
||
| 410 | except (KeyError, TypeError): |
||
| 411 | msg = '{:>6}'.format('_') |
||
| 412 | ret.append(self.curse_add_line(msg, self.get_views(item=container['name'], key='cpu', option='decoration'))) |
||
| 413 | |||
| 414 | return ret |
||
| 415 | |||
| 416 | def build_memory_line(self, ret, container): |
||
| 417 | try: |
||
| 418 | msg = '{:>7}'.format(self.auto_unit(self.memory_usage_no_cache(container['memory']))) |
||
| 419 | except KeyError: |
||
| 420 | msg = '{:>7}'.format('_') |
||
| 421 | ret.append(self.curse_add_line(msg, self.get_views(item=container['name'], key='mem', option='decoration'))) |
||
| 422 | try: |
||
| 423 | msg = '/{:<7}'.format(self.auto_unit(container['memory']['limit'])) |
||
| 424 | except (KeyError, TypeError): |
||
| 425 | msg = '/{:<7}'.format('_') |
||
| 426 | ret.append(self.curse_add_line(msg)) |
||
| 427 | |||
| 428 | return ret |
||
| 429 | |||
| 430 | def build_io_line(self, ret, container): |
||
| 431 | unit = 'B' |
||
| 432 | try: |
||
| 433 | value = self.auto_unit(int(container['io_rx'])) + unit |
||
| 434 | msg = f'{value:>7}' |
||
| 435 | except (KeyError, TypeError): |
||
| 436 | msg = '{:>7}'.format('_') |
||
| 437 | ret.append(self.curse_add_line(msg)) |
||
| 438 | try: |
||
| 439 | value = self.auto_unit(int(container['io_wx'])) + unit |
||
| 440 | msg = f' {value:<7}' |
||
| 441 | except (KeyError, TypeError): |
||
| 442 | msg = ' {:<7}'.format('_') |
||
| 443 | ret.append(self.curse_add_line(msg)) |
||
| 444 | |||
| 445 | return ret |
||
| 446 | |||
| 447 | def build_net_line(self, args): |
||
| 448 | def build_with_this_args(ret, container): |
||
| 449 | if args.byte: |
||
| 450 | # Bytes per second (for dummy) |
||
| 451 | to_bit = 1 |
||
| 452 | unit = '' |
||
| 453 | else: |
||
| 454 | # Bits per second (for real network administrator | Default) |
||
| 455 | to_bit = 8 |
||
| 456 | unit = 'b' |
||
| 457 | try: |
||
| 458 | value = self.auto_unit(int(container['network_rx'] * to_bit)) + unit |
||
| 459 | msg = f'{value:>7}' |
||
| 460 | except (KeyError, TypeError): |
||
| 461 | msg = '{:>7}'.format('_') |
||
| 462 | ret.append(self.curse_add_line(msg)) |
||
| 463 | try: |
||
| 464 | value = self.auto_unit(int(container['network_tx'] * to_bit)) + unit |
||
| 465 | msg = f' {value:<7}' |
||
| 466 | except (KeyError, TypeError): |
||
| 467 | msg = ' {:<7}'.format('_') |
||
| 468 | ret.append(self.curse_add_line(msg)) |
||
| 469 | |||
| 470 | return ret |
||
| 471 | |||
| 472 | return build_with_this_args |
||
| 473 | |||
| 474 | def build_cmd_line(self, ret, container): |
||
| 475 | if container['command'] is not None: |
||
| 476 | msg = ' {}'.format(container['command']) |
||
| 477 | else: |
||
| 478 | msg = ' {}'.format('_') |
||
| 479 | ret.append(self.curse_add_line(msg, splittable=True)) |
||
| 480 | |||
| 481 | return ret |
||
| 482 | |||
| 483 | def msg_curse(self, args=None, max_width: Optional[int] = None) -> list[str]: |
||
| 484 | """Return the dict to display in the curse interface.""" |
||
| 485 | # Init the return message |
||
| 486 | init = [] |
||
| 487 | |||
| 488 | # Only process if stats exist (and non null) and display plugin enable... |
||
| 489 | conditions = [not self.stats, len(self.stats) == 0, self.is_disabled()] |
||
| 490 | if any(conditions): |
||
| 491 | return init |
||
| 492 | |||
| 493 | # Build the string message |
||
| 494 | # Get the maximum containers name |
||
| 495 | # Max size is configurable. See feature request #1723. |
||
| 496 | name_max_width = self.get_max_of_container_names() |
||
| 497 | |||
| 498 | steps = [ |
||
| 499 | self.build_title, |
||
| 500 | partial(self.build_header, name_max_width=name_max_width), |
||
| 501 | self.build_data_line(name_max_width, args), |
||
| 502 | ] |
||
| 503 | |||
| 504 | return reduce(lambda ret, step: step(ret), steps, init) |
||
| 505 | |||
| 506 | def build_data_line(self, name_max_width, args): |
||
| 507 | def build_for_this_params(ret): |
||
| 508 | build_data_with_params = self.build_container_data(name_max_width, args) |
||
| 509 | return reduce(build_data_with_params, self.stats, ret) |
||
| 510 | |||
| 511 | return build_for_this_params |
||
| 512 | |||
| 513 | def build_container_data(self, name_max_width, args): |
||
| 514 | def build_with_this_params(ret, container): |
||
| 515 | steps = [self.maybe_add_engine_name_or_pod_name] |
||
| 516 | if 'name' not in self.disable_stats: |
||
| 517 | steps.append(self.build_container_name(name_max_width)) |
||
| 518 | if 'status' not in self.disable_stats: |
||
| 519 | steps.append(self.build_status_name) |
||
| 520 | if 'uptime' not in self.disable_stats: |
||
| 521 | steps.append(self.build_uptime_line) |
||
| 522 | if 'cpu' not in self.disable_stats: |
||
| 523 | steps.append(self.build_cpu_line) |
||
| 524 | if 'mem' not in self.disable_stats: |
||
| 525 | steps.append(self.build_memory_line) |
||
| 526 | if 'diskio' not in self.disable_stats: |
||
| 527 | steps.append(self.build_io_line) |
||
| 528 | if 'networkio' not in self.disable_stats: |
||
| 529 | steps.append(self.build_net_line(args)) |
||
| 530 | if 'command' not in self.disable_stats: |
||
| 531 | steps.append(self.build_cmd_line) |
||
| 532 | |||
| 533 | return reduce(lambda ret, step: step(ret, container), steps, ret) |
||
| 534 | |||
| 535 | return build_with_this_params |
||
| 536 | |||
| 537 | @staticmethod |
||
| 538 | def container_alert(status: str) -> str: |
||
| 539 | """Analyse the container status. |
||
| 540 | One of created, restarting, running, removing, paused, exited, or dead |
||
| 541 | """ |
||
| 542 | if status == 'running': |
||
| 543 | return 'OK' |
||
| 544 | if status == 'dead': |
||
| 545 | return 'ERROR' |
||
| 546 | if status in ['created', 'restarting', 'exited']: |
||
| 547 | return 'WARNING' |
||
| 548 | return 'INFO' |
||
| 549 | |||
| 550 | |||
| 551 | View Code Duplication | def sort_docker_stats(stats: list[dict[str, Any]]) -> tuple[str, list[dict[str, Any]]]: |
|
|
|
|||
| 552 | # Make VM sort related to process sort |
||
| 553 | if glances_processes.sort_key == 'memory_percent': |
||
| 554 | sort_by = 'memory_usage' |
||
| 555 | sort_by_secondary = 'cpu_percent' |
||
| 556 | elif glances_processes.sort_key == 'name': |
||
| 557 | sort_by = 'name' |
||
| 558 | sort_by_secondary = 'cpu_percent' |
||
| 559 | else: |
||
| 560 | sort_by = 'cpu_percent' |
||
| 561 | sort_by_secondary = 'memory_usage' |
||
| 562 | |||
| 563 | # Sort docker stats |
||
| 564 | sort_stats_processes( |
||
| 565 | stats, |
||
| 566 | sorted_by=sort_by, |
||
| 567 | sorted_by_secondary=sort_by_secondary, |
||
| 568 | # Reverse for all but name |
||
| 569 | reverse=glances_processes.sort_key != 'name', |
||
| 570 | ) |
||
| 571 | |||
| 572 | # Return the main sort key and the sorted stats |
||
| 573 | return sort_by, stats |
||
| 574 |