glances_processlist.py 30.9 KB
Newer Older
A
Alessio Sergi 已提交
1 2
# -*- coding: utf-8 -*-
#
3
# This file is part of Glances.
A
Alessio Sergi 已提交
4
#
N
nicolargo 已提交
5
# Copyright (C) 2021 Nicolargo <nicolas@nicolargo.com>
A
Alessio Sergi 已提交
6 7 8 9 10 11 12 13 14 15 16 17 18 19
#
# Glances is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Glances is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

A
PEP 257  
Alessio Sergi 已提交
20 21
"""Process list plugin."""

22
import os
N
nicolargo 已提交
23
import copy
A
Alessio Sergi 已提交
24

25
from glances.logger import logger
26
from glances.globals import WINDOWS
N
nicolargo 已提交
27
from glances.compat import key_exist_value_not_none_not_v
28
from glances.processes import glances_processes, sort_stats
29
from glances.plugins.glances_core import Plugin as CorePlugin
A
Alessio Sergi 已提交
30
from glances.plugins.glances_plugin import GlancesPlugin
A
Alessio Sergi 已提交
31

D
desbma 已提交
32

33 34 35 36
def seconds_to_hms(input_seconds):
    """Convert seconds to human-readable time."""
    minutes, seconds = divmod(input_seconds, 60)
    hours, minutes = divmod(minutes, 60)
37

38 39 40 41 42
    hours = int(hours)
    minutes = int(minutes)
    seconds = str(int(seconds)).zfill(2)

    return hours, minutes, seconds
43 44


45 46
def split_cmdline(cmdline):
    """Return path, cmd and arguments for a process cmdline."""
47 48
    path, cmd = os.path.split(cmdline[0])
    arguments = ' '.join(cmdline[1:])
49 50 51
    return path, cmd, arguments


A
Alessio Sergi 已提交
52
class Plugin(GlancesPlugin):
A
PEP 257  
Alessio Sergi 已提交
53
    """Glances' processes plugin.
A
Alessio Sergi 已提交
54 55 56 57

    stats is a list
    """

58 59 60 61 62 63 64 65 66 67 68 69 70 71
    # Define the header layout of the processes list columns
    layout_header = {
        'cpu': '{:<6} ',
        'mem': '{:<5} ',
        'virt': '{:<5} ',
        'res': '{:<5} ',
        'pid': '{:>{width}} ',
        'user': '{:<10} ',
        'time': '{:>8} ',
        'thread': '{:<3} ',
        'nice': '{:>3} ',
        'status': '{:>1} ',
        'ior': '{:>4} ',
        'iow': '{:<4} ',
72
        'command': '{} {}',
73 74 75 76
    }

    # Define the stat layout of the processes list columns
    layout_stat = {
N
nicolargo 已提交
77 78
        'cpu': '{:<6.1f}',
        'cpu_no_digit': '{:<6.0f}',
79 80 81 82 83 84 85 86 87 88 89 90
        'mem': '{:<5.1f} ',
        'virt': '{:<5} ',
        'res': '{:<5} ',
        'pid': '{:>{width}} ',
        'user': '{:<10} ',
        'time': '{:>8} ',
        'thread': '{:<3} ',
        'nice': '{:>3} ',
        'status': '{:>1} ',
        'ior': '{:>4} ',
        'iow': '{:<4} ',
        'command': '{}',
91
        'name': '[{}]'
92 93
    }

94
    def __init__(self, args=None, config=None):
A
PEP 257  
Alessio Sergi 已提交
95
        """Init the plugin."""
96
        super(Plugin, self).__init__(args=args,
97
                                     config=config,
98
                                     stats_init_value=[])
A
Alessio Sergi 已提交
99 100 101 102

        # We want to display the stat in the curse interface
        self.display_curse = True

103 104 105
        # Trying to display proc time
        self.tag_proc_time = True

106 107 108 109 110 111
        # Call CorePlugin to get the core number (needed when not in IRIX mode / Solaris mode)
        try:
            self.nb_log_core = CorePlugin(args=self.args).update()["log"]
        except Exception:
            self.nb_log_core = 0

112
        # Get the max values (dict)
N
nicolargo 已提交
113
        self.max_values = copy.deepcopy(glances_processes.max_values())
114

115 116 117 118
        # Get the maximum PID number
        # Use to optimize space (see https://github.com/nicolargo/glances/issues/959)
        self.pid_max = glances_processes.pid_max

119
        # Set the default sort key if it is defined in the configuration file
120 121
        if config is not None:
            if 'processlist' in config.as_dict() and 'sort_key' in config.as_dict()['processlist']:
N
nicolargo 已提交
122 123 124
                logger.debug(
                    'Configuration overwrites processes sort key by {}'.format(
                        config.as_dict()['processlist']['sort_key']))
125
                glances_processes.set_sort_key(config.as_dict()['processlist']['sort_key'], False)
126

127 128 129 130
        # The default sort key could also be overwrite by command line (see #1903)
        if args.sort_processes_key is not None:
            glances_processes.set_sort_key(args.sort_processes_key, False)

131
        # Note: 'glances_processes' is already init in the processes.py script
132

133
    def get_key(self):
A
PEP 257  
Alessio Sergi 已提交
134
        """Return the key of the list."""
135 136
        return 'pid'

137
    def update(self):
A
PEP 257  
Alessio Sergi 已提交
138
        """Update processes stats using the input method."""
139 140
        # Init new stats
        stats = self.get_init_value()
141

142
        if self.input_method == 'local':
143 144 145
            # Update stats using the standard system lib
            # Note: Update is done in the processcount plugin
            # Just return the processes list
146
            stats = glances_processes.getlist()
147

148
        elif self.input_method == 'snmp':
N
Nicolargo 已提交
149
            # No SNMP grab for processes
150
            pass
151

152 153 154 155 156 157 158
        # Update the stats
        self.stats = stats

        # Get the max values (dict)
        # Use Deep copy to avoid change between update and display
        self.max_values = copy.deepcopy(glances_processes.max_values())

159
        return self.stats
160

161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
    def get_nice_alert(self, value):
        """Return the alert relative to the Nice configuration list"""
        value = str(value)
        try:
            if value in self.get_limit('nice_critical'):
                return 'CRITICAL'
        except KeyError:
            pass
        try:
            if value in self.get_limit('nice_warning'):
                return 'WARNING'
        except KeyError:
            pass
        try:
            if value in self.get_limit('nice_careful'):
                return 'CAREFUL'
        except KeyError:
            pass
        return 'DEFAULT'

N
nicolargo 已提交
181 182 183 184
    def _get_process_curses_cpu(self, p, selected, args):
        """Return process CPU curses"""
        ret = ''
        if key_exist_value_not_none_not_v('cpu_percent', p, ''):
185
            cpu_layout = self.layout_stat['cpu'] if p['cpu_percent'] < 100 else self.layout_stat['cpu_no_digit']
186
            if args.disable_irix and self.nb_log_core != 0:
187 188
                msg = cpu_layout.format(
                    p['cpu_percent'] / float(self.nb_log_core))
189
            else:
190
                msg = cpu_layout.format(p['cpu_percent'])
191 192
            alert = self.get_alert(p['cpu_percent'],
                                   highlight_zero=False,
N
nicolargo 已提交
193 194
                                   is_max=(p['cpu_percent'] ==
                                           self.max_values['cpu_percent']),
195
                                   header="cpu")
N
nicolargo 已提交
196
            ret = self.curse_add_line(msg, alert)
D
desbma 已提交
197
        else:
198
            msg = self.layout_header['cpu'].format('?')
N
nicolargo 已提交
199 200 201 202 203 204 205
            ret = self.curse_add_line(msg)
        return ret

    def _get_process_curses_mem(self, p, selected, args):
        """Return process MEM curses"""
        ret = ''
        if key_exist_value_not_none_not_v('memory_percent', p, ''):
206
            msg = self.layout_stat['mem'].format(p['memory_percent'])
207 208
            alert = self.get_alert(p['memory_percent'],
                                   highlight_zero=False,
N
nicolargo 已提交
209 210
                                   is_max=(p['memory_percent'] ==
                                           self.max_values['memory_percent']),
211
                                   header="mem")
N
nicolargo 已提交
212
            ret = self.curse_add_line(msg, alert)
D
desbma 已提交
213
        else:
214
            msg = self.layout_header['mem'].format('?')
N
nicolargo 已提交
215 216 217 218 219 220 221 222 223 224
            ret = self.curse_add_line(msg)
        return ret

    def _get_process_curses_vms(self, p, selected, args):
        """Return process VMS curses"""
        ret = ''
        if key_exist_value_not_none_not_v('memory_info', p, ''):
            msg = self.layout_stat['virt'].format(
                self.auto_unit(p['memory_info'][1], low_precision=False))
            ret = self.curse_add_line(msg, optional=True)
D
desbma 已提交
225
        else:
226
            msg = self.layout_header['virt'].format('?')
N
nicolargo 已提交
227 228 229 230 231 232 233 234 235 236 237
            ret = self.curse_add_line(msg)
        return ret

    def _get_process_curses_rss(self, p, selected, args):
        """Return process RSS curses"""
        ret = ''
        if key_exist_value_not_none_not_v('memory_info', p, ''):
            msg = self.layout_stat['res'].format(
                self.auto_unit(p['memory_info'][0], low_precision=False))
            ret = self.curse_add_line(msg, optional=True)
        else:
238
            msg = self.layout_header['res'].format('?')
N
nicolargo 已提交
239 240 241 242 243 244
            ret = self.curse_add_line(msg)
        return ret

    def _get_process_curses_username(self, p, selected, args):
        """Return process username curses"""
        ret = ''
D
desbma 已提交
245 246
        if 'username' in p:
            # docker internal users are displayed as ints only, therefore str()
247
            # Correct issue #886 on Windows OS
248
            msg = self.layout_stat['user'].format(str(p['username'])[:9])
N
nicolargo 已提交
249
            ret = self.curse_add_line(msg)
250 251
        else:
            msg = self.layout_header['user'].format('?')
N
nicolargo 已提交
252 253 254 255 256 257
            ret = self.curse_add_line(msg)
        return ret

    def _get_process_curses_time(self, p, selected, args):
        """Return process time curses"""
        ret = ''
258
        try:
259 260
            # Sum user and system time
            user_system_time = p['cpu_times'][0] + p['cpu_times'][1]
N
nicolargo 已提交
261
        except (OverflowError, TypeError):
262 263 264 265 266 267
            # Catch OverflowError on some Amazon EC2 server
            # See https://github.com/nicolargo/glances/issues/87
            # Also catch TypeError on macOS
            # See: https://github.com/nicolargo/glances/issues/622
            # logger.debug("Cannot get TIME+ ({})".format(e))
            msg = self.layout_header['time'].format('?')
N
nicolargo 已提交
268
            ret = self.curse_add_line(msg, optional=True)
269
        else:
270
            hours, minutes, seconds = seconds_to_hms(user_system_time)
271 272 273 274 275 276 277 278
            if hours > 99:
                msg = '{:<7}h'.format(hours)
            elif 0 < hours < 100:
                msg = '{}h{}:{}'.format(hours, minutes, seconds)
            else:
                msg = '{}:{}'.format(minutes, seconds)
            msg = self.layout_stat['time'].format(msg)
            if hours > 0:
N
nicolargo 已提交
279 280 281
                ret = self.curse_add_line(msg,
                                          decoration='CPU_TIME',
                                          optional=True)
282
            else:
N
nicolargo 已提交
283 284 285 286 287 288
                ret = self.curse_add_line(msg, optional=True)
        return ret

    def _get_process_curses_thread(self, p, selected, args):
        """Return process thread curses"""
        ret = ''
289 290 291 292 293
        if 'num_threads' in p:
            num_threads = p['num_threads']
            if num_threads is None:
                num_threads = '?'
            msg = self.layout_stat['thread'].format(num_threads)
N
nicolargo 已提交
294
            ret = self.curse_add_line(msg)
D
desbma 已提交
295
        else:
296
            msg = self.layout_header['thread'].format('?')
N
nicolargo 已提交
297 298 299 300 301 302
            ret = self.curse_add_line(msg)
        return ret

    def _get_process_curses_nice(self, p, selected, args):
        """Return process nice curses"""
        ret = ''
D
desbma 已提交
303 304 305 306
        if 'nice' in p:
            nice = p['nice']
            if nice is None:
                nice = '?'
307
            msg = self.layout_stat['nice'].format(nice)
N
nicolargo 已提交
308 309
            ret = self.curse_add_line(msg,
                                      decoration=self.get_nice_alert(nice))
D
desbma 已提交
310
        else:
311
            msg = self.layout_header['nice'].format('?')
N
nicolargo 已提交
312 313 314 315 316 317
            ret = self.curse_add_line(msg)
        return ret

    def _get_process_curses_status(self, p, selected, args):
        """Return process status curses"""
        ret = ''
D
desbma 已提交
318 319
        if 'status' in p:
            status = p['status']
320
            msg = self.layout_stat['status'].format(status)
D
desbma 已提交
321
            if status == 'R':
N
nicolargo 已提交
322
                ret = self.curse_add_line(msg, decoration='STATUS')
D
desbma 已提交
323
            else:
N
nicolargo 已提交
324
                ret = self.curse_add_line(msg)
D
desbma 已提交
325
        else:
326
            msg = self.layout_header['status'].format('?')
N
nicolargo 已提交
327 328 329
            ret = self.curse_add_line(msg)
        return ret

330 331
    def _get_process_curses_io(self, p, selected, args, rorw='ior'):
        """Return process IO Read or Write curses"""
N
nicolargo 已提交
332 333 334 335
        ret = ''
        if 'io_counters' in p and \
           p['io_counters'][4] == 1 and \
           p['time_since_update'] != 0:
N
nicolargo 已提交
336
            # Display rate if stats is available and io_tag ([4]) == 1
337 338 339 340 341
            # IO
            io = int((p['io_counters'][0 if rorw == 'ior' else 1] - p['io_counters']
                      [2 if rorw == 'ior' else 3]) / p['time_since_update'])
            if io == 0:
                msg = self.layout_stat[rorw].format("0")
D
desbma 已提交
342
            else:
343 344
                msg = self.layout_stat[rorw].format(
                    self.auto_unit(io,
N
nicolargo 已提交
345 346 347
                                   low_precision=True))
            ret = self.curse_add_line(msg, optional=True, additional=True)
        else:
348
            msg = self.layout_header[rorw].format("?")
N
nicolargo 已提交
349 350 351
            ret = self.curse_add_line(msg, optional=True, additional=True)
        return ret

352 353 354 355
    def _get_process_curses_io_read(self, p, selected, args):
        """Return process IO Read curses"""
        return self._get_process_curses_io(p, selected, args, rorw='ior')

N
nicolargo 已提交
356 357
    def _get_process_curses_io_write(self, p, selected, args):
        """Return process IO Write curses"""
358
        return self._get_process_curses_io(p, selected, args, rorw='iow')
N
nicolargo 已提交
359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402

    def get_process_curses_data(self, p, selected, args):
        """Get curses data to display for a process.

        - p is the process to display
        - selected is a tag=True if the selected process
        """
        ret = [self.curse_new_line()]
        # When a process is selected:
        # * display a special caracter at the beginning of the line
        # * underline the command name
        if args.is_standalone:
            ret.append(self.curse_add_line('>' if selected else ' ', 'SELECTED'))

        # CPU
        ret.append(self._get_process_curses_cpu(p, selected, args))

        # MEM
        ret.append(self._get_process_curses_mem(p, selected, args))
        ret.append(self._get_process_curses_vms(p, selected, args))
        ret.append(self._get_process_curses_rss(p, selected, args))

        # PID
        msg = self.layout_stat['pid'].format(p['pid'], width=self.__max_pid_size())
        ret.append(self.curse_add_line(msg))

        # USER
        ret.append(self._get_process_curses_username(p, selected, args))

        # TIME+
        ret.append(self._get_process_curses_time(p, selected, args))

        # THREAD
        ret.append(self._get_process_curses_thread(p, selected, args))

        # NICE
        ret.append(self._get_process_curses_nice(p, selected, args))

        # STATUS
        ret.append(self._get_process_curses_status(p, selected, args))

        # IO read/write
        ret.append(self._get_process_curses_io_read(p, selected, args))
        ret.append(self._get_process_curses_io_write(p, selected, args))
D
desbma 已提交
403 404

        # Command line
405 406
        # If no command line for the process is available, fallback to
        # the bare process name instead
N
nicolargo 已提交
407 408 409 410
        if 'cmdline' in p:
            cmdline = p['cmdline']
        else:
            cmdline = '?'
411
        try:
412
            process_decoration = 'PROCESS_SELECTED' if (selected and args.is_standalone) else 'PROCESS'
A
Alessio Sergi 已提交
413
            if cmdline:
414
                path, cmd, arguments = split_cmdline(cmdline)
415 416 417
                # Manage end of line in arguments (see #1692)
                arguments.replace('\r\n', ' ')
                arguments.replace('\n', ' ')
418
                if os.path.isdir(path) and not args.process_short_name:
419
                    msg = self.layout_stat['command'].format(path) + os.sep
D
desbma 已提交
420
                    ret.append(self.curse_add_line(msg, splittable=True))
N
nicolargo 已提交
421 422
                    ret.append(self.curse_add_line(
                        cmd, decoration=process_decoration, splittable=True))
D
desbma 已提交
423
                else:
424
                    msg = self.layout_stat['command'].format(cmd)
N
nicolargo 已提交
425 426
                    ret.append(self.curse_add_line(
                        msg, decoration=process_decoration, splittable=True))
427
                if arguments:
428
                    msg = ' ' + self.layout_stat['command'].format(arguments)
429
                    ret.append(self.curse_add_line(msg, splittable=True))
430
            else:
431
                msg = self.layout_stat['name'].format(p['name'])
N
nicolargo 已提交
432
                ret.append(self.curse_add_line(msg, decoration=process_decoration, splittable=True))
433 434 435
        except (TypeError, UnicodeEncodeError) as e:
            # Avoid crach after running fine for several hours #1335
            logger.debug("Can not decode command line '{}' ({})".format(cmdline, e))
436
            ret.append(self.curse_add_line('', splittable=True))
D
desbma 已提交
437 438

        # Add extended stats but only for the top processes
439
        if args.cursor_position == 0 and 'extended_stats' in p and args.enable_process_extended:
D
desbma 已提交
440 441 442 443 444
            # Left padding
            xpad = ' ' * 13
            # First line is CPU affinity
            if 'cpu_affinity' in p and p['cpu_affinity'] is not None:
                ret.append(self.curse_new_line())
A
Alessio Sergi 已提交
445
                msg = xpad + 'CPU affinity: ' + str(len(p['cpu_affinity'])) + ' cores'
D
desbma 已提交
446 447
                ret.append(self.curse_add_line(msg, splittable=True))
            # Second line is memory info
448 449
            if 'memory_info' in p and \
               p['memory_info'] is not None:
D
desbma 已提交
450
                ret.append(self.curse_new_line())
451
                msg = '{}Memory info: {}'.format(xpad, p['memory_info'])
D
desbma 已提交
452
                if 'memory_swap' in p and p['memory_swap'] is not None:
453
                    msg += ' swap ' + self.auto_unit(p['memory_swap'], low_precision=False)
D
desbma 已提交
454 455 456 457
                ret.append(self.curse_add_line(msg, splittable=True))
            # Third line is for open files/network sessions
            msg = ''
            if 'num_threads' in p and p['num_threads'] is not None:
458
                msg += str(p['num_threads']) + ' threads '
D
desbma 已提交
459
            if 'num_fds' in p and p['num_fds'] is not None:
460
                msg += str(p['num_fds']) + ' files '
D
desbma 已提交
461
            if 'num_handles' in p and p['num_handles'] is not None:
462
                msg += str(p['num_handles']) + ' handles '
D
desbma 已提交
463
            if 'tcp' in p and p['tcp'] is not None:
464
                msg += str(p['tcp']) + ' TCP '
D
desbma 已提交
465
            if 'udp' in p and p['udp'] is not None:
466
                msg += str(p['udp']) + ' UDP'
D
desbma 已提交
467 468
            if msg != '':
                ret.append(self.curse_new_line())
A
Alessio Sergi 已提交
469
                msg = xpad + 'Open: ' + msg
D
desbma 已提交
470 471
                ret.append(self.curse_add_line(msg, splittable=True))
            # Fouth line is IO nice level (only Linux and Windows OS)
472 473 474
            if 'ionice' in p and \
               p['ionice'] is not None \
               and hasattr(p['ionice'], 'ioclass'):
D
desbma 已提交
475
                ret.append(self.curse_new_line())
A
Alessio Sergi 已提交
476 477
                msg = xpad + 'IO nice: '
                k = 'Class is '
D
desbma 已提交
478 479 480
                v = p['ionice'].ioclass
                # Linux: The scheduling class. 0 for none, 1 for real time, 2 for best-effort, 3 for idle.
                # Windows: On Windows only ioclass is used and it can be set to 2 (normal), 1 (low) or 0 (very low).
A
Alessio Sergi 已提交
481
                if WINDOWS:
D
desbma 已提交
482 483 484 485 486
                    if v == 0:
                        msg += k + 'Very Low'
                    elif v == 1:
                        msg += k + 'Low'
                    elif v == 2:
A
Alessio Sergi 已提交
487
                        msg += 'No specific I/O priority'
D
desbma 已提交
488 489 490 491
                    else:
                        msg += k + str(v)
                else:
                    if v == 0:
A
Alessio Sergi 已提交
492
                        msg += 'No specific I/O priority'
D
desbma 已提交
493 494 495 496 497 498 499 500 501 502 503
                    elif v == 1:
                        msg += k + 'Real Time'
                    elif v == 2:
                        msg += k + 'Best Effort'
                    elif v == 3:
                        msg += k + 'IDLE'
                    else:
                        msg += k + str(v)
                #  value is a number which goes from 0 to 7.
                # The higher the value, the lower the I/O priority of the process.
                if hasattr(p['ionice'], 'value') and p['ionice'].value != 0:
A
Alessio Sergi 已提交
504
                    msg += ' (value %s/7)' % str(p['ionice'].value)
D
desbma 已提交
505 506 507 508
                ret.append(self.curse_add_line(msg, splittable=True))

        return ret

509
    def msg_curse(self, args=None, max_width=None):
A
PEP 257  
Alessio Sergi 已提交
510
        """Return the dict to display in the curse interface."""
A
Alessio Sergi 已提交
511 512 513
        # Init the return message
        ret = []

514
        # Only process if stats exist and display plugin enable...
D
desbma 已提交
515
        if not self.stats or args.disable_process:
516 517
            return ret

A
Alessio Sergi 已提交
518
        # Compute the sort key
519
        process_sort_key = glances_processes.sort_key
A
Alessio Sergi 已提交
520 521

        # Header
522 523 524
        self.__msg_curse_header(ret, process_sort_key, args)

        # Process list
N
nicolargo 已提交
525
        # Loop over processes (sorted by the sort key previously compute)
526
        i = 0
N
nicolargo 已提交
527
        for p in self.__sort_stats(process_sort_key):
528 529 530
            ret.extend(self.get_process_curses_data(
                p, i == args.cursor_position, args))
            i += 1
N
nicolargo 已提交
531

532
        # A filter is set Display the stats summaries
N
nicolargo 已提交
533 534 535 536 537 538 539
        if glances_processes.process_filter is not None:
            if args.reset_minmax_tag:
                args.reset_minmax_tag = not args.reset_minmax_tag
                self.__mmm_reset()
            self.__msg_curse_sum(ret, args=args)
            self.__msg_curse_sum(ret, mmm='min', args=args)
            self.__msg_curse_sum(ret, mmm='max', args=args)
540 541 542 543 544

        # Return the message with decoration
        return ret

    def __msg_curse_header(self, ret, process_sort_key, args=None):
545
        """Build the header and add it to the ret dict."""
546 547
        sort_style = 'SORT'

548
        if args.disable_irix and 0 < self.nb_log_core < 10:
549
            msg = self.layout_header['cpu'].format('CPU%/' + str(self.nb_log_core))
550
        elif args.disable_irix and self.nb_log_core != 0:
551
            msg = self.layout_header['cpu'].format('CPU%/C')
552
        else:
553
            msg = self.layout_header['cpu'].format('CPU%')
A
Alessio Sergi 已提交
554
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'cpu_percent' else 'DEFAULT'))
555
        msg = self.layout_header['mem'].format('MEM%')
A
Alessio Sergi 已提交
556
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'memory_percent' else 'DEFAULT'))
557
        msg = self.layout_header['virt'].format('VIRT')
A
Alessio Sergi 已提交
558
        ret.append(self.curse_add_line(msg, optional=True))
559
        msg = self.layout_header['res'].format('RES')
A
Alessio Sergi 已提交
560
        ret.append(self.curse_add_line(msg, optional=True))
561
        msg = self.layout_header['pid'].format('PID', width=self.__max_pid_size())
562
        ret.append(self.curse_add_line(msg))
563
        msg = self.layout_header['user'].format('USER')
A
Alessio Sergi 已提交
564
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'username' else 'DEFAULT'))
565
        msg = self.layout_header['time'].format('TIME+')
N
nicolargo 已提交
566 567 568
        ret.append(self.curse_add_line(msg,
                                       sort_style if process_sort_key == 'cpu_times' else 'DEFAULT',
                                       optional=True))
569
        msg = self.layout_header['thread'].format('THR')
570
        ret.append(self.curse_add_line(msg))
571
        msg = self.layout_header['nice'].format('NI')
572
        ret.append(self.curse_add_line(msg))
573 574 575
        msg = self.layout_header['status'].format('S')
        ret.append(self.curse_add_line(msg))
        msg = self.layout_header['ior'].format('R/s')
N
nicolargo 已提交
576 577 578 579
        ret.append(self.curse_add_line(msg,
                                       sort_style if process_sort_key == 'io_counters' else 'DEFAULT',
                                       optional=True,
                                       additional=True))
580
        msg = self.layout_header['iow'].format('W/s')
N
nicolargo 已提交
581 582 583 584
        ret.append(self.curse_add_line(msg,
                                       sort_style if process_sort_key == 'io_counters' else 'DEFAULT',
                                       optional=True,
                                       additional=True))
585 586
        msg = self.layout_header['command'].format('Command',
                                                   "('k' to kill)" if args.is_standalone else "")
587
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'name' else 'DEFAULT'))
A
Alessio Sergi 已提交
588

N
nicolargo 已提交
589
    def __msg_curse_sum(self, ret, sep_char='_', mmm=None, args=None):
590
        """
591 592
        Build the sum message (only when filter is on) and add it to the ret dict.

N
nicolargo 已提交
593 594 595 596
        * ret: list of string where the message is added
        * sep_char: define the line separation char
        * mmm: display min, max, mean or current (if mmm=None)
        * args: Glances args
597 598
        """
        ret.append(self.curse_new_line())
N
nicolargo 已提交
599 600 601
        if mmm is None:
            ret.append(self.curse_add_line(sep_char * 69))
            ret.append(self.curse_new_line())
602
        # CPU percent sum
N
nicolargo 已提交
603
        msg = self.layout_stat['cpu'].format(self.__sum_stats('cpu_percent', mmm=mmm))
N
nicolargo 已提交
604 605
        ret.append(self.curse_add_line(msg,
                                       decoration=self.__mmm_deco(mmm)))
606
        # MEM percent sum
N
nicolargo 已提交
607
        msg = self.layout_stat['mem'].format(self.__sum_stats('memory_percent', mmm=mmm))
N
nicolargo 已提交
608 609
        ret.append(self.curse_add_line(msg,
                                       decoration=self.__mmm_deco(mmm)))
610
        # VIRT and RES memory sum
N
nicolargo 已提交
611 612
        if 'memory_info' in self.stats[0] and \
           self.stats[0]['memory_info'] is not None and self.stats[0]['memory_info'] != '':
613
            # VMS
N
nicolargo 已提交
614 615
            msg = self.layout_stat['virt'].format(self.auto_unit(self.__sum_stats('memory_info', indice=1, mmm=mmm),
                                                                 low_precision=False))
N
nicolargo 已提交
616 617 618
            ret.append(self.curse_add_line(msg,
                                           decoration=self.__mmm_deco(mmm),
                                           optional=True))
619
            # RSS
N
nicolargo 已提交
620 621
            msg = self.layout_stat['res'].format(self.auto_unit(self.__sum_stats('memory_info', indice=0, mmm=mmm),
                                                                low_precision=False))
N
nicolargo 已提交
622 623 624
            ret.append(self.curse_add_line(msg,
                                           decoration=self.__mmm_deco(mmm),
                                           optional=True))
D
desbma 已提交
625
        else:
N
nicolargo 已提交
626
            msg = self.layout_header['virt'].format('')
627
            ret.append(self.curse_add_line(msg))
N
nicolargo 已提交
628
            msg = self.layout_header['res'].format('')
629 630
            ret.append(self.curse_add_line(msg))
        # PID
N
nicolargo 已提交
631
        msg = self.layout_header['pid'].format('', width=self.__max_pid_size())
632 633
        ret.append(self.curse_add_line(msg))
        # USER
N
nicolargo 已提交
634 635 636 637 638 639 640
        msg = self.layout_header['user'].format('')
        ret.append(self.curse_add_line(msg))
        # TIME+
        msg = self.layout_header['time'].format('')
        ret.append(self.curse_add_line(msg, optional=True))
        # THREAD
        msg = self.layout_header['thread'].format('')
641 642
        ret.append(self.curse_add_line(msg))
        # NICE
N
nicolargo 已提交
643
        msg = self.layout_header['nice'].format('')
644 645
        ret.append(self.curse_add_line(msg))
        # STATUS
N
nicolargo 已提交
646
        msg = self.layout_header['status'].format('')
647 648
        ret.append(self.curse_add_line(msg))
        # IO read/write
N
nicolargo 已提交
649
        if 'io_counters' in self.stats[0] and mmm is None:
650
            # IO read
651 652
            io_rs = int((self.__sum_stats('io_counters', 0) - self.__sum_stats('io_counters',
                        indice=2, mmm=mmm)) / self.stats[0]['time_since_update'])
653
            if io_rs == 0:
N
nicolargo 已提交
654
                msg = self.layout_stat['ior'].format('0')
655
            else:
N
nicolargo 已提交
656
                msg = self.layout_stat['ior'].format(self.auto_unit(io_rs, low_precision=True))
N
nicolargo 已提交
657 658 659
            ret.append(self.curse_add_line(msg,
                                           decoration=self.__mmm_deco(mmm),
                                           optional=True, additional=True))
660
            # IO write
661 662
            io_ws = int((self.__sum_stats('io_counters', 1) - self.__sum_stats('io_counters',
                        indice=3, mmm=mmm)) / self.stats[0]['time_since_update'])
663
            if io_ws == 0:
N
nicolargo 已提交
664
                msg = self.layout_stat['iow'].format('0')
665
            else:
N
nicolargo 已提交
666
                msg = self.layout_stat['iow'].format(self.auto_unit(io_ws, low_precision=True))
N
nicolargo 已提交
667 668 669
            ret.append(self.curse_add_line(msg,
                                           decoration=self.__mmm_deco(mmm),
                                           optional=True, additional=True))
670
        else:
N
nicolargo 已提交
671
            msg = self.layout_header['ior'].format('')
672
            ret.append(self.curse_add_line(msg, optional=True, additional=True))
N
nicolargo 已提交
673
            msg = self.layout_header['iow'].format('')
674
            ret.append(self.curse_add_line(msg, optional=True, additional=True))
N
nicolargo 已提交
675
        if mmm is None:
676
            msg = ' < {}'.format('current')
N
nicolargo 已提交
677 678
            ret.append(self.curse_add_line(msg, optional=True))
        else:
679
            msg = ' < {}'.format(mmm)
680 681
            ret.append(self.curse_add_line(msg, optional=True))
            msg = ' (\'M\' to reset)'
N
nicolargo 已提交
682 683 684
            ret.append(self.curse_add_line(msg, optional=True))

    def __mmm_deco(self, mmm):
685
        """Return the decoration string for the current mmm status."""
N
nicolargo 已提交
686 687 688 689
        if mmm is not None:
            return 'DEFAULT'
        else:
            return 'FILTER'
N
Nicolargo 已提交
690

691
    def __mmm_reset(self):
692
        """Reset the MMM stats."""
693 694 695
        self.mmm_min = {}
        self.mmm_max = {}

N
nicolargo 已提交
696
    def __sum_stats(self, key, indice=None, mmm=None):
697 698
        """Return the sum of the stats value for the given key.

N
nicolargo 已提交
699 700
        * indice: If indice is set, get the p[key][indice]
        * mmm: display min, max, mean or current (if mmm=None)
701
        """
N
nicolargo 已提交
702
        # Compute stats summary
703 704
        ret = 0
        for p in self.stats:
705 706 707
            if key not in p:
                # Correct issue #1188
                continue
708 709 710
            if p[key] is None:
                # Correct https://github.com/nicolargo/glances/issues/1105#issuecomment-363553788
                continue
711 712 713 714
            if indice is None:
                ret += p[key]
            else:
                ret += p[key][indice]
N
nicolargo 已提交
715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744

        # Manage Min/Max/Mean
        mmm_key = self.__mmm_key(key, indice)
        if mmm == 'min':
            try:
                if self.mmm_min[mmm_key] > ret:
                    self.mmm_min[mmm_key] = ret
            except AttributeError:
                self.mmm_min = {}
                return 0
            except KeyError:
                self.mmm_min[mmm_key] = ret
            ret = self.mmm_min[mmm_key]
        elif mmm == 'max':
            try:
                if self.mmm_max[mmm_key] < ret:
                    self.mmm_max[mmm_key] = ret
            except AttributeError:
                self.mmm_max = {}
                return 0
            except KeyError:
                self.mmm_max[mmm_key] = ret
            ret = self.mmm_max[mmm_key]

        return ret

    def __mmm_key(self, key, indice):
        ret = key
        if indice is not None:
            ret += str(indice)
A
Alessio Sergi 已提交
745
        return ret
746

747
    def __sort_stats(self, sortedby=None):
748
        """Return the stats (dict) sorted by (sortedby)."""
749 750
        return sort_stats(self.stats, sortedby,
                          reverse=glances_processes.sort_reverse)
751 752

    def __max_pid_size(self):
753
        """Return the maximum PID size in number of char."""
754 755 756 757 758
        if self.pid_max is not None:
            return len(str(self.pid_max))
        else:
            # By default return 5 (corresponding to 99999 PID number)
            return 5