Test Failed
Push — master ( 4c6c3d...040528 )
by Nicolas
04:27
created

glances/plugins/glances_processlist.py (9 issues)

1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of Glances.
4
#
5
# Copyright (C) 2019 Nicolargo <[email protected]>
6
#
7
# Glances is free software; you can redistribute it and/or modify
8
# it under the terms of the GNU Lesser General Public License as published by
9
# the Free Software Foundation, either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Glances is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU Lesser General Public License for more details.
16
#
17
# You should have received a copy of the GNU Lesser General Public License
18
# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20
"""Process list plugin."""
21
22
import os
23
import shlex
24
import copy
25
from datetime import timedelta
26
27
from glances.logger import logger
28
from glances.globals import WINDOWS
29
from glances.processes import glances_processes, sort_stats
30
from glances.plugins.glances_core import Plugin as CorePlugin
31
from glances.plugins.glances_plugin import GlancesPlugin
32
33
34
def seconds_to_hms(input_seconds):
35
    """Convert seconds to human-readable time."""
36
    minutes, seconds = divmod(input_seconds, 60)
37
    hours, minutes = divmod(minutes, 60)
38
39
    hours = int(hours)
40
    minutes = int(minutes)
41
    seconds = str(int(seconds)).zfill(2)
42
43
    return hours, minutes, seconds
44
45
46
def split_cmdline(cmdline):
47
    """Return path, cmd and arguments for a process cmdline."""
48
    path, cmd = os.path.split(cmdline[0])
49
    arguments = ' '.join(cmdline[1:])
50
    return path, cmd, arguments
51
52
53
class Plugin(GlancesPlugin):
54
    """Glances' processes plugin.
55
56
    stats is a list
57
    """
58
59
    # Define the header layout of the processes list columns
60
    layout_header = {
61
        'cpu': '{:<6} ',
62
        'mem': '{:<5} ',
63
        'virt': '{:<5} ',
64
        'res': '{:<5} ',
65
        'pid': '{:>{width}} ',
66
        'user': '{:<10} ',
67
        'time': '{:>8} ',
68
        'thread': '{:<3} ',
69
        'nice': '{:>3} ',
70
        'status': '{:>1} ',
71
        'ior': '{:>4} ',
72
        'iow': '{:<4} ',
73
        'command': '{} {}',
74
    }
75
76
    # Define the stat layout of the processes list columns
77
    layout_stat = {
78
        'cpu': '{:<6.1f}',
79
        'cpu_no_digit': '{:<6.0f}',
80
        'mem': '{:<5.1f} ',
81
        'virt': '{:<5} ',
82
        'res': '{:<5} ',
83
        'pid': '{:>{width}} ',
84
        'user': '{:<10} ',
85
        'time': '{:>8} ',
86
        'thread': '{:<3} ',
87
        'nice': '{:>3} ',
88
        'status': '{:>1} ',
89
        'ior': '{:>4} ',
90
        'iow': '{:<4} ',
91
        'command': '{}',
92
        'name': '[{}]'
93
    }
94
95
    def __init__(self, args=None, config=None):
96
        """Init the plugin."""
97
        super(Plugin, self).__init__(args=args,
98
                                     config=config,
99
                                     stats_init_value=[])
100
101
        # We want to display the stat in the curse interface
102
        self.display_curse = True
103
104
        # Trying to display proc time
105
        self.tag_proc_time = True
106
107
        # Call CorePlugin to get the core number (needed when not in IRIX mode / Solaris mode)
108
        try:
109
            self.nb_log_core = CorePlugin(args=self.args).update()["log"]
110
        except Exception:
111
            self.nb_log_core = 0
112
113
        # Get the max values (dict)
114
        self.max_values = copy.deepcopy(glances_processes.max_values())
115
116
        # Get the maximum PID number
117
        # Use to optimize space (see https://github.com/nicolargo/glances/issues/959)
118
        self.pid_max = glances_processes.pid_max
119
120
        # Set the default sort key if it is defined in the configuration file
121
        if config is not None:
122
            if 'processlist' in config.as_dict() and 'sort_key' in config.as_dict()['processlist']:
123
                logger.debug('Configuration overwrites processes sort key by {}'.format(config.as_dict()['processlist']['sort_key']))
124
                glances_processes.set_sort_key(config.as_dict()['processlist']['sort_key'], False)
125
126
        # Note: 'glances_processes' is already init in the processes.py script
127
128
    def get_key(self):
129
        """Return the key of the list."""
130
        return 'pid'
131
132
    def update(self):
133
        """Update processes stats using the input method."""
134
        # Init new stats
135
        stats = self.get_init_value()
136
137
        if self.input_method == 'local':
138
            # Update stats using the standard system lib
139
            # Note: Update is done in the processcount plugin
140
            # Just return the processes list
141
            stats = glances_processes.getlist()
142
143
        elif self.input_method == 'snmp':
144
            # No SNMP grab for processes
145
            pass
146
147
        # Update the stats
148
        self.stats = stats
149
150
        # Get the max values (dict)
151
        # Use Deep copy to avoid change between update and display
152
        self.max_values = copy.deepcopy(glances_processes.max_values())
153
154
        return self.stats
155
156
    def get_nice_alert(self, value):
157
        """Return the alert relative to the Nice configuration list"""
158
        value = str(value)
159
        try:
160
            if value in self.get_limit('nice_critical'):
161
                return 'CRITICAL'
162
        except KeyError:
163
            pass
164
        try:
165
            if value in self.get_limit('nice_warning'):
166
                return 'WARNING'
167
        except KeyError:
168
            pass
169
        try:
170
            if value in self.get_limit('nice_careful'):
171
                return 'CAREFUL'
172
        except KeyError:
173
            pass
174
        return 'DEFAULT'
175
176
    def get_process_curses_data(self, p, selected, args):
0 ignored issues
show
Coding Style Naming introduced by
The name p does not conform to the argument naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Comprehensibility introduced by
This function exceeds the maximum number of variables (26/15).
Loading history...
177
        """Get curses data to display for a process.
178
179
        - p is the process to display
180
        - selected is a tag=True if the selected process
181
        """
182
        ret = [self.curse_new_line()]
183
        # When a process is selected:
184
        # * display a special caracter at the beginning of the line
185
        # * underline the command name
186
        if args.is_standalone:
187
            ret.append(self.curse_add_line('>' if selected else ' ', 'SELECTED'))
188
        # CPU
189
        if 'cpu_percent' in p and p['cpu_percent'] is not None and p['cpu_percent'] != '':
0 ignored issues
show
This line is too long as per the coding-style (90/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
190
            cpu_layout = self.layout_stat['cpu'] if p['cpu_percent'] < 100 else self.layout_stat['cpu_no_digit']
191
            if args.disable_irix and self.nb_log_core != 0:
192
                msg = cpu_layout.format(
193
                    p['cpu_percent'] / float(self.nb_log_core))
194
            else:
195
                msg = cpu_layout.format(p['cpu_percent'])
196
            alert = self.get_alert(p['cpu_percent'],
197
                                   highlight_zero=False,
198
                                   is_max=(p['cpu_percent'] == self.max_values['cpu_percent']),
199
                                   header="cpu")
200
            ret.append(self.curse_add_line(msg, alert))
201
        else:
202
            msg = self.layout_header['cpu'].format('?')
203
            ret.append(self.curse_add_line(msg))
204
        # MEM
205
        if 'memory_percent' in p and p['memory_percent'] is not None and p['memory_percent'] != '':
0 ignored issues
show
This line is too long as per the coding-style (99/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
206
            msg = self.layout_stat['mem'].format(p['memory_percent'])
207
            alert = self.get_alert(p['memory_percent'],
208
                                   highlight_zero=False,
209
                                   is_max=(p['memory_percent'] == self.max_values['memory_percent']),
210
                                   header="mem")
211
            ret.append(self.curse_add_line(msg, alert))
212
        else:
213
            msg = self.layout_header['mem'].format('?')
214
            ret.append(self.curse_add_line(msg))
215
        # VMS/RSS
216
        if 'memory_info' in p and p['memory_info'] is not None and p['memory_info'] != '':
0 ignored issues
show
This line is too long as per the coding-style (90/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
217
            # VMS
218
            msg = self.layout_stat['virt'].format(self.auto_unit(p['memory_info'][1], low_precision=False))
219
            ret.append(self.curse_add_line(msg, optional=True))
220
            # RSS
221
            msg = self.layout_stat['res'].format(self.auto_unit(p['memory_info'][0], low_precision=False))
222
            ret.append(self.curse_add_line(msg, optional=True))
223
        else:
224
            msg = self.layout_header['virt'].format('?')
225
            ret.append(self.curse_add_line(msg))
226
            msg = self.layout_header['res'].format('?')
227
            ret.append(self.curse_add_line(msg))
228
        # PID
229
        msg = self.layout_stat['pid'].format(p['pid'], width=self.__max_pid_size())
230
        ret.append(self.curse_add_line(msg))
231
        # USER
232
        if 'username' in p:
233
            # docker internal users are displayed as ints only, therefore str()
234
            # Correct issue #886 on Windows OS
235
            msg = self.layout_stat['user'].format(str(p['username'])[:9])
236
            ret.append(self.curse_add_line(msg))
237
        else:
238
            msg = self.layout_header['user'].format('?')
239
            ret.append(self.curse_add_line(msg))
240
        # TIME+
241
        try:
242
            # Sum user and system time
243
            user_system_time = p['cpu_times'][0] + p['cpu_times'][1]
244
        except (OverflowError, TypeError) as e:
245
            # Catch OverflowError on some Amazon EC2 server
246
            # See https://github.com/nicolargo/glances/issues/87
247
            # Also catch TypeError on macOS
248
            # See: https://github.com/nicolargo/glances/issues/622
249
            # logger.debug("Cannot get TIME+ ({})".format(e))
250
            msg = self.layout_header['time'].format('?')
251
            ret.append(self.curse_add_line(msg, optional=True))
252
        else:
253
            hours, minutes, seconds = seconds_to_hms(user_system_time)
254
            if hours > 99:
255
                msg = '{:<7}h'.format(hours)
256
            elif 0 < hours < 100:
257
                msg = '{}h{}:{}'.format(hours, minutes, seconds)
258
            else:
259
                msg = '{}:{}'.format(minutes, seconds)
260
            msg = self.layout_stat['time'].format(msg)
261
            if hours > 0:
262
                ret.append(self.curse_add_line(msg,
263
                                               decoration='CPU_TIME',
264
                                               optional=True))
265
            else:
266
                ret.append(self.curse_add_line(msg, optional=True))
267
        # THREAD
268
        if 'num_threads' in p:
269
            num_threads = p['num_threads']
270
            if num_threads is None:
271
                num_threads = '?'
272
            msg = self.layout_stat['thread'].format(num_threads)
273
            ret.append(self.curse_add_line(msg))
274
        else:
275
            msg = self.layout_header['thread'].format('?')
276
            ret.append(self.curse_add_line(msg))
277
        # NICE
278
        if 'nice' in p:
279
            nice = p['nice']
280
            if nice is None:
281
                nice = '?'
282
            msg = self.layout_stat['nice'].format(nice)
283
            ret.append(self.curse_add_line(msg,
284
                                           decoration=self.get_nice_alert(nice)))
285
        else:
286
            msg = self.layout_header['nice'].format('?')
287
            ret.append(self.curse_add_line(msg))
288
        # STATUS
289
        if 'status' in p:
290
            status = p['status']
291
            msg = self.layout_stat['status'].format(status)
292
            if status == 'R':
293
                ret.append(self.curse_add_line(msg, decoration='STATUS'))
294
            else:
295
                ret.append(self.curse_add_line(msg))
296
        else:
297
            msg = self.layout_header['status'].format('?')
298
            ret.append(self.curse_add_line(msg))
299
        # IO read/write
300
        if 'io_counters' in p and p['io_counters'][4] == 1 and p['time_since_update'] != 0:
301
            # Display rate if stats is available and io_tag ([4]) == 1
302
            # IO read
303
            io_rs = int((p['io_counters'][0] - p['io_counters'][2]) / p['time_since_update'])
304
            if io_rs == 0:
305
                msg = self.layout_stat['ior'].format("0")
306
            else:
307
                msg = self.layout_stat['ior'].format(self.auto_unit(io_rs,
308
                                                                    low_precision=True))
309
            ret.append(self.curse_add_line(msg, optional=True, additional=True))
310
            # IO write
311
            io_ws = int((p['io_counters'][1] - p['io_counters'][3]) / p['time_since_update'])
312
            if io_ws == 0:
313
                msg = self.layout_stat['iow'].format("0")
314
            else:
315
                msg = self.layout_stat['iow'].format(self.auto_unit(io_ws,
316
                                                                    low_precision=True))
317
            ret.append(self.curse_add_line(msg, optional=True, additional=True))
318
        else:
319
            msg = self.layout_header['ior'].format("?")
320
            ret.append(self.curse_add_line(msg, optional=True, additional=True))
321
            msg = self.layout_header['iow'].format("?")
322
            ret.append(self.curse_add_line(msg, optional=True, additional=True))
323
324
        # Command line
325
        # If no command line for the process is available, fallback to
326
        # the bare process name instead
327
        if 'cmdline' in p:
328
            cmdline = p['cmdline']
329
        else:
330
            cmdline = '?'
331
        try:
332
            process_decoration = 'PROCESS_SELECTED' if (selected and args.is_standalone) else 'PROCESS'
333
            if cmdline:
334
                path, cmd, arguments = split_cmdline(cmdline)
335
                # Manage end of line in arguments (see #1692)
336
                arguments.replace('\r\n', ' ')
337
                arguments.replace('\n', ' ')
338
                if os.path.isdir(path) and not args.process_short_name:
339
                    msg = self.layout_stat['command'].format(path) + os.sep
340
                    ret.append(self.curse_add_line(msg, splittable=True))
341
                    ret.append(self.curse_add_line(
342
                        cmd, decoration=process_decoration, splittable=True))
343
                else:
344
                    msg = self.layout_stat['command'].format(cmd)
345
                    ret.append(self.curse_add_line(
346
                        msg, decoration=process_decoration, splittable=True))
347
                if arguments:
348
                    msg = ' ' + self.layout_stat['command'].format(arguments)
349
                    ret.append(self.curse_add_line(msg, splittable=True))
350
            else:
351
                msg = self.layout_stat['name'].format(p['name'])
352
                ret.append(self.curse_add_line(msg, decoration=process_decoration, splittable=True))
353
        except (TypeError, UnicodeEncodeError) as e:
354
            # Avoid crach after running fine for several hours #1335
355
            logger.debug("Can not decode command line '{}' ({})".format(cmdline, e))
356
            ret.append(self.curse_add_line('', splittable=True))
357
358
        # Add extended stats but only for the top processes
359
        if args.cursor_position == 0 and 'extended_stats' in p and args.enable_process_extended:
360
            # Left padding
361
            xpad = ' ' * 13
362
            # First line is CPU affinity
363
            if 'cpu_affinity' in p and p['cpu_affinity'] is not None:
364
                ret.append(self.curse_new_line())
365
                msg = xpad + 'CPU affinity: ' + str(len(p['cpu_affinity'])) + ' cores'
366
                ret.append(self.curse_add_line(msg, splittable=True))
367
            # Second line is memory info
368
            if 'memory_info' in p and \
369
               p['memory_info'] is not None:
370
                ret.append(self.curse_new_line())
371
                msg = '{}Memory info: {}'.format(xpad, p['memory_info'])
372
                if 'memory_swap' in p and p['memory_swap'] is not None:
373
                    msg += ' swap ' + self.auto_unit(p['memory_swap'], low_precision=False)
374
                ret.append(self.curse_add_line(msg, splittable=True))
375
            # Third line is for open files/network sessions
376
            msg = ''
377
            if 'num_threads' in p and p['num_threads'] is not None:
378
                msg += str(p['num_threads']) + ' threads '
379
            if 'num_fds' in p and p['num_fds'] is not None:
380
                msg += str(p['num_fds']) + ' files '
381
            if 'num_handles' in p and p['num_handles'] is not None:
382
                msg += str(p['num_handles']) + ' handles '
383
            if 'tcp' in p and p['tcp'] is not None:
384
                msg += str(p['tcp']) + ' TCP '
385
            if 'udp' in p and p['udp'] is not None:
386
                msg += str(p['udp']) + ' UDP'
387
            if msg != '':
388
                ret.append(self.curse_new_line())
389
                msg = xpad + 'Open: ' + msg
390
                ret.append(self.curse_add_line(msg, splittable=True))
391
            # Fouth line is IO nice level (only Linux and Windows OS)
392
            if 'ionice' in p and \
393
               p['ionice'] is not None \
394
               and hasattr(p['ionice'], 'ioclass'):
395
                ret.append(self.curse_new_line())
396
                msg = xpad + 'IO nice: '
397
                k = 'Class is '
398
                v = p['ionice'].ioclass
0 ignored issues
show
Coding Style Naming introduced by
The name v does not conform to the variable naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
399
                # Linux: The scheduling class. 0 for none, 1 for real time, 2 for best-effort, 3 for idle.
0 ignored issues
show
This line is too long as per the coding-style (106/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
400
                # Windows: On Windows only ioclass is used and it can be set to 2 (normal), 1 (low) or 0 (very low).
0 ignored issues
show
This line is too long as per the coding-style (116/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
401
                if WINDOWS:
402
                    if v == 0:
403
                        msg += k + 'Very Low'
404
                    elif v == 1:
405
                        msg += k + 'Low'
406
                    elif v == 2:
407
                        msg += 'No specific I/O priority'
408
                    else:
409
                        msg += k + str(v)
410
                else:
411
                    if v == 0:
412
                        msg += 'No specific I/O priority'
413
                    elif v == 1:
414
                        msg += k + 'Real Time'
415
                    elif v == 2:
416
                        msg += k + 'Best Effort'
417
                    elif v == 3:
418
                        msg += k + 'IDLE'
419
                    else:
420
                        msg += k + str(v)
421
                #  value is a number which goes from 0 to 7.
422
                # The higher the value, the lower the I/O priority of the process.
0 ignored issues
show
This line is too long as per the coding-style (82/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
423
                if hasattr(p['ionice'], 'value') and p['ionice'].value != 0:
424
                    msg += ' (value %s/7)' % str(p['ionice'].value)
425
                ret.append(self.curse_add_line(msg, splittable=True))
426
427
        return ret
428
429
    def msg_curse(self, args=None, max_width=None):
430
        """Return the dict to display in the curse interface."""
431
        # Init the return message
432
        ret = []
433
434
        # Only process if stats exist and display plugin enable...
435
        if not self.stats or args.disable_process:
436
            return ret
437
438
        # Compute the sort key
439
        process_sort_key = glances_processes.sort_key
440
441
        # Header
442
        self.__msg_curse_header(ret, process_sort_key, args)
443
444
        # Process list
445
        # Loop over processes (sorted by the sort key previously compute)
446
        i = 0
447
        for p in self.__sort_stats(process_sort_key):
448
            ret.extend(self.get_process_curses_data(
449
                p, i == args.cursor_position, args))
450
            i += 1
451
        
452
        # A filter is set Display the stats summaries
453
        if glances_processes.process_filter is not None:
454
            if args.reset_minmax_tag:
455
                args.reset_minmax_tag = not args.reset_minmax_tag
456
                self.__mmm_reset()
457
            self.__msg_curse_sum(ret, args=args)
458
            self.__msg_curse_sum(ret, mmm='min', args=args)
459
            self.__msg_curse_sum(ret, mmm='max', args=args)
460
461
        # Return the message with decoration
462
        return ret
463
464
    def __msg_curse_header(self, ret, process_sort_key, args=None):
465
        """Build the header and add it to the ret dict."""
466
        sort_style = 'SORT'
467
468
        if args.disable_irix and 0 < self.nb_log_core < 10:
469
            msg = self.layout_header['cpu'].format('CPU%/' + str(self.nb_log_core))
470
        elif args.disable_irix and self.nb_log_core != 0:
471
            msg = self.layout_header['cpu'].format('CPU%/C')
472
        else:
473
            msg = self.layout_header['cpu'].format('CPU%')
474
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'cpu_percent' else 'DEFAULT'))
475
        msg = self.layout_header['mem'].format('MEM%')
476
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'memory_percent' else 'DEFAULT'))
477
        msg = self.layout_header['virt'].format('VIRT')
478
        ret.append(self.curse_add_line(msg, optional=True))
479
        msg = self.layout_header['res'].format('RES')
480
        ret.append(self.curse_add_line(msg, optional=True))
481
        msg = self.layout_header['pid'].format('PID', width=self.__max_pid_size())
482
        ret.append(self.curse_add_line(msg))
483
        msg = self.layout_header['user'].format('USER')
484
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'username' else 'DEFAULT'))
485
        msg = self.layout_header['time'].format('TIME+')
486
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'cpu_times' else 'DEFAULT', optional=True))
487
        msg = self.layout_header['thread'].format('THR')
488
        ret.append(self.curse_add_line(msg))
489
        msg = self.layout_header['nice'].format('NI')
490
        ret.append(self.curse_add_line(msg))
491
        msg = self.layout_header['status'].format('S')
492
        ret.append(self.curse_add_line(msg))
493
        msg = self.layout_header['ior'].format('R/s')
494
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'io_counters' else 'DEFAULT', optional=True, additional=True))
495
        msg = self.layout_header['iow'].format('W/s')
496
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'io_counters' else 'DEFAULT', optional=True, additional=True))
497
        msg = self.layout_header['command'].format('Command',
498
                                                   "('k' to kill)" if args.is_standalone else "")
499
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'name' else 'DEFAULT'))
500
501
    def __msg_curse_sum(self, ret, sep_char='_', mmm=None, args=None):
502
        """
503
        Build the sum message (only when filter is on) and add it to the ret dict.
504
505
        * ret: list of string where the message is added
506
        * sep_char: define the line separation char
507
        * mmm: display min, max, mean or current (if mmm=None)
508
        * args: Glances args
509
        """
510
        ret.append(self.curse_new_line())
511
        if mmm is None:
512
            ret.append(self.curse_add_line(sep_char * 69))
513
            ret.append(self.curse_new_line())
514
        # CPU percent sum
515
        msg = self.layout_stat['cpu'].format(self.__sum_stats('cpu_percent', mmm=mmm))
516
        ret.append(self.curse_add_line(msg,
517
                                       decoration=self.__mmm_deco(mmm)))
518
        # MEM percent sum
519
        msg = self.layout_stat['mem'].format(self.__sum_stats('memory_percent', mmm=mmm))
520
        ret.append(self.curse_add_line(msg,
521
                                       decoration=self.__mmm_deco(mmm)))
522
        # VIRT and RES memory sum
523
        if 'memory_info' in self.stats[0] and self.stats[0]['memory_info'] is not None and self.stats[0]['memory_info'] != '':
524
            # VMS
525
            msg = self.layout_stat['virt'].format(self.auto_unit(self.__sum_stats('memory_info', indice=1, mmm=mmm), low_precision=False))
526
            ret.append(self.curse_add_line(msg,
527
                                           decoration=self.__mmm_deco(mmm),
528
                                           optional=True))
529
            # RSS
530
            msg = self.layout_stat['res'].format(self.auto_unit(self.__sum_stats('memory_info', indice=0, mmm=mmm), low_precision=False))
531
            ret.append(self.curse_add_line(msg,
532
                                           decoration=self.__mmm_deco(mmm),
533
                                           optional=True))
534
        else:
535
            msg = self.layout_header['virt'].format('')
536
            ret.append(self.curse_add_line(msg))
537
            msg = self.layout_header['res'].format('')
538
            ret.append(self.curse_add_line(msg))
539
        # PID
540
        msg = self.layout_header['pid'].format('', width=self.__max_pid_size())
541
        ret.append(self.curse_add_line(msg))
542
        # USER
543
        msg = self.layout_header['user'].format('')
544
        ret.append(self.curse_add_line(msg))
545
        # TIME+
546
        msg = self.layout_header['time'].format('')
547
        ret.append(self.curse_add_line(msg, optional=True))
548
        # THREAD
549
        msg = self.layout_header['thread'].format('')
550
        ret.append(self.curse_add_line(msg))
551
        # NICE
552
        msg = self.layout_header['nice'].format('')
553
        ret.append(self.curse_add_line(msg))
554
        # STATUS
555
        msg = self.layout_header['status'].format('')
556
        ret.append(self.curse_add_line(msg))
557
        # IO read/write
558
        if 'io_counters' in self.stats[0] and mmm is None:
559
            # IO read
560
            io_rs = int((self.__sum_stats('io_counters', 0) - self.__sum_stats('io_counters', indice=2, mmm=mmm)) / self.stats[0]['time_since_update'])
561
            if io_rs == 0:
562
                msg = self.layout_stat['ior'].format('0')
563
            else:
564
                msg = self.layout_stat['ior'].format(self.auto_unit(io_rs, low_precision=True))
565
            ret.append(self.curse_add_line(msg,
566
                                           decoration=self.__mmm_deco(mmm),
567
                                           optional=True, additional=True))
568
            # IO write
569
            io_ws = int((self.__sum_stats('io_counters', 1) - self.__sum_stats('io_counters', indice=3, mmm=mmm)) / self.stats[0]['time_since_update'])
570
            if io_ws == 0:
571
                msg = self.layout_stat['iow'].format('0')
572
            else:
573
                msg = self.layout_stat['iow'].format(self.auto_unit(io_ws, low_precision=True))
574
            ret.append(self.curse_add_line(msg,
575
                                           decoration=self.__mmm_deco(mmm),
576
                                           optional=True, additional=True))
577
        else:
578
            msg = self.layout_header['ior'].format('')
579
            ret.append(self.curse_add_line(msg, optional=True, additional=True))
580
            msg = self.layout_header['iow'].format('')
581
            ret.append(self.curse_add_line(msg, optional=True, additional=True))
582
        if mmm is None:
583
            msg = ' < {}'.format('current')
584
            ret.append(self.curse_add_line(msg, optional=True))
585
        else:
586
            msg = ' < {}'.format(mmm)
587
            ret.append(self.curse_add_line(msg, optional=True))
588
            msg = ' (\'M\' to reset)'
589
            ret.append(self.curse_add_line(msg, optional=True))
590
591
    def __mmm_deco(self, mmm):
592
        """Return the decoration string for the current mmm status."""
593
        if mmm is not None:
594
            return 'DEFAULT'
595
        else:
596
            return 'FILTER'
597
598
    def __mmm_reset(self):
599
        """Reset the MMM stats."""
600
        self.mmm_min = {}
601
        self.mmm_max = {}
602
603
    def __sum_stats(self, key, indice=None, mmm=None):
604
        """Return the sum of the stats value for the given key.
605
606
        * indice: If indice is set, get the p[key][indice]
607
        * mmm: display min, max, mean or current (if mmm=None)
608
        """
609
        # Compute stats summary
610
        ret = 0
611
        for p in self.stats:
612
            if key not in p:
613
                # Correct issue #1188
614
                continue
615
            if p[key] is None:
616
                # Correct https://github.com/nicolargo/glances/issues/1105#issuecomment-363553788
617
                continue
618
            if indice is None:
619
                ret += p[key]
620
            else:
621
                ret += p[key][indice]
622
623
        # Manage Min/Max/Mean
624
        mmm_key = self.__mmm_key(key, indice)
625
        if mmm == 'min':
626
            try:
627
                if self.mmm_min[mmm_key] > ret:
628
                    self.mmm_min[mmm_key] = ret
629
            except AttributeError:
630
                self.mmm_min = {}
631
                return 0
632
            except KeyError:
633
                self.mmm_min[mmm_key] = ret
634
            ret = self.mmm_min[mmm_key]
635
        elif mmm == 'max':
636
            try:
637
                if self.mmm_max[mmm_key] < ret:
638
                    self.mmm_max[mmm_key] = ret
639
            except AttributeError:
640
                self.mmm_max = {}
641
                return 0
642
            except KeyError:
643
                self.mmm_max[mmm_key] = ret
644
            ret = self.mmm_max[mmm_key]
645
646
        return ret
647
648
    def __mmm_key(self, key, indice):
649
        ret = key
650
        if indice is not None:
651
            ret += str(indice)
652
        return ret
653
654
    def __sort_stats(self, sortedby=None):
655
        """Return the stats (dict) sorted by (sortedby)."""
656
        return sort_stats(self.stats, sortedby,
657
                          reverse=glances_processes.sort_reverse)
658
659
    def __max_pid_size(self):
660
        """Return the maximum PID size in number of char."""
661
        if self.pid_max is not None:
662
            return len(str(self.pid_max))
663
        else:
664
            # By default return 5 (corresponding to 99999 PID number)
665
            return 5
666