diff options
Diffstat (limited to 'glances/plugins/glances_docker.py')
-rw-r--r-- | glances/plugins/glances_docker.py | 764 |
1 files changed, 0 insertions, 764 deletions
diff --git a/glances/plugins/glances_docker.py b/glances/plugins/glances_docker.py deleted file mode 100644 index 65f8124e..00000000 --- a/glances/plugins/glances_docker.py +++ /dev/null @@ -1,764 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Glances. -# -# SPDX-FileCopyrightText: 2022 Nicolas Hennion <nicolas@nicolargo.com> -# -# SPDX-License-Identifier: LGPL-3.0-only -# - -"""Docker plugin.""" - -import os -import threading -import time -from copy import deepcopy - -from glances.compat import iterkeys, itervalues, nativestr, pretty_date -from glances.logger import logger -from glances.plugins.glances_plugin import GlancesPlugin -from glances.processes import sort_stats as sort_stats_processes, glances_processes -from glances.timer import getTimeSinceLastUpdate - -# Docker-py library (optional and Linux-only) -# https://github.com/docker/docker-py -try: - import docker - from dateutil import parser, tz -except Exception as e: - import_error_tag = True - # Display debug message if import KeyError - logger.warning("Error loading Docker deps Lib. Docker plugin is disabled ({})".format(e)) -else: - import_error_tag = False - -# Define the items history list (list of items to add to history) -# TODO: For the moment limited to the CPU. Had to change the graph exports -# method to display one graph per container. -# items_history_list = [{'name': 'cpu_percent', -# 'description': 'Container CPU consumption in %', -# 'y_unit': '%'}, -# {'name': 'memory_usage', -# 'description': 'Container memory usage in bytes', -# 'y_unit': 'B'}, -# {'name': 'network_rx', -# 'description': 'Container network RX bitrate in bits per second', -# 'y_unit': 'bps'}, -# {'name': 'network_tx', -# 'description': 'Container network TX bitrate in bits per second', -# 'y_unit': 'bps'}, -# {'name': 'io_r', -# 'description': 'Container IO bytes read per second', -# 'y_unit': 'Bps'}, -# {'name': 'io_w', -# 'description': 'Container IO bytes write per second', -# 'y_unit': 'Bps'}] -items_history_list = [{'name': 'cpu_percent', 'description': 'Container CPU consumption in %', 'y_unit': '%'}] - -# List of key to remove before export -export_exclude_list = ['cpu', 'io', 'memory', 'network'] - -# Sort dictionary for human -sort_for_human = { - 'io_counters': 'disk IO', - 'cpu_percent': 'CPU consumption', - 'memory_usage': 'memory consumption', - 'cpu_times': 'uptime', - 'name': 'container name', - None: 'None', -} - - -class Plugin(GlancesPlugin): - """Glances Docker plugin. - - stats is a dict: {'version': {...}, 'containers': [{}, {}]} - """ - - def __init__(self, args=None, config=None): - """Init the plugin.""" - super(Plugin, self).__init__(args=args, config=config, items_history_list=items_history_list) - - # The plugin can be disabled using: args.disable_docker - self.args = args - - # Default config keys - self.config = config - - # We want to display the stat in the curse interface - self.display_curse = True - - # Init the Docker API - self.docker_client = self.connect() - - # Dict of thread (to grab stats asynchronously, one thread is created per container) - # key: Container Id - # value: instance of ThreadDockerGrabber - self.thread_list = {} - - # Dict of Network stats (Storing previous network stats to compute Rx/s and Tx/s) - # key: Container Id - # value: network stats dict - self.network_old = {} - - # Dict of Disk IO stats (Storing previous disk_io stats to compute Rx/s and Tx/s) - # key: Container Id - # value: network stats dict - self.io_old = {} - - # Sort key - self.sort_key = None - - # Force a first update because we need two update to have the first stat - self.update() - self.refresh_timer.set(0) - - def exit(self): - """Overwrite the exit method to close threads.""" - for t in itervalues(self.thread_list): - t.stop() - # Call the father class - super(Plugin, self).exit() - - def get_key(self): - """Return the key of the list.""" - return 'name' - - def get_export(self): - """Overwrite the default export method. - - - Only exports containers - - The key is the first container name - """ - try: - ret = deepcopy(self.stats['containers']) - except KeyError as e: - logger.debug("docker plugin - Docker export error {}".format(e)) - ret = [] - - # Remove fields uses to compute rate - for container in ret: - for i in export_exclude_list: - container.pop(i) - - return ret - - def connect(self): - """Connect to the Docker server.""" - try: - # If the following line replace the next one, the issue #1878 - # is reproduced (Docker containers information missing with Docker 20.10.x) - # So, for the moment disable the timeout option - ret = docker.from_env() - except Exception as e: - logger.error("docker plugin - Can not connect to Docker ({})".format(e)) - ret = None - - return ret - - def _all_tag(self): - """Return the all tag of the Glances/Docker configuration file. - - # By default, Glances only display running containers - # Set the following key to True to display all containers - all=True - """ - all_tag = self.get_conf_value('all') - if len(all_tag) == 0: - return False - else: - return all_tag[0].lower() == 'true' - - @GlancesPlugin._check_decorator - @GlancesPlugin._log_result_decorator - def update(self): - """Update Docker stats using the input method.""" - # Init new stats - stats = self.get_init_value() - - # The Docker-py lib is mandatory and connection should be ok - if import_error_tag or self.docker_client is None: - return self.stats - - if self.input_method == 'local': - # Update stats - - # Docker version - # Example: { - # "KernelVersion": "3.16.4-tinycore64", - # "Arch": "amd64", - # "ApiVersion": "1.15", - # "Version": "1.3.0", - # "GitCommit": "c78088f", - # "Os": "linux", - # "GoVersion": "go1.3.3" - # } - try: - stats['version'] = self.docker_client.version() - except Exception as e: - # Correct issue#649 - logger.error("{} plugin - Cannot get Docker version ({})".format(self.plugin_name, e)) - # We may have lost connection remove version info - if 'version' in self.stats: - del self.stats['version'] - self.stats['containers'] = [] - return self.stats - - # Update current containers list - try: - # Issue #1152: Docker module doesn't export details about stopped containers - # The Docker/all key of the configuration file should be set to True - containers = self.docker_client.containers.list(all=self._all_tag()) or [] - except Exception as e: - logger.error("{} plugin - Cannot get containers list ({})".format(self.plugin_name, e)) - # We may have lost connection empty the containers list. - self.stats['containers'] = [] - return self.stats - - # Start new thread for new container - for container in containers: - if container.id not in self.thread_list: - # Thread did not exist in the internal dict - # Create it, add it to the internal dict and start it - logger.debug( - "{} plugin - Create thread for container {}".format(self.plugin_name, container.id[:12]) - ) - t = ThreadDockerGrabber(container) - self.thread_list[container.id] = t - t.start() - - # Stop threads for non-existing containers - absent_containers = set(iterkeys(self.thread_list)) - set([c.id for c in containers]) - for container_id in absent_containers: - # Stop the thread - logger.debug("{} plugin - Stop thread for old container {}".format(self.plugin_name, container_id[:12])) - self.thread_list[container_id].stop() - # Delete the item from the dict - del self.thread_list[container_id] - - # Get stats for all containers - stats['containers'] = [] - for container in containers: - # Shall we display the stats ? - if not self.is_display(nativestr(container.name)): - continue - - # Init the stats for the current container - container_stats = {} - # The key is the container name and not the Id - container_stats['key'] = self.get_key() - # Export name - container_stats['name'] = nativestr(container.name) - # Container Id - container_stats['Id'] = container.id - # Container Image - container_stats['Image'] = container.image.tags - # Global stats (from attrs) - # Container Status - container_stats['Status'] = container.attrs['State']['Status'] - # Container Command (see #1912) - container_stats['Command'] = [] - if container.attrs['Config'].get('Entrypoint', None): - container_stats['Command'].extend(container.attrs['Config'].get('Entrypoint', [])) - if container.attrs['Config'].get('Cmd', None): - container_stats['Command'].extend(container.attrs['Config'].get('Cmd', [])) - if not container_stats['Command']: - container_stats['Command'] = None - # Standards stats - # See https://docs.docker.com/engine/api/v1.41/#operation/ContainerStats - # Be aware that the API can change... (example see issue #1857) - if container_stats['Status'] in ('running', 'paused'): - # CPU - container_stats['cpu'] = self.get_docker_cpu(container.id, self.thread_list[container.id].stats) - container_stats['cpu_percent'] = container_stats['cpu'].get('total', None) - # MEM - container_stats['memory'] = self.get_docker_memory( - container.id, self.thread_list[container.id].stats - ) - container_stats['memory_usage'] = container_stats['memory'].get('usage', None) - if container_stats['memory'].get('cache', None) is not None: - container_stats['memory_usage'] -= container_stats['memory']['cache'] - # IO - container_stats['io'] = self.get_docker_io(container.id, self.thread_list[container.id].stats) - container_stats['io_r'] = container_stats['io'].get('ior', None) - container_stats['io_w'] = container_stats['io'].get('iow', None) - # NET - container_stats['network'] = self.get_docker_network( - container.id, self.thread_list[container.id].stats - ) - container_stats['network_rx'] = container_stats['network'].get('rx', None) - container_stats['network_tx'] = container_stats['network'].get('tx', None) - # Uptime - container_stats['Uptime'] = pretty_date( - # parser.parse(container.attrs['State']['StartedAt']).replace(tzinfo=None) - parser.parse(container.attrs['State']['StartedAt']) - .astimezone(tz.tzlocal()) - .replace(tzinfo=None) - ) - else: - container_stats['cpu'] = {} - container_stats['cpu_percent'] = None - container_stats['memory'] = {} - container_stats['memory_percent'] = None - container_stats['io'] = {} - container_stats['io_r'] = None - container_stats['io_w'] = None - container_stats['network'] = {} - container_stats['network_rx'] = None - container_stats['network_tx'] = None - container_stats['Uptime'] = None - # Add current container stats to the stats list - stats['containers'].append(container_stats) - - elif self.input_method == 'snmp': - # Update stats using SNMP - # Not available - pass - - # Sort and update the stats - self.sort_key, self.stats = sort_docker_stats(stats) - - return self.stats - - def get_docker_cpu(self, container_id, all_stats): - """Return the container CPU usage. - - Input: id is the full container id - all_stats is the output of the stats method of the Docker API - Output: a dict {'total': 1.49} - """ - cpu_stats = {'total': 0.0} - - try: - cpu = { - 'system': all_stats['cpu_stats']['system_cpu_usage'], - 'total': all_stats['cpu_stats']['cpu_usage']['total_usage'], - } - precpu = { - 'system': all_stats['precpu_stats']['system_cpu_usage'], - 'total': all_stats['precpu_stats']['cpu_usage']['total_usage'], - } - # Issue #1857 - # If either precpu_stats.online_cpus or cpu_stats.online_cpus is nil - # then for compatibility with older daemons the length of - # the corresponding cpu_usage.percpu_usage array should be used. - cpu['nb_core'] = all_stats['cpu_stats'].get('online_cpus', None) - if cpu['nb_core'] is None: - cpu['nb_core'] = len(all_stats['cpu_stats']['cpu_usage']['percpu_usage'] or []) - except KeyError as e: - logger.debug("docker plugin - Cannot grab CPU usage for container {} ({})".format(container_id, e)) - logger.debug(all_stats) - else: - try: - cpu_delta = cpu['total'] - precpu['total'] - system_cpu_delta = cpu['system'] - precpu['system'] - # CPU usage % = (cpu_delta / system_cpu_delta) * number_cpus * 100.0 - cpu_stats['total'] = (cpu_delta / system_cpu_delta) * cpu['nb_core'] * 100.0 - except TypeError as e: - logger.debug("docker plugin - Cannot compute CPU usage for container {} ({})".format(container_id, e)) - logger.debug(all_stats) - - # Return the stats - return cpu_stats - - def get_docker_memory(self, container_id, all_stats): - """Return the container MEMORY. - - Input: id is the full container id - all_stats is the output of the stats method of the Docker API - Output: a dict {'rss': 1015808, 'cache': 356352, 'usage': ..., 'max_usage': ...} - """ - memory_stats = {} - # Read the stats - try: - # Mandatory fields - memory_stats['usage'] = all_stats['memory_stats']['usage'] - memory_stats['limit'] = all_stats['memory_stats']['limit'] - # Issue #1857 - # Some stats are not always available in ['memory_stats']['stats'] - if 'rss' in all_stats['memory_stats']['stats']: - memory_stats['rss'] = all_stats['memory_stats']['stats']['rss'] - elif 'total_rss' in all_stats['memory_stats']['stats']: - memory_stats['rss'] = all_stats['memory_stats']['stats']['total_rss'] - else: - memory_stats['rss'] = None - memory_stats['cache'] = all_stats['memory_stats']['stats'].get('cache', None) - memory_stats['max_usage'] = all_stats['memory_stats'].get('max_usage', None) - except (KeyError, TypeError) as e: - # all_stats do not have MEM information - logger.debug("docker plugin - Cannot grab MEM usage for container {} ({})".format(container_id, e)) - logger.debug(all_stats) - # Return the stats - return memory_stats - - def get_docker_network(self, container_id, all_stats): - """Return the container network usage using the Docker API (v1.0 or higher). - - Input: id is the full container id - Output: a dict {'time_since_update': 3000, 'rx': 10, 'tx': 65}. - with: - time_since_update: number of seconds elapsed between the latest grab - rx: Number of bytes received - tx: Number of bytes transmitted - """ - # Init the returned dict - network_new = {} - - # Read the rx/tx stats (in bytes) - try: - net_stats = all_stats["networks"] - except KeyError as e: - # all_stats do not have NETWORK information - logger.debug("docker plugin - Cannot grab NET usage for container {} ({})".format(container_id, e)) - logger.debug(all_stats) - # No fallback available... - return network_new - - # Previous network interface stats are stored in the self.network_old variable - # By storing time data we enable Rx/s and Tx/s calculations in the XML/RPC API, which would otherwise - # be overly difficult work for users of the API - try: - network_new['cumulative_rx'] = net_stats["eth0"]["rx_bytes"] - network_new['cumulative_tx'] = net_stats["eth0"]["tx_bytes"] - except KeyError as e: - # all_stats do not have INTERFACE information - logger.debug( - "docker plugin - Cannot grab network interface usage for container {} ({})".format(container_id, e) - ) - logger.debug(all_stats) - else: - network_new['time_since_update'] = getTimeSinceLastUpdate('docker_net_{}'.format(container_id)) - if container_id in self.network_old: - network_new['rx'] = network_new['cumulative_rx'] - self.network_old[container_id]['cumulative_rx'] - network_new['tx'] = network_new['cumulative_tx'] - self.network_old[container_id]['cumulative_tx'] - - # Save stats to compute next bitrate - self.network_old[container_id] = network_new - - # Return the stats - return network_new - - def get_docker_io(self, container_id, all_stats): - """Return the container IO usage using the Docker API (v1.0 or higher). - - Input: id is the full container id - Output: a dict {'time_since_update': 3000, 'ior': 10, 'iow': 65}. - with: - time_since_update: number of seconds elapsed between the latest grab - ior: Number of bytes read - iow: Number of bytes written - """ - # Init the returned dict - io_new = {} - - # Read the ior/iow stats (in bytes) - try: - io_stats = all_stats["blkio_stats"] - except KeyError as e: - # all_stats do not have io information - logger.debug("docker plugin - Cannot grab block IO usage for container {} ({})".format(container_id, e)) - logger.debug(all_stats) - # No fallback available... - return io_new - - # Previous io interface stats are stored in the self.io_old variable - # By storing time data we enable IoR/s and IoW/s calculations in the - # XML/RPC API, which would otherwise be overly difficult work - # for users of the API - try: - io_service_bytes_recursive = io_stats['io_service_bytes_recursive'] - - # Read IOR and IOW value in the structure list of dict - io_new['cumulative_ior'] = [i for i in io_service_bytes_recursive if i['op'].lower() == 'read'][0]['value'] - io_new['cumulative_iow'] = [i for i in io_service_bytes_recursive if i['op'].lower() == 'write'][0]['value'] - except (TypeError, IndexError, KeyError, AttributeError) as e: - # all_stats do not have io information - logger.debug("docker plugin - Cannot grab block IO usage for container {} ({})".format(container_id, e)) - else: - io_new['time_since_update'] = getTimeSinceLastUpdate('docker_io_{}'.format(container_id)) - if container_id in self.io_old: - io_new['ior'] = io_new['cumulative_ior'] - self.io_old[container_id]['cumulative_ior'] - io_new['iow'] = io_new['cumulative_iow'] - self.io_old[container_id]['cumulative_iow'] - - # Save stats to compute next bitrate - self.io_old[container_id] = io_new - - # Return the stats - return io_new - - def get_user_ticks(self): - """Return the user ticks by reading the environment variable.""" - return os.sysconf(os.sysconf_names['SC_CLK_TCK']) - - def get_stats_action(self): - """Return stats for the action. - - Docker will return self.stats['containers'] - """ - return self.stats['containers'] - - def update_views(self): - """Update stats views.""" - # Call the father's method - super(Plugin, self).update_views() - - if 'containers' not in self.stats: - return False - - # Add specifics information - # Alert - for i in self.stats['containers']: - # Init the views for the current container (key = container name) - self.views[i[self.get_key()]] = {'cpu': {}, 'mem': {}} - # CPU alert - if 'cpu' in i and 'total' in i['cpu']: - # Looking for specific CPU container threshold in the conf file - alert = self.get_alert(i['cpu']['total'], header=i['name'] + '_cpu', action_key=i['name']) - if alert == 'DEFAULT': - # Not found ? Get back to default CPU threshold value - alert = self.get_alert(i['cpu']['total'], header='cpu') - self.views[i[self.get_key()]]['cpu']['decoration'] = alert - # MEM alert - if 'memory' in i and 'usage' in i['memory']: - # Looking for specific MEM container threshold in the conf file - alert = self.get_alert( - i['memory']['usage'], maximum=i['memory']['limit'], header=i['name'] + '_mem', action_key=i['name'] - ) - if alert == 'DEFAULT': - # Not found ? Get back to default MEM threshold value - alert = self.get_alert(i['memory']['usage'], maximum=i['memory']['limit'], header='mem') - self.views[i[self.get_key()]]['mem']['decoration'] = alert - - return True - - def msg_curse(self, args=None, max_width=None): - """Return the dict to display in the curse interface.""" - # Init the return message - ret = [] - - # Only process if stats exist (and non null) and display plugin enable... - if not self.stats or 'containers' not in self.stats or len(self.stats['containers']) == 0 or self.is_disabled(): - return ret - - # Build the string message - # Title - msg = '{}'.format('CONTAINERS') - ret.append(self.curse_add_line(msg, "TITLE")) - msg = ' {}'.format(len(self.stats['containers'])) - ret.append(self.curse_add_line(msg)) - msg = ' sorted by {}'.format(sort_for_human[self.sort_key]) - ret.append(self.curse_add_line(msg)) - # msg = ' (served by Docker {})'.format(self.stats['version']["Version"]) - # ret.append(self.curse_add_line(msg)) - ret.append(self.curse_new_line()) - # Header - ret.append(self.curse_new_line()) - # Get the maximum containers name - # Max size is configurable. See feature request #1723. - name_max_width = min( - self.config.get_int_value('docker', 'max_name_size', default=20) if self.config is not None else 20, - len(max(self.stats['containers'], key=lambda x: len(x['name']))['name']), - ) - msg = ' {:{width}}'.format('Name', width=name_max_width) - ret.append(self.curse_add_line(msg, 'SORT' if self.sort_key == 'name' else 'DEFAULT')) - msg = '{:>10}'.format('Status') - ret.append(self.curse_add_line(msg)) - msg = '{:>10}'.format('Uptime') - ret.append(self.curse_add_line(msg)) - msg = '{:>6}'.format('CPU%') - ret.append(self.curse_add_line(msg, 'SORT' if self.sort_key == 'cpu_percent' else 'DEFAULT')) - msg = '{:>7}'.format('MEM') - ret.append(self.curse_add_line(msg, 'SORT' if self.sort_key == 'memory_usage' else 'DEFAULT')) - msg = '/{:<7}'.format('MAX') - ret.append(self.curse_add_line(msg)) - msg = '{:>7}'.format('IOR/s') - ret.append(self.curse_add_line(msg)) - msg = ' {:<7}'.format('IOW/s') - ret.append(self.curse_add_line(msg)) - msg = '{:>7}'.format('Rx/s') - ret.append(self.curse_add_line(msg)) - msg = ' {:<7}'.format('Tx/s') - ret.append(self.curse_add_line(msg)) - msg = ' {:8}'.format('Command') - ret.append(self.curse_add_line(msg)) - # Data - for container in self.stats['containers']: - ret.append(self.curse_new_line()) - # Name - ret.append(self.curse_add_line(self._msg_name(container=container, max_width=name_max_width))) - # Status - status = self.container_alert(container['Status']) - msg = '{:>10}'.format(container['Status'][0:10]) - ret.append(self.curse_add_line(msg, status)) - # Uptime - if container['Uptime']: - msg = '{:>10}'.format(container['Uptime']) - else: - msg = '{:>10}'.format('_') - ret.append(self.curse_add_line(msg, status)) - # CPU - try: - msg = '{:>6.1f}'.format(container['cpu']['total']) - except KeyError: - msg = '{:>6}'.format('_') - ret.append(self.curse_add_line(msg, self.get_views(item=container['name'], key='cpu', option='decoration'))) - # MEM - try: - msg = '{:>7}'.format(self.auto_unit(container['memory']['usage'])) - except KeyError: - msg = '{:>7}'.format('_') - ret.append(self.curse_add_line(msg, self.get_views(item=container['name'], key='mem', option='decoration'))) - try: - msg = '/{:<7}'.format(self.auto_unit(container['memory']['limit'])) - except KeyError: - msg = '/{:<7}'.format('_') - ret.append(self.curse_add_line(msg)) - # IO R/W - unit = 'B' - try: - value = self.auto_unit(int(container['io']['ior'] // container['io']['time_since_update'])) + unit - msg = '{:>7}'.format(value) - except KeyError: - msg = '{:>7}'.format('_') - ret.append(self.curse_add_line(msg)) - try: - value = self.auto_unit(int(container['io']['iow'] // container['io']['time_since_update'])) + unit - msg = ' {:<7}'.format(value) - except KeyError: - msg = ' {:<7}'.format('_') - ret.append(self.curse_add_line(msg)) - # NET RX/TX - if args.byte: - # Bytes per second (for dummy) - to_bit = 1 - unit = '' - else: - # Bits per second (for real network administrator | Default) - to_bit = 8 - unit = 'b' - try: - value = ( - self.auto_unit( - int(container['network']['rx'] // container['network']['time_since_update'] * to_bit) - ) - + unit - ) - msg = '{:>7}'.format(value) - except KeyError: - msg = '{:>7}'.format('_') - ret.append(self.curse_add_line(msg)) - try: - value = ( - self.auto_unit( - int(container['network']['tx'] // container['network']['time_since_update'] * to_bit) - ) - + unit - ) - msg = ' {:<7}'.format(value) - except KeyError: - msg = ' {:<7}'.format('_') - ret.append(self.curse_add_line(msg)) - # Command - if container['Command'] is not None: - msg = ' {}'.format(' '.join(container['Command'])) - else: - msg = ' {}'.format('_') - ret.append(self.curse_add_line(msg, splittable=True)) - - return ret - - def _msg_name(self, container, max_width): - """Build the container name.""" - name = container['name'][:max_width] - return ' {:{width}}'.format(name, width=max_width) - - def container_alert(self, status): - """Analyse the container status.""" - if status == 'running': - return 'OK' - elif status == 'exited': - return 'WARNING' - elif status == 'dead': - return 'CRITICAL' - else: - return 'CAREFUL' - - -class ThreadDockerGrabber(threading.Thread): - """ - Specific thread to grab docker stats. - - stats is a dict - """ - - def __init__(self, container): - """Init the class. - - container: instance of Docker-py Container - """ - super(ThreadDockerGrabber, self).__init__() - # Event needed to stop properly the thread - self._stopper = threading.Event() - # The docker-py return stats as a stream - self._container = container - self._stats_stream = container.stats(decode=True) - # The class return the stats as a dict - self._stats = {} - logger.debug("docker plugin - Create thread for container {}".format(self._container.name)) - - def run(self): - """Grab the stats. - - Infinite loop, should be stopped by calling the stop() method - """ - try: - for i in self._stats_stream: - self._stats = i - time.sleep(0.1) - if self.stopped(): - break - except Exception as e: - logger.debug("docker plugin - Exception thrown during run ({})".format(e)) - self.stop() - - @property - def stats(self): - """Stats getter.""" - return self._stats - - @stats.setter - def stats(self, value): - """Stats setter.""" - self._stats = value - - def stop(self, timeout=None): - """Stop the thread.""" - logger.debug("docker plugin - Close thread for container {}".format(self._container.name)) - self._stopper.set() - - def stopped(self): - """Return True is the thread is stopped.""" - return self._stopper.is_set() - - -def sort_docker_stats(stats): - # Sort Docker stats using the same function than processes - sort_by = glances_processes.sort_key - sort_by_secondary = 'memory_usage' - if sort_by == 'memory_percent': - sort_by = 'memory_usage' - sort_by_secondary = 'cpu_percent' - elif sort_by in ['username', 'io_counters', 'cpu_times']: - sort_by = 'cpu_percent' - - # Sort docker stats - sort_stats_processes( - stats['containers'], - sorted_by=sort_by, - sorted_by_secondary=sort_by_secondary, - # Reverse for all but name - reverse=glances_processes.sort_key != 'name', - ) - - # Return the main sort key and the sorted stats - return sort_by, stats |