glances_processlist.py 24.2 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."""
48 49 50 51 52 53 54 55 56 57
    # There is an issue in PsUtil for Electron/Atom processes (maybe others...)
    # Tracked by https://github.com/nicolargo/glances/issues/1192
    #            https://github.com/giampaolo/psutil/issues/1179
    # Add this dirty workarround (to be removed when the PsUtil is solved)
    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:
265 266
            # XXX: remove `cmdline != ['']` when we'll drop support for psutil<4.0.0
            if cmdline and cmdline != ['']:
267
                path, cmd, arguments = split_cmdline(cmdline)
268
                if os.path.isdir(path) and not args.process_short_name:
269
                    msg = ' {}'.format(path) + os.sep
D
desbma 已提交
270
                    ret.append(self.curse_add_line(msg, splittable=True))
271
                    ret.append(self.curse_add_line(cmd, decoration='PROCESS', splittable=True))
D
desbma 已提交
272
                else:
273
                    msg = ' {}'.format(cmd)
274
                    ret.append(self.curse_add_line(msg, decoration='PROCESS', splittable=True))
275
                if arguments:
276
                    msg = ' {}'.format(arguments)
277
                    ret.append(self.curse_add_line(msg, splittable=True))
278
            else:
279
                msg = ' {}'.format(p['name'])
280
                ret.append(self.curse_add_line(msg, splittable=True))
281
        except UnicodeEncodeError:
282
            ret.append(self.curse_add_line('', splittable=True))
D
desbma 已提交
283 284 285 286 287 288 289 290

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

        return ret

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

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

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

        # Header
369 370 371
        self.__msg_curse_header(ret, process_sort_key, args)

        # Process list
N
nicolargo 已提交
372 373 374 375 376 377 378 379 380 381 382 383 384
        # 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)
385 386 387 388 389

        # Return the message with decoration
        return ret

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

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

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

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

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

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

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

N
nicolargo 已提交
523 524
        * indice: If indice is set, get the p[key][indice]
        * mmm: display min, max, mean or current (if mmm=None)
525
        """
N
nicolargo 已提交
526
        # Compute stats summary
527 528
        ret = 0
        for p in self.stats:
529 530 531
            if key not in p:
                # Correct issue #1188
                continue
532 533 534
            if p[key] is None:
                # Correct https://github.com/nicolargo/glances/issues/1105#issuecomment-363553788
                continue
535 536 537 538
            if indice is None:
                ret += p[key]
            else:
                ret += p[key][indice]
N
nicolargo 已提交
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 567 568

        # 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 已提交
569
        return ret
570

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

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