| Total Complexity | 180 | 
| Total Lines | 951 | 
| Duplicated Lines | 16.51 % | 
| 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.processlist 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: 2024 Nicolas Hennion <[email protected]>  | 
            ||
| 5 | #  | 
            ||
| 6 | # SPDX-License-Identifier: LGPL-3.0-only  | 
            ||
| 7 | #  | 
            ||
| 8 | |||
| 9 | """Process list plugin."""  | 
            ||
| 10 | |||
| 11 | import copy  | 
            ||
| 12 | import functools  | 
            ||
| 13 | import os  | 
            ||
| 14 | |||
| 15 | from glances.globals import WINDOWS, key_exist_value_not_none_not_v, replace_special_chars  | 
            ||
| 16 | from glances.logger import logger  | 
            ||
| 17 | from glances.outputs.glances_unicode import unicode_message  | 
            ||
| 18 | from glances.plugins.core import CorePlugin  | 
            ||
| 19 | from glances.plugins.plugin.model import GlancesPluginModel  | 
            ||
| 20 | from glances.processes import glances_processes, sort_stats  | 
            ||
| 21 | |||
| 22 | # Fields description  | 
            ||
| 23 | # description: human readable description  | 
            ||
| 24 | # short_name: shortname to use un UI  | 
            ||
| 25 | # unit: unit type  | 
            ||
| 26 | # rate: if True then compute and add *_gauge and *_rate_per_is fields  | 
            ||
| 27 | # min_symbol: Auto unit should be used if value > than 1 'X' (K, M, G)...  | 
            ||
| 28 | fields_description = { | 
            ||
| 29 |     'pid': { | 
            ||
| 30 | 'description': 'Process identifier (ID)',  | 
            ||
| 31 | 'unit': 'number',  | 
            ||
| 32 | },  | 
            ||
| 33 |     'name': { | 
            ||
| 34 | 'description': 'Process name',  | 
            ||
| 35 | 'unit': 'string',  | 
            ||
| 36 | },  | 
            ||
| 37 |     'cmdline': { | 
            ||
| 38 | 'description': 'Command line with arguments',  | 
            ||
| 39 | 'unit': 'list',  | 
            ||
| 40 | },  | 
            ||
| 41 |     'username': { | 
            ||
| 42 | 'description': 'Process owner',  | 
            ||
| 43 | 'unit': 'string',  | 
            ||
| 44 | },  | 
            ||
| 45 |     'num_threads': { | 
            ||
| 46 | 'description': 'Number of threads',  | 
            ||
| 47 | 'unit': 'number',  | 
            ||
| 48 | },  | 
            ||
| 49 |     'cpu_percent': { | 
            ||
| 50 | 'description': 'Process CPU consumption',  | 
            ||
| 51 | 'unit': 'percent',  | 
            ||
| 52 | },  | 
            ||
| 53 |     'memory_percent': { | 
            ||
| 54 | 'description': 'Process memory consumption',  | 
            ||
| 55 | 'unit': 'percent',  | 
            ||
| 56 | },  | 
            ||
| 57 |     'memory_info': { | 
            ||
| 58 | 'description': 'Process memory information (dict with rss, vms, shared, text, lib, data, dirty keys)',  | 
            ||
| 59 | 'unit': 'byte',  | 
            ||
| 60 | },  | 
            ||
| 61 |     'status': { | 
            ||
| 62 | 'description': 'Process status',  | 
            ||
| 63 | 'unit': 'string',  | 
            ||
| 64 | },  | 
            ||
| 65 |     'nice': { | 
            ||
| 66 | 'description': 'Process nice value',  | 
            ||
| 67 | 'unit': 'number',  | 
            ||
| 68 | },  | 
            ||
| 69 |     'cpu_times': { | 
            ||
| 70 | 'description': 'Process CPU times (dict with user, system, iowait keys)',  | 
            ||
| 71 | 'unit': 'second',  | 
            ||
| 72 | },  | 
            ||
| 73 |     'gids': { | 
            ||
| 74 | 'description': 'Process group IDs (dict with real, effective, saved keys)',  | 
            ||
| 75 | 'unit': 'number',  | 
            ||
| 76 | },  | 
            ||
| 77 |     'io_counters': { | 
            ||
| 78 | 'description': 'Process IO counters (list with read_count, write_count, read_bytes, write_bytes, io_tag keys)',  | 
            ||
| 79 | 'unit': 'byte',  | 
            ||
| 80 | },  | 
            ||
| 81 | }  | 
            ||
| 82 | |||
| 83 | |||
| 84 | def seconds_to_hms(input_seconds):  | 
            ||
| 85 | """Convert seconds to human-readable time."""  | 
            ||
| 86 | minutes, seconds = divmod(input_seconds, 60)  | 
            ||
| 87 | hours, minutes = divmod(minutes, 60)  | 
            ||
| 88 | |||
| 89 | hours = int(hours)  | 
            ||
| 90 | minutes = int(minutes)  | 
            ||
| 91 | seconds = str(int(seconds)).zfill(2)  | 
            ||
| 92 | |||
| 93 | return hours, minutes, seconds  | 
            ||
| 94 | |||
| 95 | |||
| 96 | def split_cmdline(bare_process_name, cmdline):  | 
            ||
| 97 | """Return path, cmd and arguments for a process cmdline based on bare_process_name.  | 
            ||
| 98 | |||
| 99 | If first argument of cmdline starts with the bare_process_name then  | 
            ||
| 100 | cmdline will just be considered cmd and path will be empty (see https://github.com/nicolargo/glances/issues/1795)  | 
            ||
| 101 | |||
| 102 | :param bare_process_name: Name of the process from psutil  | 
            ||
| 103 | :param cmdline: cmdline from psutil  | 
            ||
| 104 | :return: Tuple with three strings, which are path, cmd and arguments of the process  | 
            ||
| 105 | """  | 
            ||
| 106 | if cmdline[0].startswith(bare_process_name):  | 
            ||
| 107 | path, cmd = "", cmdline[0]  | 
            ||
| 108 | else:  | 
            ||
| 109 | path, cmd = os.path.split(cmdline[0])  | 
            ||
| 110 | arguments = ' '.join(cmdline[1:])  | 
            ||
| 111 | return path, cmd, arguments  | 
            ||
| 112 | |||
| 113 | |||
| 114 | class ProcesslistPlugin(GlancesPluginModel):  | 
            ||
| 115 | """Glances' processes plugin.  | 
            ||
| 116 | |||
| 117 | stats is a list  | 
            ||
| 118 | """  | 
            ||
| 119 | |||
| 120 | # Default list of processes stats to be grabbed / displayed  | 
            ||
| 121 | # Can be altered by glances_processes.disable_stats  | 
            ||
| 122 | enable_stats = [  | 
            ||
| 123 | 'cpu_percent',  | 
            ||
| 124 | 'memory_percent',  | 
            ||
| 125 | 'memory_info', # vms and rss  | 
            ||
| 126 | 'pid',  | 
            ||
| 127 | 'username',  | 
            ||
| 128 | 'cpu_times',  | 
            ||
| 129 | 'num_threads',  | 
            ||
| 130 | 'nice',  | 
            ||
| 131 | 'status',  | 
            ||
| 132 | 'io_counters', # ior and iow  | 
            ||
| 133 | 'cmdline',  | 
            ||
| 134 | ]  | 
            ||
| 135 | |||
| 136 | # Define the header layout of the processes list columns  | 
            ||
| 137 |     layout_header = { | 
            ||
| 138 |         'cpu': '{:<6} ', | 
            ||
| 139 |         'mem': '{:<5} ', | 
            ||
| 140 |         'virt': '{:<5} ', | 
            ||
| 141 |         'res': '{:<5} ', | 
            ||
| 142 |         'pid': '{:>{width}} ', | 
            ||
| 143 |         'user': '{:<10} ', | 
            ||
| 144 |         'time': '{:>8} ', | 
            ||
| 145 |         'thread': '{:<3} ', | 
            ||
| 146 |         'nice': '{:>3} ', | 
            ||
| 147 |         'status': '{:>1} ', | 
            ||
| 148 |         'ior': '{:>4} ', | 
            ||
| 149 |         'iow': '{:<4} ', | 
            ||
| 150 |         'command': '{} {}', | 
            ||
| 151 | }  | 
            ||
| 152 | |||
| 153 | # Define the stat layout of the processes list columns  | 
            ||
| 154 |     layout_stat = { | 
            ||
| 155 |         'cpu': '{:<6.1f}', | 
            ||
| 156 |         'cpu_no_digit': '{:<6.0f}', | 
            ||
| 157 |         'mem': '{:<5.1f} ', | 
            ||
| 158 |         'virt': '{:<5} ', | 
            ||
| 159 |         'res': '{:<5} ', | 
            ||
| 160 |         'pid': '{:>{width}} ', | 
            ||
| 161 |         'user': '{:<10} ', | 
            ||
| 162 |         'time': '{:>8} ', | 
            ||
| 163 |         'thread': '{:<3} ', | 
            ||
| 164 |         'nice': '{:>3} ', | 
            ||
| 165 |         'status': '{:>1} ', | 
            ||
| 166 |         'ior': '{:>4} ', | 
            ||
| 167 |         'iow': '{:<4} ', | 
            ||
| 168 |         'command': '{}', | 
            ||
| 169 |         'name': '[{}]', | 
            ||
| 170 | }  | 
            ||
| 171 | |||
| 172 | def __init__(self, args=None, config=None):  | 
            ||
| 173 | """Init the plugin."""  | 
            ||
| 174 | super().__init__(args=args, config=config, fields_description=fields_description, stats_init_value=[])  | 
            ||
| 175 | |||
| 176 | # We want to display the stat in the curse interface  | 
            ||
| 177 | self.display_curse = True  | 
            ||
| 178 | |||
| 179 | # Trying to display proc time  | 
            ||
| 180 | self.tag_proc_time = True  | 
            ||
| 181 | |||
| 182 | # Call CorePlugin to get the core number (needed when not in IRIX mode / Solaris mode)  | 
            ||
| 183 | try:  | 
            ||
| 184 | self.nb_log_core = CorePlugin(args=self.args).update()["log"]  | 
            ||
| 185 | except Exception:  | 
            ||
| 186 | self.nb_log_core = 0  | 
            ||
| 187 | |||
| 188 | # Get the max values (dict)  | 
            ||
| 189 | self.max_values = copy.deepcopy(glances_processes.max_values())  | 
            ||
| 190 | |||
| 191 | # Get the maximum PID number  | 
            ||
| 192 | # Use to optimize space (see https://github.com/nicolargo/glances/issues/959)  | 
            ||
| 193 | self.pid_max = glances_processes.pid_max  | 
            ||
| 194 | |||
| 195 | # Load the config file  | 
            ||
| 196 | self.load(args, config)  | 
            ||
| 197 | |||
| 198 | # The default sort key could also be overwrite by command line (see #1903)  | 
            ||
| 199 | if args and args.sort_processes_key is not None:  | 
            ||
| 200 | glances_processes.set_sort_key(args.sort_processes_key, False)  | 
            ||
| 201 | |||
| 202 | # Note: 'glances_processes' is already init in the processes.py script  | 
            ||
| 203 | |||
| 204 | def load(self, args, config):  | 
            ||
| 205 | # Set the default sort key if it is defined in the configuration file  | 
            ||
| 206 | if config is None or 'processlist' not in config.as_dict():  | 
            ||
| 207 | return  | 
            ||
| 208 | if 'sort_key' in config.as_dict()['processlist']:  | 
            ||
| 209 | logger.debug(  | 
            ||
| 210 |                 'Configuration overwrites processes sort key by {}'.format(config.as_dict()['processlist']['sort_key']) | 
            ||
| 211 | )  | 
            ||
| 212 | glances_processes.set_sort_key(config.as_dict()['processlist']['sort_key'], False)  | 
            ||
| 213 | if 'export' in config.as_dict()['processlist']:  | 
            ||
| 214 | glances_processes.export_process_filter = config.as_dict()['processlist']['export']  | 
            ||
| 215 | if args.export:  | 
            ||
| 216 |                 logger.info("Export process filter is set to: {}".format(config.as_dict()['processlist']['export'])) | 
            ||
| 217 | if 'disable_stats' in config.as_dict()['processlist']:  | 
            ||
| 218 | logger.info(  | 
            ||
| 219 |                 'Followings processes stats wil not be displayed: {}'.format( | 
            ||
| 220 | config.as_dict()['processlist']['disable_stats']  | 
            ||
| 221 | )  | 
            ||
| 222 | )  | 
            ||
| 223 |             glances_processes.disable_stats = config.as_dict()['processlist']['disable_stats'].split(',') | 
            ||
| 224 | |||
| 225 | def get_key(self):  | 
            ||
| 226 | """Return the key of the list."""  | 
            ||
| 227 | return 'pid'  | 
            ||
| 228 | |||
| 229 | def update(self):  | 
            ||
| 230 | """Update processes stats using the input method."""  | 
            ||
| 231 | # Update the stats  | 
            ||
| 232 | if self.input_method == 'local':  | 
            ||
| 233 | # Update stats using the standard system lib  | 
            ||
| 234 | # Note: Update is done in the processcount plugin  | 
            ||
| 235 | # Just return the result  | 
            ||
| 236 | stats = glances_processes.get_list()  | 
            ||
| 237 | else:  | 
            ||
| 238 | stats = self.get_init_value()  | 
            ||
| 239 | |||
| 240 | # Get the max values (dict)  | 
            ||
| 241 | # Use Deep copy to avoid change between update and display  | 
            ||
| 242 | self.max_values = copy.deepcopy(glances_processes.max_values())  | 
            ||
| 243 | |||
| 244 | # Update the stats  | 
            ||
| 245 | self.stats = stats  | 
            ||
| 246 | |||
| 247 | return self.stats  | 
            ||
| 248 | |||
| 249 | def get_export(self):  | 
            ||
| 250 | """Return the processes list to export.  | 
            ||
| 251 | Not all the processeses are exported.  | 
            ||
| 252 | Only the one defined in the Glances configuration file (see #794 for details).  | 
            ||
| 253 | """  | 
            ||
| 254 | return glances_processes.get_export()  | 
            ||
| 255 | |||
| 256 | def get_nice_alert(self, value):  | 
            ||
| 257 | """Return the alert relative to the Nice configuration list"""  | 
            ||
| 258 | value = str(value)  | 
            ||
| 259 |         if self.get_limit('nice_critical') and value in self.get_limit('nice_critical'): | 
            ||
| 260 | return 'CRITICAL'  | 
            ||
| 261 |         if self.get_limit('nice_warning') and value in self.get_limit('nice_warning'): | 
            ||
| 262 | return 'WARNING'  | 
            ||
| 263 |         if self.get_limit('nice_careful') and value in self.get_limit('nice_careful'): | 
            ||
| 264 | return 'CAREFUL'  | 
            ||
| 265 |         if self.get_limit('nice_ok') and value in self.get_limit('nice_ok'): | 
            ||
| 266 | return 'OK'  | 
            ||
| 267 | |||
| 268 | return 'DEFAULT'  | 
            ||
| 269 | |||
| 270 | def get_status_alert(self, value):  | 
            ||
| 271 | """Return the alert relative to the Status configuration list"""  | 
            ||
| 272 | value = str(value)  | 
            ||
| 273 |         if self.get_limit('status_critical') and value in self.get_limit('status_critical'): | 
            ||
| 274 | return 'CRITICAL'  | 
            ||
| 275 |         if self.get_limit('status_warning') and value in self.get_limit('status_warning'): | 
            ||
| 276 | return 'WARNING'  | 
            ||
| 277 |         if self.get_limit('status_careful') and value in self.get_limit('status_careful'): | 
            ||
| 278 | return 'CAREFUL'  | 
            ||
| 279 |         if self.get_limit('status_ok') and value in self.get_limit('status_ok'): | 
            ||
| 280 | return 'OK'  | 
            ||
| 281 | |||
| 282 | return 'OK' if value == 'R' else 'DEFAULT'  | 
            ||
| 283 | |||
| 284 | def _get_process_curses_cpu_percent(self, p, selected, args):  | 
            ||
| 285 | """Return process CPU curses"""  | 
            ||
| 286 |         if key_exist_value_not_none_not_v('cpu_percent', p, ''): | 
            ||
| 287 | cpu_layout = self.layout_stat['cpu'] if p['cpu_percent'] < 100 else self.layout_stat['cpu_no_digit']  | 
            ||
| 288 | if args.disable_irix and self.nb_log_core != 0:  | 
            ||
| 289 | msg = cpu_layout.format(p['cpu_percent'] / float(self.nb_log_core))  | 
            ||
| 290 | else:  | 
            ||
| 291 | msg = cpu_layout.format(p['cpu_percent'])  | 
            ||
| 292 | alert = self.get_alert(  | 
            ||
| 293 | p['cpu_percent'],  | 
            ||
| 294 | highlight_zero=False,  | 
            ||
| 295 | is_max=(p['cpu_percent'] == self.max_values['cpu_percent']),  | 
            ||
| 296 | header="cpu",  | 
            ||
| 297 | )  | 
            ||
| 298 | ret = self.curse_add_line(msg, alert)  | 
            ||
| 299 | else:  | 
            ||
| 300 |             msg = self.layout_header['cpu'].format('?') | 
            ||
| 301 | ret = self.curse_add_line(msg)  | 
            ||
| 302 | return ret  | 
            ||
| 303 | |||
| 304 | def _get_process_curses_memory_percent(self, p, selected, args):  | 
            ||
| 305 | """Return process MEM curses"""  | 
            ||
| 306 |         if key_exist_value_not_none_not_v('memory_percent', p, ''): | 
            ||
| 307 | msg = self.layout_stat['mem'].format(p['memory_percent'])  | 
            ||
| 308 | alert = self.get_alert(  | 
            ||
| 309 | p['memory_percent'],  | 
            ||
| 310 | highlight_zero=False,  | 
            ||
| 311 | is_max=(p['memory_percent'] == self.max_values['memory_percent']),  | 
            ||
| 312 | header="mem",  | 
            ||
| 313 | )  | 
            ||
| 314 | ret = self.curse_add_line(msg, alert)  | 
            ||
| 315 | else:  | 
            ||
| 316 |             msg = self.layout_header['mem'].format('?') | 
            ||
| 317 | ret = self.curse_add_line(msg)  | 
            ||
| 318 | return ret  | 
            ||
| 319 | |||
| 320 | def _get_process_curses_vms(self, p, selected, args):  | 
            ||
| 321 | """Return process VMS curses"""  | 
            ||
| 322 |         if key_exist_value_not_none_not_v('memory_info', p, '', 1) and 'vms' in p['memory_info']: | 
            ||
| 323 | msg = self.layout_stat['virt'].format(self.auto_unit(p['memory_info']['vms'], low_precision=False))  | 
            ||
| 324 | ret = self.curse_add_line(msg, optional=True)  | 
            ||
| 325 | else:  | 
            ||
| 326 |             msg = self.layout_header['virt'].format('?') | 
            ||
| 327 | ret = self.curse_add_line(msg)  | 
            ||
| 328 | return ret  | 
            ||
| 329 | |||
| 330 | def _get_process_curses_rss(self, p, selected, args):  | 
            ||
| 331 | """Return process RSS curses"""  | 
            ||
| 332 |         if key_exist_value_not_none_not_v('memory_info', p, '', 0) and 'rss' in p['memory_info']: | 
            ||
| 333 | msg = self.layout_stat['res'].format(self.auto_unit(p['memory_info']['rss'], low_precision=False))  | 
            ||
| 334 | ret = self.curse_add_line(msg, optional=True)  | 
            ||
| 335 | else:  | 
            ||
| 336 |             msg = self.layout_header['res'].format('?') | 
            ||
| 337 | ret = self.curse_add_line(msg)  | 
            ||
| 338 | return ret  | 
            ||
| 339 | |||
| 340 | def _get_process_curses_memory_info(self, p, selected, args):  | 
            ||
| 341 | return [  | 
            ||
| 342 | self._get_process_curses_vms(p, selected, args),  | 
            ||
| 343 | self._get_process_curses_rss(p, selected, args),  | 
            ||
| 344 | ]  | 
            ||
| 345 | |||
| 346 | def _get_process_curses_pid(self, p, selected, args):  | 
            ||
| 347 | """Return process PID curses"""  | 
            ||
| 348 | # Display processes, so the PID should be displayed  | 
            ||
| 349 | msg = self.layout_stat['pid'].format(p['pid'], width=self._max_pid_size())  | 
            ||
| 350 | return self.curse_add_line(msg)  | 
            ||
| 351 | |||
| 352 | def _get_process_curses_username(self, p, selected, args):  | 
            ||
| 353 | """Return process username curses"""  | 
            ||
| 354 | if 'username' in p:  | 
            ||
| 355 | # docker internal users are displayed as ints only, therefore str()  | 
            ||
| 356 | # Correct issue #886 on Windows OS  | 
            ||
| 357 | msg = self.layout_stat['user'].format(str(p['username'])[:9])  | 
            ||
| 358 | else:  | 
            ||
| 359 |             msg = self.layout_header['user'].format('?') | 
            ||
| 360 | return self.curse_add_line(msg)  | 
            ||
| 361 | |||
| 362 | def _get_process_curses_cpu_times(self, p, selected, args):  | 
            ||
| 363 | """Return process time curses"""  | 
            ||
| 364 | cpu_times = p['cpu_times']  | 
            ||
| 365 | try:  | 
            ||
| 366 | # Sum user and system time  | 
            ||
| 367 | user_system_time = cpu_times['user'] + cpu_times['system']  | 
            ||
| 368 | except (OverflowError, TypeError, KeyError):  | 
            ||
| 369 | # Catch OverflowError on some Amazon EC2 server  | 
            ||
| 370 | # See https://github.com/nicolargo/glances/issues/87  | 
            ||
| 371 | # Also catch TypeError on macOS  | 
            ||
| 372 | # See: https://github.com/nicolargo/glances/issues/622  | 
            ||
| 373 | # Also catch KeyError (as no stats be present for processes of other users)  | 
            ||
| 374 | # See: https://github.com/nicolargo/glances/issues/2831  | 
            ||
| 375 |             # logger.debug("Cannot get TIME+ ({})".format(e)) | 
            ||
| 376 |             msg = self.layout_header['time'].format('?') | 
            ||
| 377 | return self.curse_add_line(msg, optional=True)  | 
            ||
| 378 | |||
| 379 | hours, minutes, seconds = seconds_to_hms(user_system_time)  | 
            ||
| 380 | if hours > 99:  | 
            ||
| 381 |             msg = f'{hours:<7}h' | 
            ||
| 382 | elif 0 < hours < 100:  | 
            ||
| 383 |             msg = f'{hours}h{minutes}:{seconds}' | 
            ||
| 384 | else:  | 
            ||
| 385 |             msg = f'{minutes}:{seconds}' | 
            ||
| 386 | |||
| 387 | msg = self.layout_stat['time'].format(msg)  | 
            ||
| 388 | if hours > 0:  | 
            ||
| 389 | return self.curse_add_line(msg, decoration='CPU_TIME', optional=True)  | 
            ||
| 390 | |||
| 391 | return self.curse_add_line(msg, optional=True)  | 
            ||
| 392 | |||
| 393 | def _get_process_curses_num_threads(self, p, selected, args):  | 
            ||
| 394 | """Return process thread curses"""  | 
            ||
| 395 | if 'num_threads' in p:  | 
            ||
| 396 | num_threads = p['num_threads']  | 
            ||
| 397 | if num_threads is None:  | 
            ||
| 398 | num_threads = '?'  | 
            ||
| 399 | msg = self.layout_stat['thread'].format(num_threads)  | 
            ||
| 400 | else:  | 
            ||
| 401 |             msg = self.layout_header['thread'].format('?') | 
            ||
| 402 | return self.curse_add_line(msg)  | 
            ||
| 403 | |||
| 404 | def _get_process_curses_nice(self, p, selected, args):  | 
            ||
| 405 | """Return process nice curses"""  | 
            ||
| 406 | if 'nice' in p:  | 
            ||
| 407 | nice = p['nice']  | 
            ||
| 408 | if nice is None:  | 
            ||
| 409 | nice = '?'  | 
            ||
| 410 | msg = self.layout_stat['nice'].format(nice)  | 
            ||
| 411 | ret = self.curse_add_line(msg, decoration=self.get_nice_alert(nice))  | 
            ||
| 412 | else:  | 
            ||
| 413 |             msg = self.layout_header['nice'].format('?') | 
            ||
| 414 | ret = self.curse_add_line(msg)  | 
            ||
| 415 | return ret  | 
            ||
| 416 | |||
| 417 | def _get_process_curses_status(self, p, selected, args):  | 
            ||
| 418 | """Return process status curses"""  | 
            ||
| 419 | if 'status' in p:  | 
            ||
| 420 | status = p['status']  | 
            ||
| 421 | msg = self.layout_stat['status'].format(status)  | 
            ||
| 422 | ret = self.curse_add_line(msg, decoration=self.get_status_alert(status))  | 
            ||
| 423 | # if status == 'R':  | 
            ||
| 424 | # ret = self.curse_add_line(msg, decoration='STATUS')  | 
            ||
| 425 | # else:  | 
            ||
| 426 | # ret = self.curse_add_line(msg)  | 
            ||
| 427 | else:  | 
            ||
| 428 |             msg = self.layout_header['status'].format('?') | 
            ||
| 429 | ret = self.curse_add_line(msg)  | 
            ||
| 430 | return ret  | 
            ||
| 431 | |||
| 432 | def _get_process_curses_io_read_write(self, p, selected, args, rorw='ior'):  | 
            ||
| 433 | """Return process IO Read or Write curses"""  | 
            ||
| 434 | if 'io_counters' in p and p['io_counters'][4] == 1 and p['time_since_update'] != 0:  | 
            ||
| 435 | # Display rate if stats is available and io_tag ([4]) == 1  | 
            ||
| 436 | # IO  | 
            ||
| 437 | io = int(  | 
            ||
| 438 | (p['io_counters'][0 if rorw == 'ior' else 1] - p['io_counters'][2 if rorw == 'ior' else 3])  | 
            ||
| 439 | / p['time_since_update']  | 
            ||
| 440 | )  | 
            ||
| 441 | if io == 0:  | 
            ||
| 442 |                 msg = self.layout_stat[rorw].format("0") | 
            ||
| 443 | else:  | 
            ||
| 444 | msg = self.layout_stat[rorw].format(self.auto_unit(io, low_precision=True))  | 
            ||
| 445 | ret = self.curse_add_line(msg, optional=True, additional=True)  | 
            ||
| 446 | else:  | 
            ||
| 447 |             msg = self.layout_header[rorw].format("?") | 
            ||
| 448 | ret = self.curse_add_line(msg, optional=True, additional=True)  | 
            ||
| 449 | return ret  | 
            ||
| 450 | |||
| 451 | def _get_process_curses_io_counters(self, p, selected, args):  | 
            ||
| 452 | return [  | 
            ||
| 453 | self._get_process_curses_io_read_write(p, selected, args, rorw='ior'),  | 
            ||
| 454 | self._get_process_curses_io_read_write(p, selected, args, rorw='iow'),  | 
            ||
| 455 | ]  | 
            ||
| 456 | |||
| 457 | def _get_process_curses_cmdline(self, p, selected, args):  | 
            ||
| 458 | """Return process cmdline curses"""  | 
            ||
| 459 | ret = []  | 
            ||
| 460 | # If no command line for the process is available, fallback to the bare process name instead  | 
            ||
| 461 | bare_process_name = p['name']  | 
            ||
| 462 |         cmdline = p.get('cmdline', '?') | 
            ||
| 463 | try:  | 
            ||
| 464 | process_decoration = 'PROCESS_SELECTED' if (selected and not args.disable_cursor) else 'PROCESS'  | 
            ||
| 465 | if cmdline:  | 
            ||
| 466 | path, cmd, arguments = split_cmdline(bare_process_name, cmdline)  | 
            ||
| 467 | # Manage end of line in arguments (see #1692)  | 
            ||
| 468 | arguments = replace_special_chars(arguments)  | 
            ||
| 469 | if os.path.isdir(path) and not args.process_short_name:  | 
            ||
| 470 | msg = self.layout_stat['command'].format(path) + os.sep  | 
            ||
| 471 | ret.append(self.curse_add_line(msg, splittable=True))  | 
            ||
| 472 | ret.append(self.curse_add_line(cmd, decoration=process_decoration, splittable=True))  | 
            ||
| 473 | else:  | 
            ||
| 474 | msg = self.layout_stat['command'].format(cmd)  | 
            ||
| 475 | ret.append(self.curse_add_line(msg, decoration=process_decoration, splittable=True))  | 
            ||
| 476 | if arguments:  | 
            ||
| 477 | msg = ' ' + self.layout_stat['command'].format(arguments)  | 
            ||
| 478 | ret.append(self.curse_add_line(msg, splittable=True))  | 
            ||
| 479 | else:  | 
            ||
| 480 | msg = self.layout_stat['name'].format(bare_process_name)  | 
            ||
| 481 | ret.append(self.curse_add_line(msg, decoration=process_decoration, splittable=True))  | 
            ||
| 482 | except (TypeError, UnicodeEncodeError) as e:  | 
            ||
| 483 | # Avoid crash after running fine for several hours #1335  | 
            ||
| 484 |             logger.debug(f"Can not decode command line '{cmdline}' ({e})") | 
            ||
| 485 |             ret.append(self.curse_add_line('', splittable=True)) | 
            ||
| 486 | return ret  | 
            ||
| 487 | |||
| 488 | def get_process_curses_data(self, p, selected, args):  | 
            ||
| 489 | """Get curses data to display for a process.  | 
            ||
| 490 | |||
| 491 | - p is the process to display  | 
            ||
| 492 | - selected is a tag=True if p is the selected process  | 
            ||
| 493 | """  | 
            ||
| 494 | ret = [self.curse_new_line()]  | 
            ||
| 495 | |||
| 496 | # When a process is selected:  | 
            ||
| 497 | # * display a special character at the beginning of the line  | 
            ||
| 498 | # * underline the command name  | 
            ||
| 499 | ret.append(  | 
            ||
| 500 | self.curse_add_line(  | 
            ||
| 501 |                 unicode_message('PROCESS_SELECTOR') if (selected and not args.disable_cursor) else ' ', 'SELECTED' | 
            ||
| 502 | )  | 
            ||
| 503 | )  | 
            ||
| 504 | |||
| 505 | for stat in [i for i in self.enable_stats if i not in glances_processes.disable_stats]:  | 
            ||
| 506 |             msg = getattr(self, f'_get_process_curses_{stat}')(p, selected, args) | 
            ||
| 507 | if isinstance(msg, list):  | 
            ||
| 508 | # ex: _get_process_curses_command return a list, so extend  | 
            ||
| 509 | ret.extend(msg)  | 
            ||
| 510 | else:  | 
            ||
| 511 | # ex: _get_process_curses_cpu return a dict, so append  | 
            ||
| 512 | ret.append(msg)  | 
            ||
| 513 | |||
| 514 | return ret  | 
            ||
| 515 | |||
| 516 | def is_selected_process(self, args):  | 
            ||
| 517 | return (  | 
            ||
| 518 | args.is_standalone  | 
            ||
| 519 | and self.args.enable_process_extended  | 
            ||
| 520 | and args.cursor_position is not None  | 
            ||
| 521 | and glances_processes.extended_process is not None  | 
            ||
| 522 | )  | 
            ||
| 523 | |||
| 524 | def msg_curse(self, args=None, max_width=None):  | 
            ||
| 525 | """Return the dict to display in the curse interface."""  | 
            ||
| 526 | # Init the return message  | 
            ||
| 527 | ret = []  | 
            ||
| 528 | |||
| 529 | # Only process if stats exist and display plugin enable...  | 
            ||
| 530 | if not self.stats or args.disable_process:  | 
            ||
| 531 | return ret  | 
            ||
| 532 | |||
| 533 | # Compute the sort key  | 
            ||
| 534 | process_sort_key = glances_processes.sort_key  | 
            ||
| 535 | processes_list_sorted = self._sort_stats(process_sort_key)  | 
            ||
| 536 | |||
| 537 | # Display extended stats for selected process  | 
            ||
| 538 | #############################################  | 
            ||
| 539 | |||
| 540 | if self.is_selected_process(args):  | 
            ||
| 541 | self._msg_curse_extended_process(ret, glances_processes.extended_process)  | 
            ||
| 542 | |||
| 543 | # Display others processes list  | 
            ||
| 544 | ###############################  | 
            ||
| 545 | |||
| 546 | # Header  | 
            ||
| 547 | self._msg_curse_header(ret, process_sort_key, args)  | 
            ||
| 548 | |||
| 549 | # Process list  | 
            ||
| 550 | # Loop over processes (sorted by the sort key previously compute)  | 
            ||
| 551 | # This is a Glances bottleneck (see flame graph),  | 
            ||
| 552 | # TODO: get_process_curses_data should be optimized  | 
            ||
| 553 | for position, process in enumerate(processes_list_sorted):  | 
            ||
| 554 | ret.extend(self.get_process_curses_data(process, position == args.cursor_position, args))  | 
            ||
| 555 | |||
| 556 | # A filter is set Display the stats summaries  | 
            ||
| 557 | if glances_processes.process_filter is not None:  | 
            ||
| 558 | if args.reset_minmax_tag:  | 
            ||
| 559 | args.reset_minmax_tag = not args.reset_minmax_tag  | 
            ||
| 560 | self._mmm_reset()  | 
            ||
| 561 | self._msg_curse_sum(ret, args=args)  | 
            ||
| 562 | self._msg_curse_sum(ret, mmm='min', args=args)  | 
            ||
| 563 | self._msg_curse_sum(ret, mmm='max', args=args)  | 
            ||
| 564 | |||
| 565 | # Return the message with decoration  | 
            ||
| 566 | return ret  | 
            ||
| 567 | |||
| 568 | def _msg_curse_extended_process(self, ret, p):  | 
            ||
| 569 | """Get extended curses data for the selected process (see issue #2225)  | 
            ||
| 570 | |||
| 571 | The result depends of the process type (process or thread).  | 
            ||
| 572 | |||
| 573 | Input p is a dict with the following keys:  | 
            ||
| 574 |         {'status': 'S', | 
            ||
| 575 |          'memory_info': {'rss': 466890752, 'vms': 3365347328, 'shared': 68153344, | 
            ||
| 576 | 'text': 659456, 'lib': 0, 'data': 774647808, 'dirty': 0],  | 
            ||
| 577 | 'pid': 4980,  | 
            ||
| 578 | 'io_counters': [165385216, 0, 165385216, 0, 1],  | 
            ||
| 579 | 'num_threads': 20,  | 
            ||
| 580 | 'nice': 0,  | 
            ||
| 581 | 'memory_percent': 5.958135664449709,  | 
            ||
| 582 | 'cpu_percent': 0.0,  | 
            ||
| 583 |          'gids': {'real': 1000, 'effective': 1000, 'saved': 1000}, | 
            ||
| 584 |          'cpu_times': {'user': 696.38, 'system': 119.98, 'children_user': 0.0, | 
            ||
| 585 | 'children_system': 0.0, 'iowait': 0.0),  | 
            ||
| 586 | 'name': 'WebExtensions',  | 
            ||
| 587 | 'key': 'pid',  | 
            ||
| 588 | 'time_since_update': 2.1997854709625244,  | 
            ||
| 589 | 'cmdline': ['/snap/firefox/2154/usr/lib/firefox/firefox', '-contentproc', '-childID', '...'],  | 
            ||
| 590 | 'username': 'nicolargo',  | 
            ||
| 591 | 'cpu_min': 0.0,  | 
            ||
| 592 | 'cpu_max': 7.0,  | 
            ||
| 593 | 'cpu_mean': 3.2}  | 
            ||
| 594 | """  | 
            ||
| 595 | self._msg_curse_extended_process_thread(ret, p)  | 
            ||
| 596 | |||
| 597 | def add_title_line(self, ret, prog):  | 
            ||
| 598 |         ret.append(self.curse_add_line("Pinned thread ", "TITLE")) | 
            ||
| 599 | ret.append(self.curse_add_line(prog['name'], "UNDERLINE"))  | 
            ||
| 600 |         ret.append(self.curse_add_line(" ('e' to unpin)")) | 
            ||
| 601 | |||
| 602 | return ret  | 
            ||
| 603 | |||
| 604 | def add_cpu_line(self, ret, prog):  | 
            ||
| 605 | ret.append(self.curse_new_line())  | 
            ||
| 606 |         ret.append(self.curse_add_line(' CPU Min/Max/Mean: ')) | 
            ||
| 607 |         msg = '{: >7.1f}{: >7.1f}{: >7.1f}%'.format(prog['cpu_min'], prog['cpu_max'], prog['cpu_mean']) | 
            ||
| 608 | ret.append(self.curse_add_line(msg, decoration='INFO'))  | 
            ||
| 609 | |||
| 610 | return ret  | 
            ||
| 611 | |||
| 612 | def maybe_add_cpu_affinity_line(self, ret, prog):  | 
            ||
| 613 | if 'cpu_affinity' in prog and prog['cpu_affinity'] is not None:  | 
            ||
| 614 |             ret.append(self.curse_add_line(' Affinity: ')) | 
            ||
| 615 | ret.append(self.curse_add_line(str(len(prog['cpu_affinity'])), decoration='INFO'))  | 
            ||
| 616 |             ret.append(self.curse_add_line(' cores', decoration='INFO')) | 
            ||
| 617 | |||
| 618 | return ret  | 
            ||
| 619 | |||
| 620 | def add_ionice_line(self, headers, default):  | 
            ||
| 621 | def add_ionice_using_matches(msg, v):  | 
            ||
| 622 | return msg + headers.get(v, default(v))  | 
            ||
| 623 | |||
| 624 | return add_ionice_using_matches  | 
            ||
| 625 | |||
| 626 | def get_headers(self, k):  | 
            ||
| 627 | # Linux: The scheduling class. 0 for none, 1 for real time, 2 for best-effort, 3 for idle.  | 
            ||
| 628 |         default = {0: 'No specific I/O priority', 1: k + 'Real Time', 2: k + 'Best Effort', 3: k + 'IDLE'} | 
            ||
| 629 | |||
| 630 | # Windows: On Windows only ioclass is used and it can be set to 2 (normal), 1 (low) or 0 (very low).  | 
            ||
| 631 |         windows = {0: k + 'Very Low', 1: k + 'Low', 2: 'No specific I/O priority'} | 
            ||
| 632 | |||
| 633 | return windows if WINDOWS else default  | 
            ||
| 634 | |||
| 635 | def maybe_add_ionice_line(self, ret, prog):  | 
            ||
| 636 | if 'ionice' in prog and prog['ionice'] is not None and hasattr(prog['ionice'], 'ioclass'):  | 
            ||
| 637 | msg = ' IO nice: '  | 
            ||
| 638 | k = 'Class is '  | 
            ||
| 639 | v = prog['ionice'].ioclass  | 
            ||
| 640 | |||
| 641 | def default(v):  | 
            ||
| 642 | return k + str(v)  | 
            ||
| 
                                                                                                    
                        
                         | 
                |||
| 643 | |||
| 644 | headers = self.get_headers(k)  | 
            ||
| 645 | msg = self.add_ionice_line(headers, default)(msg, v)  | 
            ||
| 646 | # value is a number which goes from 0 to 7.  | 
            ||
| 647 | # The higher the value, the lower the I/O priority of the process.  | 
            ||
| 648 | if hasattr(prog['ionice'], 'value') and prog['ionice'].value != 0:  | 
            ||
| 649 |                 msg += ' (value {}/7)'.format(str(prog['ionice'].value)) | 
            ||
| 650 | ret.append(self.curse_add_line(msg, splittable=True))  | 
            ||
| 651 | |||
| 652 | return ret  | 
            ||
| 653 | |||
| 654 | def maybe_add_memory_swap_line(self, ret, prog):  | 
            ||
| 655 | if 'memory_swap' in prog and prog['memory_swap'] is not None:  | 
            ||
| 656 | ret.append(  | 
            ||
| 657 | self.curse_add_line(  | 
            ||
| 658 | self.auto_unit(prog['memory_swap'], low_precision=False), decoration='INFO', splittable=True  | 
            ||
| 659 | )  | 
            ||
| 660 | )  | 
            ||
| 661 |             ret.append(self.curse_add_line(' swap ', splittable=True)) | 
            ||
| 662 | |||
| 663 | return ret  | 
            ||
| 664 | |||
| 665 | def add_memory_info_lines(self, ret, prog):  | 
            ||
| 666 | for key, val in prog['memory_info'].items():  | 
            ||
| 667 | ret.append(  | 
            ||
| 668 | self.curse_add_line(  | 
            ||
| 669 | self.auto_unit(val, low_precision=False),  | 
            ||
| 670 | decoration='INFO',  | 
            ||
| 671 | splittable=True,  | 
            ||
| 672 | )  | 
            ||
| 673 | )  | 
            ||
| 674 |             ret.append(self.curse_add_line(' ' + key + ' ', splittable=True)) | 
            ||
| 675 | |||
| 676 | return ret  | 
            ||
| 677 | |||
| 678 | def add_memory_line(self, ret, prog):  | 
            ||
| 679 | ret.append(self.curse_new_line())  | 
            ||
| 680 |         ret.append(self.curse_add_line(' MEM Min/Max/Mean: ')) | 
            ||
| 681 |         msg = '{: >7.1f}{: >7.1f}{: >7.1f}%'.format(prog['memory_min'], prog['memory_max'], prog['memory_mean']) | 
            ||
| 682 | ret.append(self.curse_add_line(msg, decoration='INFO'))  | 
            ||
| 683 | if 'memory_info' in prog and prog['memory_info'] is not None:  | 
            ||
| 684 |             ret.append(self.curse_add_line(' Memory info: ')) | 
            ||
| 685 | steps = [self.add_memory_info_lines, self.maybe_add_memory_swap_line]  | 
            ||
| 686 | ret = functools.reduce(lambda ret, step: step(ret, prog), steps, ret)  | 
            ||
| 687 | |||
| 688 | return ret  | 
            ||
| 689 | |||
| 690 | def add_io_and_network_lines(self, ret, prog):  | 
            ||
| 691 | ret.append(self.curse_new_line())  | 
            ||
| 692 |         ret.append(self.curse_add_line(' Open: ')) | 
            ||
| 693 | for stat_prefix in ['num_threads', 'num_fds', 'num_handles', 'tcp', 'udp']:  | 
            ||
| 694 | if stat_prefix in prog and prog[stat_prefix] is not None:  | 
            ||
| 695 | ret.append(self.curse_add_line(str(prog[stat_prefix]), decoration='INFO'))  | 
            ||
| 696 |                 ret.append(self.curse_add_line(' {} '.format(stat_prefix.replace('num_', '')))) | 
            ||
| 697 | return ret  | 
            ||
| 698 | |||
| 699 | def _msg_curse_extended_process_thread(self, ret, prog):  | 
            ||
| 700 | # `append_newlines` has dummy arguments for piping thru `functools.reduce`  | 
            ||
| 701 | def append_newlines(ret, prog):  | 
            ||
| 702 | (ret.append(self.curse_new_line()),)  | 
            ||
| 703 | ret.append(self.curse_new_line())  | 
            ||
| 704 | |||
| 705 | return ret  | 
            ||
| 706 | |||
| 707 | steps = [  | 
            ||
| 708 | self.add_title_line,  | 
            ||
| 709 | self.add_cpu_line,  | 
            ||
| 710 | self.maybe_add_cpu_affinity_line,  | 
            ||
| 711 | self.maybe_add_ionice_line,  | 
            ||
| 712 | self.add_memory_line,  | 
            ||
| 713 | self.add_io_and_network_lines,  | 
            ||
| 714 | append_newlines,  | 
            ||
| 715 | ]  | 
            ||
| 716 | |||
| 717 | functools.reduce(lambda ret, step: step(ret, prog), steps, ret)  | 
            ||
| 718 | |||
| 719 | View Code Duplication | def _msg_curse_header(self, ret, process_sort_key, args=None):  | 
            |
| 720 | """Build the header and add it to the ret dict."""  | 
            ||
| 721 | sort_style = 'SORT'  | 
            ||
| 722 | |||
| 723 | display_stats = [i for i in self.enable_stats if i not in glances_processes.disable_stats]  | 
            ||
| 724 | |||
| 725 | if 'cpu_percent' in display_stats:  | 
            ||
| 726 | if args.disable_irix and 0 < self.nb_log_core < 10:  | 
            ||
| 727 |                 msg = self.layout_header['cpu'].format('CPU%/' + str(self.nb_log_core)) | 
            ||
| 728 | elif args.disable_irix and self.nb_log_core != 0:  | 
            ||
| 729 |                 msg = self.layout_header['cpu'].format('CPUi') | 
            ||
| 730 | else:  | 
            ||
| 731 |                 msg = self.layout_header['cpu'].format('CPU%') | 
            ||
| 732 | ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'cpu_percent' else 'DEFAULT'))  | 
            ||
| 733 | |||
| 734 | if 'memory_percent' in display_stats:  | 
            ||
| 735 |             msg = self.layout_header['mem'].format('MEM%') | 
            ||
| 736 | ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'memory_percent' else 'DEFAULT'))  | 
            ||
| 737 | if 'memory_info' in display_stats:  | 
            ||
| 738 |             msg = self.layout_header['virt'].format('VIRT') | 
            ||
| 739 | ret.append(self.curse_add_line(msg, optional=True))  | 
            ||
| 740 |             msg = self.layout_header['res'].format('RES') | 
            ||
| 741 | ret.append(self.curse_add_line(msg, optional=True))  | 
            ||
| 742 | if 'pid' in display_stats:  | 
            ||
| 743 |             msg = self.layout_header['pid'].format('PID', width=self._max_pid_size()) | 
            ||
| 744 | ret.append(self.curse_add_line(msg))  | 
            ||
| 745 | if 'username' in display_stats:  | 
            ||
| 746 |             msg = self.layout_header['user'].format('USER') | 
            ||
| 747 | ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'username' else 'DEFAULT'))  | 
            ||
| 748 | if 'cpu_times' in display_stats:  | 
            ||
| 749 |             msg = self.layout_header['time'].format('TIME+') | 
            ||
| 750 | ret.append(  | 
            ||
| 751 | self.curse_add_line(msg, sort_style if process_sort_key == 'cpu_times' else 'DEFAULT', optional=True)  | 
            ||
| 752 | )  | 
            ||
| 753 | if 'num_threads' in display_stats:  | 
            ||
| 754 |             msg = self.layout_header['thread'].format('THR') | 
            ||
| 755 | ret.append(self.curse_add_line(msg))  | 
            ||
| 756 | if 'nice' in display_stats:  | 
            ||
| 757 |             msg = self.layout_header['nice'].format('NI') | 
            ||
| 758 | ret.append(self.curse_add_line(msg))  | 
            ||
| 759 | if 'status' in display_stats:  | 
            ||
| 760 |             msg = self.layout_header['status'].format('S') | 
            ||
| 761 | ret.append(self.curse_add_line(msg))  | 
            ||
| 762 | if 'io_counters' in display_stats:  | 
            ||
| 763 |             msg = self.layout_header['ior'].format('R/s') | 
            ||
| 764 | ret.append(  | 
            ||
| 765 | self.curse_add_line(  | 
            ||
| 766 | msg, sort_style if process_sort_key == 'io_counters' else 'DEFAULT', optional=True, additional=True  | 
            ||
| 767 | )  | 
            ||
| 768 | )  | 
            ||
| 769 |             msg = self.layout_header['iow'].format('W/s') | 
            ||
| 770 | ret.append(  | 
            ||
| 771 | self.curse_add_line(  | 
            ||
| 772 | msg, sort_style if process_sort_key == 'io_counters' else 'DEFAULT', optional=True, additional=True  | 
            ||
| 773 | )  | 
            ||
| 774 | )  | 
            ||
| 775 | if args.is_standalone and not args.disable_cursor:  | 
            ||
| 776 |             shortkey = "('e' to pin | 'k' to kill)" | 
            ||
| 777 | else:  | 
            ||
| 778 | shortkey = ""  | 
            ||
| 779 | if 'cmdline' in display_stats:  | 
            ||
| 780 |             msg = self.layout_header['command'].format("Command", shortkey) | 
            ||
| 781 | ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'name' else 'DEFAULT'))  | 
            ||
| 782 | |||
| 783 | View Code Duplication | def _msg_curse_sum(self, ret, sep_char='_', mmm=None, args=None):  | 
            |
| 784 | """  | 
            ||
| 785 | Build the sum message (only when filter is on) and add it to the ret dict.  | 
            ||
| 786 | |||
| 787 | :param ret: list of string where the message is added  | 
            ||
| 788 | :param sep_char: define the line separation char  | 
            ||
| 789 | :param mmm: display min, max, mean or current (if mmm=None)  | 
            ||
| 790 | :param args: Glances args  | 
            ||
| 791 | """  | 
            ||
| 792 | ret.append(self.curse_new_line())  | 
            ||
| 793 | if mmm is None:  | 
            ||
| 794 | ret.append(self.curse_add_line(sep_char * 69))  | 
            ||
| 795 | ret.append(self.curse_new_line())  | 
            ||
| 796 | # CPU percent sum  | 
            ||
| 797 | msg = ' '  | 
            ||
| 798 |         msg += self.layout_stat['cpu'].format(self._sum_stats('cpu_percent', mmm=mmm)) | 
            ||
| 799 | ret.append(self.curse_add_line(msg, decoration=self._mmm_deco(mmm)))  | 
            ||
| 800 | # MEM percent sum  | 
            ||
| 801 |         msg = self.layout_stat['mem'].format(self._sum_stats('memory_percent', mmm=mmm)) | 
            ||
| 802 | ret.append(self.curse_add_line(msg, decoration=self._mmm_deco(mmm)))  | 
            ||
| 803 | # VIRT and RES memory sum  | 
            ||
| 804 | if (  | 
            ||
| 805 | 'memory_info' in self.stats[0]  | 
            ||
| 806 | and self.stats[0]['memory_info'] is not None  | 
            ||
| 807 | and self.stats[0]['memory_info'] != ''  | 
            ||
| 808 | ):  | 
            ||
| 809 | # VMS  | 
            ||
| 810 | msg = self.layout_stat['virt'].format(  | 
            ||
| 811 |                 self.auto_unit(self._sum_stats('memory_info', sub_key='vms', mmm=mmm), low_precision=False) | 
            ||
| 812 | )  | 
            ||
| 813 | ret.append(self.curse_add_line(msg, decoration=self._mmm_deco(mmm), optional=True))  | 
            ||
| 814 | # RSS  | 
            ||
| 815 | msg = self.layout_stat['res'].format(  | 
            ||
| 816 |                 self.auto_unit(self._sum_stats('memory_info', sub_key='rss', mmm=mmm), low_precision=False) | 
            ||
| 817 | )  | 
            ||
| 818 | ret.append(self.curse_add_line(msg, decoration=self._mmm_deco(mmm), optional=True))  | 
            ||
| 819 | else:  | 
            ||
| 820 |             msg = self.layout_header['virt'].format('') | 
            ||
| 821 | ret.append(self.curse_add_line(msg))  | 
            ||
| 822 |             msg = self.layout_header['res'].format('') | 
            ||
| 823 | ret.append(self.curse_add_line(msg))  | 
            ||
| 824 | # PID  | 
            ||
| 825 |         msg = self.layout_header['pid'].format('', width=self._max_pid_size()) | 
            ||
| 826 | ret.append(self.curse_add_line(msg))  | 
            ||
| 827 | # USER  | 
            ||
| 828 |         msg = self.layout_header['user'].format('') | 
            ||
| 829 | ret.append(self.curse_add_line(msg))  | 
            ||
| 830 | # TIME+  | 
            ||
| 831 |         msg = self.layout_header['time'].format('') | 
            ||
| 832 | ret.append(self.curse_add_line(msg, optional=True))  | 
            ||
| 833 | # THREAD  | 
            ||
| 834 |         msg = self.layout_header['thread'].format('') | 
            ||
| 835 | ret.append(self.curse_add_line(msg))  | 
            ||
| 836 | # NICE  | 
            ||
| 837 |         msg = self.layout_header['nice'].format('') | 
            ||
| 838 | ret.append(self.curse_add_line(msg))  | 
            ||
| 839 | # STATUS  | 
            ||
| 840 |         msg = self.layout_header['status'].format('') | 
            ||
| 841 | ret.append(self.curse_add_line(msg))  | 
            ||
| 842 | # IO read/write  | 
            ||
| 843 | if 'io_counters' in self.stats[0] and mmm is None:  | 
            ||
| 844 | # IO read  | 
            ||
| 845 | io_rs = int(  | 
            ||
| 846 |                 (self._sum_stats('io_counters', 0) - self._sum_stats('io_counters', sub_key=2, mmm=mmm)) | 
            ||
| 847 | / self.stats[0]['time_since_update']  | 
            ||
| 848 | )  | 
            ||
| 849 | if io_rs == 0:  | 
            ||
| 850 |                 msg = self.layout_stat['ior'].format('0') | 
            ||
| 851 | else:  | 
            ||
| 852 | msg = self.layout_stat['ior'].format(self.auto_unit(io_rs, low_precision=True))  | 
            ||
| 853 | ret.append(self.curse_add_line(msg, decoration=self._mmm_deco(mmm), optional=True, additional=True))  | 
            ||
| 854 | # IO write  | 
            ||
| 855 | io_ws = int(  | 
            ||
| 856 |                 (self._sum_stats('io_counters', 1) - self._sum_stats('io_counters', sub_key=3, mmm=mmm)) | 
            ||
| 857 | / self.stats[0]['time_since_update']  | 
            ||
| 858 | )  | 
            ||
| 859 | if io_ws == 0:  | 
            ||
| 860 |                 msg = self.layout_stat['iow'].format('0') | 
            ||
| 861 | else:  | 
            ||
| 862 | msg = self.layout_stat['iow'].format(self.auto_unit(io_ws, low_precision=True))  | 
            ||
| 863 | ret.append(self.curse_add_line(msg, decoration=self._mmm_deco(mmm), optional=True, additional=True))  | 
            ||
| 864 | else:  | 
            ||
| 865 |             msg = self.layout_header['ior'].format('') | 
            ||
| 866 | ret.append(self.curse_add_line(msg, optional=True, additional=True))  | 
            ||
| 867 |             msg = self.layout_header['iow'].format('') | 
            ||
| 868 | ret.append(self.curse_add_line(msg, optional=True, additional=True))  | 
            ||
| 869 | if mmm is None:  | 
            ||
| 870 |             msg = '< {}'.format('current') | 
            ||
| 871 | ret.append(self.curse_add_line(msg, optional=True))  | 
            ||
| 872 | else:  | 
            ||
| 873 |             msg = f'< {mmm}' | 
            ||
| 874 | ret.append(self.curse_add_line(msg, optional=True))  | 
            ||
| 875 | msg = '(\'M\' to reset)'  | 
            ||
| 876 | ret.append(self.curse_add_line(msg, optional=True))  | 
            ||
| 877 | |||
| 878 | def _mmm_deco(self, mmm):  | 
            ||
| 879 | """Return the decoration string for the current mmm status."""  | 
            ||
| 880 | if mmm is not None:  | 
            ||
| 881 | return 'DEFAULT'  | 
            ||
| 882 | return 'FILTER'  | 
            ||
| 883 | |||
| 884 | def _mmm_reset(self):  | 
            ||
| 885 | """Reset the MMM stats."""  | 
            ||
| 886 |         self.mmm_min = {} | 
            ||
| 887 |         self.mmm_max = {} | 
            ||
| 888 | |||
| 889 | def _sum_stats(self, key, sub_key=None, mmm=None):  | 
            ||
| 890 | """Return the sum of the stats value for the given key.  | 
            ||
| 891 | |||
| 892 | :param sub_key: If sub_key is set, get the p[key][sub_key]  | 
            ||
| 893 | :param mmm: display min, max, mean or current (if mmm=None)  | 
            ||
| 894 | """  | 
            ||
| 895 | # Compute stats summary  | 
            ||
| 896 | ret = 0  | 
            ||
| 897 | for p in self.stats:  | 
            ||
| 898 | if key not in p:  | 
            ||
| 899 | # Correct issue #1188  | 
            ||
| 900 | continue  | 
            ||
| 901 | if p[key] is None:  | 
            ||
| 902 | # Correct https://github.com/nicolargo/glances/issues/1105#issuecomment-363553788  | 
            ||
| 903 | continue  | 
            ||
| 904 | if sub_key is None:  | 
            ||
| 905 | ret += p[key]  | 
            ||
| 906 | else:  | 
            ||
| 907 | ret += p[key][sub_key]  | 
            ||
| 908 | |||
| 909 | # Manage Min/Max/Mean  | 
            ||
| 910 | mmm_key = self._mmm_key(key, sub_key)  | 
            ||
| 911 | if mmm == 'min':  | 
            ||
| 912 | try:  | 
            ||
| 913 | if self.mmm_min[mmm_key] > ret:  | 
            ||
| 914 | self.mmm_min[mmm_key] = ret  | 
            ||
| 915 | except AttributeError:  | 
            ||
| 916 |                 self.mmm_min = {} | 
            ||
| 917 | return 0  | 
            ||
| 918 | except KeyError:  | 
            ||
| 919 | self.mmm_min[mmm_key] = ret  | 
            ||
| 920 | ret = self.mmm_min[mmm_key]  | 
            ||
| 921 | elif mmm == 'max':  | 
            ||
| 922 | try:  | 
            ||
| 923 | if self.mmm_max[mmm_key] < ret:  | 
            ||
| 924 | self.mmm_max[mmm_key] = ret  | 
            ||
| 925 | except AttributeError:  | 
            ||
| 926 |                 self.mmm_max = {} | 
            ||
| 927 | return 0  | 
            ||
| 928 | except KeyError:  | 
            ||
| 929 | self.mmm_max[mmm_key] = ret  | 
            ||
| 930 | ret = self.mmm_max[mmm_key]  | 
            ||
| 931 | |||
| 932 | return ret  | 
            ||
| 933 | |||
| 934 | def _mmm_key(self, key, sub_key):  | 
            ||
| 935 | ret = key  | 
            ||
| 936 | if sub_key is not None:  | 
            ||
| 937 | ret += str(sub_key)  | 
            ||
| 938 | return ret  | 
            ||
| 939 | |||
| 940 | def _sort_stats(self, sorted_by=None):  | 
            ||
| 941 | """Return the stats (dict) sorted by (sorted_by)."""  | 
            ||
| 942 | return sort_stats(self.stats, sorted_by, reverse=glances_processes.sort_reverse)  | 
            ||
| 943 | |||
| 944 | def _max_pid_size(self):  | 
            ||
| 945 | """Return the maximum PID size in number of char."""  | 
            ||
| 946 | if self.pid_max is not None:  | 
            ||
| 947 | return len(str(self.pid_max))  | 
            ||
| 948 | |||
| 949 | # By default return 5 (corresponding to 99999 PID number)  | 
            ||
| 950 | return 5  | 
            ||
| 951 |