glances_processlist.py 31.4 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 330 331 332 333 334 335
            ret = self.curse_add_line(msg)
        return ret

    def _get_process_curses_io_read(self, p, selected, args):
        """Return process IO Read curses"""
        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
D
desbma 已提交
337
            # IO read
N
nicolargo 已提交
338 339
            io_rs = int((p['io_counters'][0] - p['io_counters']
                         [2]) / p['time_since_update'])
D
desbma 已提交
340
            if io_rs == 0:
341
                msg = self.layout_stat['ior'].format("0")
D
desbma 已提交
342
            else:
N
nicolargo 已提交
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361
                msg = self.layout_stat['ior'].format(
                    self.auto_unit(io_rs,
                                   low_precision=True))
            ret = self.curse_add_line(msg, optional=True, additional=True)
        else:
            msg = self.layout_header['ior'].format("?")
            ret = self.curse_add_line(msg, optional=True, additional=True)
        return ret

    def _get_process_curses_io_write(self, p, selected, args):
        """Return process IO Write curses"""
        ret = ''
        if 'io_counters' in p and \
           p['io_counters'][4] == 1 and \
           p['time_since_update'] != 0:
            # Display rate if stats is available and io_tag ([4]) == 1
            # IO read
            io_ws = int((p['io_counters'][0] - p['io_counters']
                         [2]) / p['time_since_update'])
D
desbma 已提交
362
            if io_ws == 0:
363
                msg = self.layout_stat['iow'].format("0")
D
desbma 已提交
364
            else:
N
nicolargo 已提交
365 366 367 368
                msg = self.layout_stat['iow'].format(
                    self.auto_unit(io_ws,
                                   low_precision=True))
            ret = self.curse_add_line(msg, optional=True, additional=True)
D
desbma 已提交
369
        else:
370
            msg = self.layout_header['iow'].format("?")
N
nicolargo 已提交
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 403 404 405 406 407 408 409 410 411 412 413 414 415 416
            ret = self.curse_add_line(msg, optional=True, additional=True)
        return ret

    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 已提交
417 418

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

        # Add extended stats but only for the top processes
453
        if args.cursor_position == 0 and 'extended_stats' in p and args.enable_process_extended:
D
desbma 已提交
454 455 456 457 458
            # 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 已提交
459
                msg = xpad + 'CPU affinity: ' + str(len(p['cpu_affinity'])) + ' cores'
D
desbma 已提交
460 461
                ret.append(self.curse_add_line(msg, splittable=True))
            # Second line is memory info
462 463
            if 'memory_info' in p and \
               p['memory_info'] is not None:
D
desbma 已提交
464
                ret.append(self.curse_new_line())
465
                msg = '{}Memory info: {}'.format(xpad, p['memory_info'])
D
desbma 已提交
466
                if 'memory_swap' in p and p['memory_swap'] is not None:
467
                    msg += ' swap ' + self.auto_unit(p['memory_swap'], low_precision=False)
D
desbma 已提交
468 469 470 471
                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:
472
                msg += str(p['num_threads']) + ' threads '
D
desbma 已提交
473
            if 'num_fds' in p and p['num_fds'] is not None:
474
                msg += str(p['num_fds']) + ' files '
D
desbma 已提交
475
            if 'num_handles' in p and p['num_handles'] is not None:
476
                msg += str(p['num_handles']) + ' handles '
D
desbma 已提交
477
            if 'tcp' in p and p['tcp'] is not None:
478
                msg += str(p['tcp']) + ' TCP '
D
desbma 已提交
479
            if 'udp' in p and p['udp'] is not None:
480
                msg += str(p['udp']) + ' UDP'
D
desbma 已提交
481 482
            if msg != '':
                ret.append(self.curse_new_line())
A
Alessio Sergi 已提交
483
                msg = xpad + 'Open: ' + msg
D
desbma 已提交
484 485
                ret.append(self.curse_add_line(msg, splittable=True))
            # Fouth line is IO nice level (only Linux and Windows OS)
486 487 488
            if 'ionice' in p and \
               p['ionice'] is not None \
               and hasattr(p['ionice'], 'ioclass'):
D
desbma 已提交
489
                ret.append(self.curse_new_line())
A
Alessio Sergi 已提交
490 491
                msg = xpad + 'IO nice: '
                k = 'Class is '
D
desbma 已提交
492 493 494
                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 已提交
495
                if WINDOWS:
D
desbma 已提交
496 497 498 499 500
                    if v == 0:
                        msg += k + 'Very Low'
                    elif v == 1:
                        msg += k + 'Low'
                    elif v == 2:
A
Alessio Sergi 已提交
501
                        msg += 'No specific I/O priority'
D
desbma 已提交
502 503 504 505
                    else:
                        msg += k + str(v)
                else:
                    if v == 0:
A
Alessio Sergi 已提交
506
                        msg += 'No specific I/O priority'
D
desbma 已提交
507 508 509 510 511 512 513 514 515 516 517
                    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 已提交
518
                    msg += ' (value %s/7)' % str(p['ionice'].value)
D
desbma 已提交
519 520 521 522
                ret.append(self.curse_add_line(msg, splittable=True))

        return ret

523
    def msg_curse(self, args=None, max_width=None):
A
PEP 257  
Alessio Sergi 已提交
524
        """Return the dict to display in the curse interface."""
A
Alessio Sergi 已提交
525 526 527
        # Init the return message
        ret = []

528
        # Only process if stats exist and display plugin enable...
D
desbma 已提交
529
        if not self.stats or args.disable_process:
530 531
            return ret

A
Alessio Sergi 已提交
532
        # Compute the sort key
533
        process_sort_key = glances_processes.sort_key
A
Alessio Sergi 已提交
534 535

        # Header
536 537 538
        self.__msg_curse_header(ret, process_sort_key, args)

        # Process list
N
nicolargo 已提交
539
        # Loop over processes (sorted by the sort key previously compute)
540
        i = 0
N
nicolargo 已提交
541
        for p in self.__sort_stats(process_sort_key):
542 543 544
            ret.extend(self.get_process_curses_data(
                p, i == args.cursor_position, args))
            i += 1
N
nicolargo 已提交
545

546
        # A filter is set Display the stats summaries
N
nicolargo 已提交
547 548 549 550 551 552 553
        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)
554 555 556 557 558

        # Return the message with decoration
        return ret

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

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

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

N
nicolargo 已提交
607 608 609 610
        * 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
611 612
        """
        ret.append(self.curse_new_line())
N
nicolargo 已提交
613 614 615
        if mmm is None:
            ret.append(self.curse_add_line(sep_char * 69))
            ret.append(self.curse_new_line())
616
        # CPU percent sum
N
nicolargo 已提交
617
        msg = self.layout_stat['cpu'].format(self.__sum_stats('cpu_percent', mmm=mmm))
N
nicolargo 已提交
618 619
        ret.append(self.curse_add_line(msg,
                                       decoration=self.__mmm_deco(mmm)))
620
        # MEM percent sum
N
nicolargo 已提交
621
        msg = self.layout_stat['mem'].format(self.__sum_stats('memory_percent', mmm=mmm))
N
nicolargo 已提交
622 623
        ret.append(self.curse_add_line(msg,
                                       decoration=self.__mmm_deco(mmm)))
624
        # VIRT and RES memory sum
N
nicolargo 已提交
625 626
        if 'memory_info' in self.stats[0] and \
           self.stats[0]['memory_info'] is not None and self.stats[0]['memory_info'] != '':
627
            # VMS
N
nicolargo 已提交
628 629
            msg = self.layout_stat['virt'].format(self.auto_unit(self.__sum_stats('memory_info', indice=1, mmm=mmm),
                                                                 low_precision=False))
N
nicolargo 已提交
630 631 632
            ret.append(self.curse_add_line(msg,
                                           decoration=self.__mmm_deco(mmm),
                                           optional=True))
633
            # RSS
N
nicolargo 已提交
634 635
            msg = self.layout_stat['res'].format(self.auto_unit(self.__sum_stats('memory_info', indice=0, mmm=mmm),
                                                                low_precision=False))
N
nicolargo 已提交
636 637 638
            ret.append(self.curse_add_line(msg,
                                           decoration=self.__mmm_deco(mmm),
                                           optional=True))
D
desbma 已提交
639
        else:
N
nicolargo 已提交
640
            msg = self.layout_header['virt'].format('')
641
            ret.append(self.curse_add_line(msg))
N
nicolargo 已提交
642
            msg = self.layout_header['res'].format('')
643 644
            ret.append(self.curse_add_line(msg))
        # PID
N
nicolargo 已提交
645
        msg = self.layout_header['pid'].format('', width=self.__max_pid_size())
646 647
        ret.append(self.curse_add_line(msg))
        # USER
N
nicolargo 已提交
648 649 650 651 652 653 654
        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('')
655 656
        ret.append(self.curse_add_line(msg))
        # NICE
N
nicolargo 已提交
657
        msg = self.layout_header['nice'].format('')
658 659
        ret.append(self.curse_add_line(msg))
        # STATUS
N
nicolargo 已提交
660
        msg = self.layout_header['status'].format('')
661 662
        ret.append(self.curse_add_line(msg))
        # IO read/write
N
nicolargo 已提交
663
        if 'io_counters' in self.stats[0] and mmm is None:
664
            # IO read
N
nicolargo 已提交
665
            io_rs = int((self.__sum_stats('io_counters', 0) - self.__sum_stats('io_counters', indice=2, mmm=mmm)) / self.stats[0]['time_since_update'])
666
            if io_rs == 0:
N
nicolargo 已提交
667
                msg = self.layout_stat['ior'].format('0')
668
            else:
N
nicolargo 已提交
669
                msg = self.layout_stat['ior'].format(self.auto_unit(io_rs, low_precision=True))
N
nicolargo 已提交
670 671 672
            ret.append(self.curse_add_line(msg,
                                           decoration=self.__mmm_deco(mmm),
                                           optional=True, additional=True))
673
            # IO write
N
nicolargo 已提交
674
            io_ws = int((self.__sum_stats('io_counters', 1) - self.__sum_stats('io_counters', indice=3, mmm=mmm)) / self.stats[0]['time_since_update'])
675
            if io_ws == 0:
N
nicolargo 已提交
676
                msg = self.layout_stat['iow'].format('0')
677
            else:
N
nicolargo 已提交
678
                msg = self.layout_stat['iow'].format(self.auto_unit(io_ws, low_precision=True))
N
nicolargo 已提交
679 680 681
            ret.append(self.curse_add_line(msg,
                                           decoration=self.__mmm_deco(mmm),
                                           optional=True, additional=True))
682
        else:
N
nicolargo 已提交
683
            msg = self.layout_header['ior'].format('')
684
            ret.append(self.curse_add_line(msg, optional=True, additional=True))
N
nicolargo 已提交
685
            msg = self.layout_header['iow'].format('')
686
            ret.append(self.curse_add_line(msg, optional=True, additional=True))
N
nicolargo 已提交
687
        if mmm is None:
688
            msg = ' < {}'.format('current')
N
nicolargo 已提交
689 690
            ret.append(self.curse_add_line(msg, optional=True))
        else:
691
            msg = ' < {}'.format(mmm)
692 693
            ret.append(self.curse_add_line(msg, optional=True))
            msg = ' (\'M\' to reset)'
N
nicolargo 已提交
694 695 696
            ret.append(self.curse_add_line(msg, optional=True))

    def __mmm_deco(self, mmm):
697
        """Return the decoration string for the current mmm status."""
N
nicolargo 已提交
698 699 700 701
        if mmm is not None:
            return 'DEFAULT'
        else:
            return 'FILTER'
N
Nicolargo 已提交
702

703
    def __mmm_reset(self):
704
        """Reset the MMM stats."""
705 706 707
        self.mmm_min = {}
        self.mmm_max = {}

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

N
nicolargo 已提交
711 712
        * indice: If indice is set, get the p[key][indice]
        * mmm: display min, max, mean or current (if mmm=None)
713
        """
N
nicolargo 已提交
714
        # Compute stats summary
715 716
        ret = 0
        for p in self.stats:
717 718 719
            if key not in p:
                # Correct issue #1188
                continue
720 721 722
            if p[key] is None:
                # Correct https://github.com/nicolargo/glances/issues/1105#issuecomment-363553788
                continue
723 724 725 726
            if indice is None:
                ret += p[key]
            else:
                ret += p[key][indice]
N
nicolargo 已提交
727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756

        # 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 已提交
757
        return ret
758

759
    def __sort_stats(self, sortedby=None):
760
        """Return the stats (dict) sorted by (sortedby)."""
761 762
        return sort_stats(self.stats, sortedby,
                          reverse=glances_processes.sort_reverse)
763 764

    def __max_pid_size(self):
765
        """Return the maximum PID size in number of char."""
766 767 768 769 770
        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