glances_processlist.py 24.1 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:
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 284 285 286 287 288 289

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

        return ret

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

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

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

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

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

        # Return the message with decoration
        return ret

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

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

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

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

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

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

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

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

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

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

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