glances_processlist.py 24.0 KB
Newer Older
A
Alessio Sergi 已提交
1 2
# -*- coding: utf-8 -*-
#
3
# This file is part of Glances.
A
Alessio Sergi 已提交
4
#
A
Alessio Sergi 已提交
5
# Copyright (C) 2017 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
23
import shlex
N
nicolargo 已提交
24
import copy
A
Alessio Sergi 已提交
25 26
from datetime import timedelta

27
from glances.compat import iteritems
28
from glances.globals import WINDOWS
29
from glances.logger import logger
30
from glances.processes import glances_processes, sort_stats
31
from glances.plugins.glances_core import Plugin as CorePlugin
A
Alessio Sergi 已提交
32
from glances.plugins.glances_plugin import GlancesPlugin
A
Alessio Sergi 已提交
33

D
desbma 已提交
34

35 36 37 38 39 40 41 42 43 44 45
def convert_timedelta(delta):
    """Convert timedelta to human-readable time."""
    days, total_seconds = delta.days, delta.seconds
    hours = days * 24 + total_seconds // 3600
    minutes = (total_seconds % 3600) // 60
    seconds = str(total_seconds % 60).zfill(2)
    microseconds = str(delta.microseconds)[:2].zfill(2)

    return hours, minutes, seconds, microseconds


46 47
def split_cmdline(cmdline):
    """Return path, cmd and arguments for a process cmdline."""
A
Alessio Sergi 已提交
48
    # There is an issue in psutil for Electron/Atom processes (maybe others...)
49 50
    # Tracked by https://github.com/nicolargo/glances/issues/1192
    #            https://github.com/giampaolo/psutil/issues/1179
A
Alessio Sergi 已提交
51
    # Add this dirty workarround (to be removed when the psutil is solved)
52 53 54 55 56 57
    if len(cmdline) == 1:
        cmdline = shlex.split(cmdline[0])
    # /End of the direty workarround

    path, cmd = os.path.split(cmdline[0])
    arguments = ' '.join(cmdline[1:])
58 59 60
    return path, cmd, arguments


A
Alessio Sergi 已提交
61
class Plugin(GlancesPlugin):
A
PEP 257  
Alessio Sergi 已提交
62
    """Glances' processes plugin.
A
Alessio Sergi 已提交
63 64 65 66

    stats is a list
    """

67
    def __init__(self, args=None):
A
PEP 257  
Alessio Sergi 已提交
68
        """Init the plugin."""
A
Alessio Sergi 已提交
69
        super(Plugin, self).__init__(args=args)
A
Alessio Sergi 已提交
70 71 72 73

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

74 75 76
        # Trying to display proc time
        self.tag_proc_time = True

77 78 79 80 81 82
        # 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

83
        # Get the max values (dict)
N
nicolargo 已提交
84
        self.max_values = copy.deepcopy(glances_processes.max_values())
85

86 87 88 89
        # Get the maximum PID number
        # Use to optimize space (see https://github.com/nicolargo/glances/issues/959)
        self.pid_max = glances_processes.pid_max

90
        # Note: 'glances_processes' is already init in the processes.py script
91

92
    def get_key(self):
A
PEP 257  
Alessio Sergi 已提交
93
        """Return the key of the list."""
94 95
        return 'pid'

96
    def reset(self):
A
PEP 257  
Alessio Sergi 已提交
97
        """Reset/init the stats."""
98
        self.stats = []
99 100

    def update(self):
A
PEP 257  
Alessio Sergi 已提交
101
        """Update processes stats using the input method."""
102 103
        # Reset stats
        self.reset()
104

105
        if self.input_method == 'local':
106 107 108
            # Update stats using the standard system lib
            # Note: Update is done in the processcount plugin
            # Just return the processes list
N
nicolargo 已提交
109
            self.stats = glances_processes.getlist()
110 111

            # Get the max values (dict)
N
nicolargo 已提交
112 113
            # Use Deep copy to avoid change between update and display
            self.max_values = copy.deepcopy(glances_processes.max_values())
114

115
        elif self.input_method == 'snmp':
N
Nicolargo 已提交
116
            # No SNMP grab for processes
117
            pass
118

119
        return self.stats
120

121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
    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'

D
desbma 已提交
141
    def get_process_curses_data(self, p, first, args):
142
        """Get curses data to display for a process.
143

144 145 146
        - p is the process to display
        - first is a tag=True if the process is the first on the list
        """
147
        ret = [self.curse_new_line()]
D
desbma 已提交
148 149
        # CPU
        if 'cpu_percent' in p and p['cpu_percent'] is not None and p['cpu_percent'] != '':
150
            if args.disable_irix and self.nb_log_core != 0:
151
                msg = '{:>6.1f}'.format(p['cpu_percent'] / float(self.nb_log_core))
152
            else:
153
                msg = '{:>6.1f}'.format(p['cpu_percent'])
154 155
            alert = self.get_alert(p['cpu_percent'],
                                   highlight_zero=False,
N
Nicolargo 已提交
156
                                   is_max=(p['cpu_percent'] == self.max_values['cpu_percent']),
157 158
                                   header="cpu")
            ret.append(self.curse_add_line(msg, alert))
D
desbma 已提交
159
        else:
160
            msg = '{:>6}'.format('?')
D
desbma 已提交
161 162 163
            ret.append(self.curse_add_line(msg))
        # MEM
        if 'memory_percent' in p and p['memory_percent'] is not None and p['memory_percent'] != '':
164
            msg = '{:>6.1f}'.format(p['memory_percent'])
165 166
            alert = self.get_alert(p['memory_percent'],
                                   highlight_zero=False,
N
Nicolargo 已提交
167
                                   is_max=(p['memory_percent'] == self.max_values['memory_percent']),
168 169
                                   header="mem")
            ret.append(self.curse_add_line(msg, alert))
D
desbma 已提交
170
        else:
171
            msg = '{:>6}'.format('?')
D
desbma 已提交
172 173 174 175
            ret.append(self.curse_add_line(msg))
        # VMS/RSS
        if 'memory_info' in p and p['memory_info'] is not None and p['memory_info'] != '':
            # VMS
176
            msg = '{:>6}'.format(self.auto_unit(p['memory_info'][1], low_precision=False))
D
desbma 已提交
177 178
            ret.append(self.curse_add_line(msg, optional=True))
            # RSS
179
            msg = '{:>6}'.format(self.auto_unit(p['memory_info'][0], low_precision=False))
D
desbma 已提交
180 181
            ret.append(self.curse_add_line(msg, optional=True))
        else:
182
            msg = '{:>6}'.format('?')
D
desbma 已提交
183 184 185
            ret.append(self.curse_add_line(msg))
            ret.append(self.curse_add_line(msg))
        # PID
186
        msg = '{:>{width}}'.format(p['pid'], width=self.__max_pid_size() + 1)
D
desbma 已提交
187 188 189 190
        ret.append(self.curse_add_line(msg))
        # USER
        if 'username' in p:
            # docker internal users are displayed as ints only, therefore str()
191
            # Correct issue #886 on Windows OS
192
            msg = ' {:9}'.format(str(p['username'])[:9])
D
desbma 已提交
193 194
            ret.append(self.curse_add_line(msg))
        else:
195
            msg = ' {:9}'.format('?')
D
desbma 已提交
196 197 198 199 200 201
            ret.append(self.curse_add_line(msg))
        # NICE
        if 'nice' in p:
            nice = p['nice']
            if nice is None:
                nice = '?'
202
            msg = '{:>5}'.format(nice)
203 204
            ret.append(self.curse_add_line(msg,
                                           decoration=self.get_nice_alert(nice)))
D
desbma 已提交
205
        else:
206
            msg = '{:>5}'.format('?')
D
desbma 已提交
207 208 209 210
            ret.append(self.curse_add_line(msg))
        # STATUS
        if 'status' in p:
            status = p['status']
211
            msg = '{:>2}'.format(status)
D
desbma 已提交
212 213 214 215 216
            if status == 'R':
                ret.append(self.curse_add_line(msg, decoration='STATUS'))
            else:
                ret.append(self.curse_add_line(msg))
        else:
217
            msg = '{:>2}'.format('?')
D
desbma 已提交
218 219
            ret.append(self.curse_add_line(msg))
        # TIME+
N
nicolargo 已提交
220 221 222 223 224 225 226 227
        try:
            delta = timedelta(seconds=sum(p['cpu_times']))
        except (OverflowError, TypeError) as e:
            # 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))
228
            msg = '{:>10}'.format('?')
N
nicolargo 已提交
229 230 231 232 233 234 235 236
        else:
            hours, minutes, seconds, microseconds = convert_timedelta(delta)
            if hours:
                msg = '{:>4}h'.format(hours)
                ret.append(self.curse_add_line(msg, decoration='CPU_TIME', optional=True))
                msg = '{}:{}'.format(str(minutes).zfill(2), seconds)
            else:
                msg = '{:>4}:{}.{}'.format(minutes, seconds, microseconds)
D
desbma 已提交
237 238
        ret.append(self.curse_add_line(msg, optional=True))
        # IO read/write
239
        if 'io_counters' in p and p['io_counters'][4] == 1 and p['time_since_update'] != 0:
N
nicolargo 已提交
240
            # Display rate if stats is available and io_tag ([4]) == 1
D
desbma 已提交
241
            # IO read
242
            io_rs = int((p['io_counters'][0] - p['io_counters'][2]) / p['time_since_update'])
D
desbma 已提交
243
            if io_rs == 0:
244
                msg = '{:>6}'.format("0")
D
desbma 已提交
245
            else:
246
                msg = '{:>6}'.format(self.auto_unit(io_rs, low_precision=True))
D
desbma 已提交
247 248
            ret.append(self.curse_add_line(msg, optional=True, additional=True))
            # IO write
249
            io_ws = int((p['io_counters'][1] - p['io_counters'][3]) / p['time_since_update'])
D
desbma 已提交
250
            if io_ws == 0:
251
                msg = '{:>6}'.format("0")
D
desbma 已提交
252
            else:
253
                msg = '{:>6}'.format(self.auto_unit(io_ws, low_precision=True))
D
desbma 已提交
254 255
            ret.append(self.curse_add_line(msg, optional=True, additional=True))
        else:
256
            msg = '{:>6}'.format("?")
D
desbma 已提交
257 258 259 260
            ret.append(self.curse_add_line(msg, optional=True, additional=True))
            ret.append(self.curse_add_line(msg, optional=True, additional=True))

        # Command line
261 262 263
        # If no command line for the process is available, fallback to
        # the bare process name instead
        cmdline = p['cmdline']
264
        try:
A
Alessio Sergi 已提交
265
            if cmdline:
266
                path, cmd, arguments = split_cmdline(cmdline)
267
                if os.path.isdir(path) and not args.process_short_name:
268
                    msg = ' {}'.format(path) + os.sep
D
desbma 已提交
269
                    ret.append(self.curse_add_line(msg, splittable=True))
270
                    ret.append(self.curse_add_line(cmd, decoration='PROCESS', splittable=True))
D
desbma 已提交
271
                else:
272
                    msg = ' {}'.format(cmd)
273
                    ret.append(self.curse_add_line(msg, decoration='PROCESS', splittable=True))
274
                if arguments:
275
                    msg = ' {}'.format(arguments)
276
                    ret.append(self.curse_add_line(msg, splittable=True))
277
            else:
278
                msg = ' {}'.format(p['name'])
279
                ret.append(self.curse_add_line(msg, splittable=True))
280
        except UnicodeEncodeError:
281
            ret.append(self.curse_add_line('', splittable=True))
D
desbma 已提交
282 283

        # Add extended stats but only for the top processes
284
        if first and 'extended_stats' in p and args.enable_process_extended:
D
desbma 已提交
285 286 287 288 289
            # 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 已提交
290
                msg = xpad + 'CPU affinity: ' + str(len(p['cpu_affinity'])) + ' cores'
D
desbma 已提交
291 292
                ret.append(self.curse_add_line(msg, splittable=True))
            # Second line is memory info
293 294
            if 'memory_info' in p and \
               p['memory_info'] is not None:
D
desbma 已提交
295
                ret.append(self.curse_new_line())
296
                msg = '{}Memory info: {}'.format(xpad, p['memory_info'])
D
desbma 已提交
297
                if 'memory_swap' in p and p['memory_swap'] is not None:
298
                    msg += ' swap ' + self.auto_unit(p['memory_swap'], low_precision=False)
D
desbma 已提交
299 300 301 302
                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:
303
                msg += str(p['num_threads']) + ' threads '
D
desbma 已提交
304
            if 'num_fds' in p and p['num_fds'] is not None:
305
                msg += str(p['num_fds']) + ' files '
D
desbma 已提交
306
            if 'num_handles' in p and p['num_handles'] is not None:
307
                msg += str(p['num_handles']) + ' handles '
D
desbma 已提交
308
            if 'tcp' in p and p['tcp'] is not None:
309
                msg += str(p['tcp']) + ' TCP '
D
desbma 已提交
310
            if 'udp' in p and p['udp'] is not None:
311
                msg += str(p['udp']) + ' UDP'
D
desbma 已提交
312 313
            if msg != '':
                ret.append(self.curse_new_line())
A
Alessio Sergi 已提交
314
                msg = xpad + 'Open: ' + msg
D
desbma 已提交
315 316
                ret.append(self.curse_add_line(msg, splittable=True))
            # Fouth line is IO nice level (only Linux and Windows OS)
317 318 319
            if 'ionice' in p and \
               p['ionice'] is not None \
               and hasattr(p['ionice'], 'ioclass'):
D
desbma 已提交
320
                ret.append(self.curse_new_line())
A
Alessio Sergi 已提交
321 322
                msg = xpad + 'IO nice: '
                k = 'Class is '
D
desbma 已提交
323 324 325
                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 已提交
326
                if WINDOWS:
D
desbma 已提交
327 328 329 330 331
                    if v == 0:
                        msg += k + 'Very Low'
                    elif v == 1:
                        msg += k + 'Low'
                    elif v == 2:
A
Alessio Sergi 已提交
332
                        msg += 'No specific I/O priority'
D
desbma 已提交
333 334 335 336
                    else:
                        msg += k + str(v)
                else:
                    if v == 0:
A
Alessio Sergi 已提交
337
                        msg += 'No specific I/O priority'
D
desbma 已提交
338 339 340 341 342 343 344 345 346 347 348
                    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 已提交
349
                    msg += ' (value %s/7)' % str(p['ionice'].value)
D
desbma 已提交
350 351 352 353
                ret.append(self.curse_add_line(msg, splittable=True))

        return ret

354
    def msg_curse(self, args=None, max_width=None):
A
PEP 257  
Alessio Sergi 已提交
355
        """Return the dict to display in the curse interface."""
A
Alessio Sergi 已提交
356 357 358
        # Init the return message
        ret = []

359
        # Only process if stats exist and display plugin enable...
D
desbma 已提交
360
        if not self.stats or args.disable_process:
361 362
            return ret

A
Alessio Sergi 已提交
363
        # Compute the sort key
364
        process_sort_key = glances_processes.sort_key
A
Alessio Sergi 已提交
365 366

        # Header
367 368 369
        self.__msg_curse_header(ret, process_sort_key, args)

        # Process list
N
nicolargo 已提交
370 371 372 373 374 375 376 377 378 379 380 381 382
        # Loop over processes (sorted by the sort key previously compute)
        first = True
        for p in self.__sort_stats(process_sort_key):
            ret.extend(self.get_process_curses_data(p, first, args))
            # End of extended stats
            first = False
        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)
383 384 385 386 387

        # Return the message with decoration
        return ret

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

391
        if args.disable_irix and 0 < self.nb_log_core < 10:
392
            msg = '{:>6}'.format('CPU%/' + str(self.nb_log_core))
393
        elif args.disable_irix and self.nb_log_core != 0:
394
            msg = '{:>6}'.format('CPU%/C')
395
        else:
396
            msg = '{:>6}'.format('CPU%')
A
Alessio Sergi 已提交
397
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'cpu_percent' else 'DEFAULT'))
398
        msg = '{:>6}'.format('MEM%')
A
Alessio Sergi 已提交
399
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'memory_percent' else 'DEFAULT'))
400
        msg = '{:>6}'.format('VIRT')
A
Alessio Sergi 已提交
401
        ret.append(self.curse_add_line(msg, optional=True))
402
        msg = '{:>6}'.format('RES')
A
Alessio Sergi 已提交
403
        ret.append(self.curse_add_line(msg, optional=True))
404
        msg = '{:>{width}}'.format('PID', width=self.__max_pid_size() + 1)
405
        ret.append(self.curse_add_line(msg))
406
        msg = ' {:10}'.format('USER')
A
Alessio Sergi 已提交
407
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'username' else 'DEFAULT'))
408
        msg = '{:>4}'.format('NI')
409
        ret.append(self.curse_add_line(msg))
410
        msg = '{:>2}'.format('S')
411
        ret.append(self.curse_add_line(msg))
412
        msg = '{:>10}'.format('TIME+')
413
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'cpu_times' else 'DEFAULT', optional=True))
414
        msg = '{:>6}'.format('R/s')
415
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'io_counters' else 'DEFAULT', optional=True, additional=True))
416
        msg = '{:>6}'.format('W/s')
417
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'io_counters' else 'DEFAULT', optional=True, additional=True))
418
        msg = ' {:8}'.format('Command')
419
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'name' else 'DEFAULT'))
A
Alessio Sergi 已提交
420

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

N
nicolargo 已提交
425 426 427 428
        * 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
429 430
        """
        ret.append(self.curse_new_line())
N
nicolargo 已提交
431 432 433
        if mmm is None:
            ret.append(self.curse_add_line(sep_char * 69))
            ret.append(self.curse_new_line())
434
        # CPU percent sum
435
        msg = '{:>6.1f}'.format(self.__sum_stats('cpu_percent', mmm=mmm))
N
nicolargo 已提交
436 437
        ret.append(self.curse_add_line(msg,
                                       decoration=self.__mmm_deco(mmm)))
438
        # MEM percent sum
439
        msg = '{:>6.1f}'.format(self.__sum_stats('memory_percent', mmm=mmm))
N
nicolargo 已提交
440 441
        ret.append(self.curse_add_line(msg,
                                       decoration=self.__mmm_deco(mmm)))
442 443 444
        # VIRT and RES memory sum
        if 'memory_info' in self.stats[0] and self.stats[0]['memory_info'] is not None and self.stats[0]['memory_info'] != '':
            # VMS
445
            msg = '{:>6}'.format(self.auto_unit(self.__sum_stats('memory_info', indice=1, mmm=mmm), low_precision=False))
N
nicolargo 已提交
446 447 448
            ret.append(self.curse_add_line(msg,
                                           decoration=self.__mmm_deco(mmm),
                                           optional=True))
449
            # RSS
450
            msg = '{:>6}'.format(self.auto_unit(self.__sum_stats('memory_info', indice=0, mmm=mmm), low_precision=False))
N
nicolargo 已提交
451 452 453
            ret.append(self.curse_add_line(msg,
                                           decoration=self.__mmm_deco(mmm),
                                           optional=True))
D
desbma 已提交
454
        else:
455
            msg = '{:>6}'.format('')
456 457 458
            ret.append(self.curse_add_line(msg))
            ret.append(self.curse_add_line(msg))
        # PID
459
        msg = '{:>6}'.format('')
460 461
        ret.append(self.curse_add_line(msg))
        # USER
462
        msg = ' {:9}'.format('')
463 464
        ret.append(self.curse_add_line(msg))
        # NICE
465
        msg = '{:>5}'.format('')
466 467
        ret.append(self.curse_add_line(msg))
        # STATUS
468
        msg = '{:>2}'.format('')
469 470
        ret.append(self.curse_add_line(msg))
        # TIME+
471
        msg = '{:>10}'.format('')
472 473
        ret.append(self.curse_add_line(msg, optional=True))
        # IO read/write
N
nicolargo 已提交
474
        if 'io_counters' in self.stats[0] and mmm is None:
475
            # IO read
N
nicolargo 已提交
476
            io_rs = int((self.__sum_stats('io_counters', 0) - self.__sum_stats('io_counters', indice=2, mmm=mmm)) / self.stats[0]['time_since_update'])
477
            if io_rs == 0:
478
                msg = '{:>6}'.format('0')
479
            else:
480
                msg = '{:>6}'.format(self.auto_unit(io_rs, low_precision=True))
N
nicolargo 已提交
481 482 483
            ret.append(self.curse_add_line(msg,
                                           decoration=self.__mmm_deco(mmm),
                                           optional=True, additional=True))
484
            # IO write
N
nicolargo 已提交
485
            io_ws = int((self.__sum_stats('io_counters', 1) - self.__sum_stats('io_counters', indice=3, mmm=mmm)) / self.stats[0]['time_since_update'])
486
            if io_ws == 0:
487
                msg = '{:>6}'.format('0')
488
            else:
489
                msg = '{:>6}'.format(self.auto_unit(io_ws, low_precision=True))
N
nicolargo 已提交
490 491 492
            ret.append(self.curse_add_line(msg,
                                           decoration=self.__mmm_deco(mmm),
                                           optional=True, additional=True))
493
        else:
494
            msg = '{:>6}'.format('')
495 496
            ret.append(self.curse_add_line(msg, optional=True, additional=True))
            ret.append(self.curse_add_line(msg, optional=True, additional=True))
N
nicolargo 已提交
497
        if mmm is None:
498
            msg = ' < {}'.format('current')
N
nicolargo 已提交
499 500
            ret.append(self.curse_add_line(msg, optional=True))
        else:
501
            msg = ' < {}'.format(mmm)
502 503
            ret.append(self.curse_add_line(msg, optional=True))
            msg = ' (\'M\' to reset)'
N
nicolargo 已提交
504 505 506
            ret.append(self.curse_add_line(msg, optional=True))

    def __mmm_deco(self, mmm):
507
        """Return the decoration string for the current mmm status."""
N
nicolargo 已提交
508 509 510 511
        if mmm is not None:
            return 'DEFAULT'
        else:
            return 'FILTER'
N
Nicolargo 已提交
512

513
    def __mmm_reset(self):
514
        """Reset the MMM stats."""
515 516 517
        self.mmm_min = {}
        self.mmm_max = {}

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

N
nicolargo 已提交
521 522
        * indice: If indice is set, get the p[key][indice]
        * mmm: display min, max, mean or current (if mmm=None)
523
        """
N
nicolargo 已提交
524
        # Compute stats summary
525 526
        ret = 0
        for p in self.stats:
527 528 529
            if key not in p:
                # Correct issue #1188
                continue
530 531 532
            if p[key] is None:
                # Correct https://github.com/nicolargo/glances/issues/1105#issuecomment-363553788
                continue
533 534 535 536
            if indice is None:
                ret += p[key]
            else:
                ret += p[key][indice]
N
nicolargo 已提交
537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566

        # 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 已提交
567
        return ret
568

569
    def __sort_stats(self, sortedby=None):
570
        """Return the stats (dict) sorted by (sortedby)."""
571 572
        return sort_stats(self.stats, sortedby,
                          reverse=glances_processes.sort_reverse)
573 574

    def __max_pid_size(self):
575
        """Return the maximum PID size in number of char."""
576 577 578 579 580
        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