glances_processlist.py 31.1 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
        # Note: 'glances_processes' is already init in the processes.py script
128

129
    def get_key(self):
A
PEP 257  
Alessio Sergi 已提交
130
        """Return the key of the list."""
131 132
        return 'pid'

133
    def update(self):
A
PEP 257  
Alessio Sergi 已提交
134
        """Update processes stats using the input method."""
135 136
        # Init new stats
        stats = self.get_init_value()
137

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

144
        elif self.input_method == 'snmp':
N
Nicolargo 已提交
145
            # No SNMP grab for processes
146
            pass
147

148 149 150 151 152 153 154
        # 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())

155
        return self.stats
156

157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
    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 已提交
177 178 179 180
    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, ''):
181
            cpu_layout = self.layout_stat['cpu'] if p['cpu_percent'] < 100 else self.layout_stat['cpu_no_digit']
182
            if args.disable_irix and self.nb_log_core != 0:
183 184
                msg = cpu_layout.format(
                    p['cpu_percent'] / float(self.nb_log_core))
185
            else:
186
                msg = cpu_layout.format(p['cpu_percent'])
187 188
            alert = self.get_alert(p['cpu_percent'],
                                   highlight_zero=False,
N
nicolargo 已提交
189 190
                                   is_max=(p['cpu_percent'] ==
                                           self.max_values['cpu_percent']),
191
                                   header="cpu")
N
nicolargo 已提交
192
            ret = self.curse_add_line(msg, alert)
D
desbma 已提交
193
        else:
194
            msg = self.layout_header['cpu'].format('?')
N
nicolargo 已提交
195 196 197 198 199 200 201
            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, ''):
202
            msg = self.layout_stat['mem'].format(p['memory_percent'])
203 204
            alert = self.get_alert(p['memory_percent'],
                                   highlight_zero=False,
N
nicolargo 已提交
205 206
                                   is_max=(p['memory_percent'] ==
                                           self.max_values['memory_percent']),
207
                                   header="mem")
N
nicolargo 已提交
208
            ret = self.curse_add_line(msg, alert)
D
desbma 已提交
209
        else:
210
            msg = self.layout_header['mem'].format('?')
N
nicolargo 已提交
211 212 213 214 215 216 217 218 219 220
            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 已提交
221
        else:
222
            msg = self.layout_header['virt'].format('?')
N
nicolargo 已提交
223 224 225 226 227 228 229 230 231 232 233
            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:
234
            msg = self.layout_header['res'].format('?')
N
nicolargo 已提交
235 236 237 238 239 240
            ret = self.curse_add_line(msg)
        return ret

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

    def _get_process_curses_time(self, p, selected, args):
        """Return process time curses"""
        ret = ''
254
        try:
255 256
            # Sum user and system time
            user_system_time = p['cpu_times'][0] + p['cpu_times'][1]
N
nicolargo 已提交
257
        except (OverflowError, TypeError):
258 259 260 261 262 263
            # 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 已提交
264
            ret = self.curse_add_line(msg, optional=True)
265
        else:
266
            hours, minutes, seconds = seconds_to_hms(user_system_time)
267 268 269 270 271 272 273 274
            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 已提交
275 276 277
                ret = self.curse_add_line(msg,
                                          decoration='CPU_TIME',
                                          optional=True)
278
            else:
N
nicolargo 已提交
279 280 281 282 283 284
                ret = self.curse_add_line(msg, optional=True)
        return ret

    def _get_process_curses_thread(self, p, selected, args):
        """Return process thread curses"""
        ret = ''
285 286 287 288 289
        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 已提交
290
            ret = self.curse_add_line(msg)
D
desbma 已提交
291
        else:
292
            msg = self.layout_header['thread'].format('?')
N
nicolargo 已提交
293 294 295 296 297 298
            ret = self.curse_add_line(msg)
        return ret

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

    def _get_process_curses_status(self, p, selected, args):
        """Return process status curses"""
        ret = ''
D
desbma 已提交
314 315
        if 'status' in p:
            status = p['status']
316
            msg = self.layout_stat['status'].format(status)
D
desbma 已提交
317
            if status == 'R':
N
nicolargo 已提交
318
                ret = self.curse_add_line(msg, decoration='STATUS')
D
desbma 已提交
319
            else:
N
nicolargo 已提交
320
                ret = self.curse_add_line(msg)
D
desbma 已提交
321
        else:
322
            msg = self.layout_header['status'].format('?')
N
nicolargo 已提交
323 324 325 326 327 328 329 330 331
            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 已提交
332
            # Display rate if stats is available and io_tag ([4]) == 1
D
desbma 已提交
333
            # IO read
N
nicolargo 已提交
334 335
            io_rs = int((p['io_counters'][0] - p['io_counters']
                         [2]) / p['time_since_update'])
D
desbma 已提交
336
            if io_rs == 0:
337
                msg = self.layout_stat['ior'].format("0")
D
desbma 已提交
338
            else:
N
nicolargo 已提交
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
                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 已提交
358
            if io_ws == 0:
359
                msg = self.layout_stat['iow'].format("0")
D
desbma 已提交
360
            else:
N
nicolargo 已提交
361 362 363 364
                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 已提交
365
        else:
366
            msg = self.layout_header['iow'].format("?")
N
nicolargo 已提交
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 403 404 405 406 407 408 409 410 411 412
            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 已提交
413 414

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

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

        return ret

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

524
        # Only process if stats exist and display plugin enable...
D
desbma 已提交
525
        if not self.stats or args.disable_process:
526 527
            return ret

A
Alessio Sergi 已提交
528
        # Compute the sort key
529
        process_sort_key = glances_processes.sort_key
A
Alessio Sergi 已提交
530 531

        # Header
532 533 534
        self.__msg_curse_header(ret, process_sort_key, args)

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

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

        # Return the message with decoration
        return ret

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

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

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

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

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

699
    def __mmm_reset(self):
700
        """Reset the MMM stats."""
701 702 703
        self.mmm_min = {}
        self.mmm_max = {}

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

N
nicolargo 已提交
707 708
        * indice: If indice is set, get the p[key][indice]
        * mmm: display min, max, mean or current (if mmm=None)
709
        """
N
nicolargo 已提交
710
        # Compute stats summary
711 712
        ret = 0
        for p in self.stats:
713 714 715
            if key not in p:
                # Correct issue #1188
                continue
716 717 718
            if p[key] is None:
                # Correct https://github.com/nicolargo/glances/issues/1105#issuecomment-363553788
                continue
719 720 721 722
            if indice is None:
                ret += p[key]
            else:
                ret += p[key][indice]
N
nicolargo 已提交
723 724 725 726 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

        # 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 已提交
753
        return ret
754

755
    def __sort_stats(self, sortedby=None):
756
        """Return the stats (dict) sorted by (sortedby)."""
757 758
        return sort_stats(self.stats, sortedby,
                          reverse=glances_processes.sort_reverse)
759 760

    def __max_pid_size(self):
761
        """Return the maximum PID size in number of char."""
762 763 764 765 766
        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