Test Failed
Push — develop ( 66c9ff...e21229 )
by Nicolas
05:06
created

glances/plugins/glances_processlist.py (1 issue)

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
0 ignored issues
show
Unused timedelta imported from datetime
Loading history...
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
        'mem': '{:<5.1f} ',
80
        'virt': '{:<5} ',
81
        'res': '{:<5} ',
82
        'pid': '{:>{width}} ',
83
        'user': '{:<10} ',
84
        'time': '{:>8} ',
85
        'thread': '{:<3} ',
86
        'nice': '{:>3} ',
87
        'status': '{:>1} ',
88
        'ior': '{:>4} ',
89
        'iow': '{:<4} ',
90
        'command': '{}',
91
        'name': '[{}]'
92
    }
93
94
    def __init__(self, args=None, config=None):
95
        """Init the plugin."""
96
        super(Plugin, self).__init__(args=args,
97
                                     config=config,
98
                                     stats_init_value=[])
99
100
        # We want to display the stat in the curse interface
101
        self.display_curse = True
102
103
        # Trying to display proc time
104
        self.tag_proc_time = True
105
106
        # Call CorePlugin to get the core number (needed when not in IRIX mode / Solaris mode)
107
        try:
108
            self.nb_log_core = CorePlugin(args=self.args).update()["log"]
109
        except Exception:
110
            self.nb_log_core = 0
111
112
        # Get the max values (dict)
113
        self.max_values = copy.deepcopy(glances_processes.max_values())
114
115
        # Get the maximum PID number
116
        # Use to optimize space (see https://github.com/nicolargo/glances/issues/959)
117
        self.pid_max = glances_processes.pid_max
118
119
        # Note: 'glances_processes' is already init in the processes.py script
120
121
    def get_key(self):
122
        """Return the key of the list."""
123
        return 'pid'
124
125
    def update(self):
126
        """Update processes stats using the input method."""
127
        # Init new stats
128
        stats = self.get_init_value()
129
130
        if self.input_method == 'local':
131
            # Update stats using the standard system lib
132
            # Note: Update is done in the processcount plugin
133
            # Just return the processes list
134
            stats = glances_processes.getlist()
135
136
        elif self.input_method == 'snmp':
137
            # No SNMP grab for processes
138
            pass
139
140
        # Update the stats
141
        self.stats = stats
142
143
        # Get the max values (dict)
144
        # Use Deep copy to avoid change between update and display
145
        self.max_values = copy.deepcopy(glances_processes.max_values())
146
147
        return self.stats
148
149
    def get_nice_alert(self, value):
150
        """Return the alert relative to the Nice configuration list"""
151
        value = str(value)
152
        try:
153
            if value in self.get_limit('nice_critical'):
154
                return 'CRITICAL'
155
        except KeyError:
156
            pass
157
        try:
158
            if value in self.get_limit('nice_warning'):
159
                return 'WARNING'
160
        except KeyError:
161
            pass
162
        try:
163
            if value in self.get_limit('nice_careful'):
164
                return 'CAREFUL'
165
        except KeyError:
166
            pass
167
        return 'DEFAULT'
168
169
    def get_process_curses_data(self, p, first, args):
170
        """Get curses data to display for a process.
171
172
        - p is the process to display
173
        - first is a tag=True if the process is the first on the list
174
        """
175
        ret = [self.curse_new_line()]
176
        # CPU
177
        if 'cpu_percent' in p and p['cpu_percent'] is not None and p['cpu_percent'] != '':
178
            if args.disable_irix and self.nb_log_core != 0:
179
                msg = self.layout_stat['cpu'].format(p['cpu_percent'] / float(self.nb_log_core))
180
            else:
181
                msg = self.layout_stat['cpu'].format(p['cpu_percent'])
182
            alert = self.get_alert(p['cpu_percent'],
183
                                   highlight_zero=False,
184
                                   is_max=(p['cpu_percent'] == self.max_values['cpu_percent']),
185
                                   header="cpu")
186
            ret.append(self.curse_add_line(msg, alert))
187
        else:
188
            msg = self.layout_header['cpu'].format('?')
189
            ret.append(self.curse_add_line(msg))
190
        # MEM
191
        if 'memory_percent' in p and p['memory_percent'] is not None and p['memory_percent'] != '':
192
            msg = self.layout_stat['mem'].format(p['memory_percent'])
193
            alert = self.get_alert(p['memory_percent'],
194
                                   highlight_zero=False,
195
                                   is_max=(p['memory_percent'] == self.max_values['memory_percent']),
196
                                   header="mem")
197
            ret.append(self.curse_add_line(msg, alert))
198
        else:
199
            msg = self.layout_header['mem'].format('?')
200
            ret.append(self.curse_add_line(msg))
201
        # VMS/RSS
202
        if 'memory_info' in p and p['memory_info'] is not None and p['memory_info'] != '':
203
            # VMS
204
            msg = self.layout_stat['virt'].format(self.auto_unit(p['memory_info'][1], low_precision=False))
205
            ret.append(self.curse_add_line(msg, optional=True))
206
            # RSS
207
            msg = self.layout_stat['res'].format(self.auto_unit(p['memory_info'][0], low_precision=False))
208
            ret.append(self.curse_add_line(msg, optional=True))
209
        else:
210
            msg = self.layout_header['virt'].format('?')
211
            ret.append(self.curse_add_line(msg))
212
            msg = self.layout_header['res'].format('?')
213
            ret.append(self.curse_add_line(msg))
214
        # PID
215
        msg = self.layout_stat['pid'].format(p['pid'], width=self.__max_pid_size())
216
        ret.append(self.curse_add_line(msg))
217
        # USER
218
        if 'username' in p:
219
            # docker internal users are displayed as ints only, therefore str()
220
            # Correct issue #886 on Windows OS
221
            msg = self.layout_stat['user'].format(str(p['username'])[:9])
222
            ret.append(self.curse_add_line(msg))
223
        else:
224
            msg = self.layout_header['user'].format('?')
225
            ret.append(self.curse_add_line(msg))
226
        # TIME+
227
        try:
228
            # Sum user and system time
229
            user_system_time = p['cpu_times'][0] + p['cpu_times'][1]
230
        except (OverflowError, TypeError) as e:
231
            # Catch OverflowError on some Amazon EC2 server
232
            # See https://github.com/nicolargo/glances/issues/87
233
            # Also catch TypeError on macOS
234
            # See: https://github.com/nicolargo/glances/issues/622
235
            # logger.debug("Cannot get TIME+ ({})".format(e))
236
            msg = self.layout_header['time'].format('?')
237
            ret.append(self.curse_add_line(msg, optional=True))
238
        else:
239
            hours, minutes, seconds = seconds_to_hms(user_system_time)
240
            if hours > 99:
241
                msg = '{:<7}h'.format(hours)
242
            elif 0 < hours < 100:
243
                msg = '{}h{}:{}'.format(hours, minutes, seconds)
244
            else:
245
                msg = '{}:{}'.format(minutes, seconds)
246
            msg = self.layout_stat['time'].format(msg)
247
            if hours > 0:
248
                ret.append(self.curse_add_line(msg,
249
                                               decoration='CPU_TIME',
250
                                               optional=True))
251
            else:
252
                ret.append(self.curse_add_line(msg, optional=True))
253
        # THREAD
254
        if 'num_threads' in p:
255
            num_threads = p['num_threads']
256
            if num_threads is None:
257
                num_threads = '?'
258
            msg = self.layout_stat['thread'].format(num_threads)
259
            ret.append(self.curse_add_line(msg))
260
        else:
261
            msg = self.layout_header['thread'].format('?')
262
            ret.append(self.curse_add_line(msg))
263
        # NICE
264
        if 'nice' in p:
265
            nice = p['nice']
266
            if nice is None:
267
                nice = '?'
268
            msg = self.layout_stat['nice'].format(nice)
269
            ret.append(self.curse_add_line(msg,
270
                                           decoration=self.get_nice_alert(nice)))
271
        else:
272
            msg = self.layout_header['nice'].format('?')
273
            ret.append(self.curse_add_line(msg))
274
        # STATUS
275
        if 'status' in p:
276
            status = p['status']
277
            msg = self.layout_stat['status'].format(status)
278
            if status == 'R':
279
                ret.append(self.curse_add_line(msg, decoration='STATUS'))
280
            else:
281
                ret.append(self.curse_add_line(msg))
282
        else:
283
            msg = self.layout_header['status'].format('?')
284
            ret.append(self.curse_add_line(msg))
285
        # IO read/write
286
        if 'io_counters' in p and p['io_counters'][4] == 1 and p['time_since_update'] != 0:
287
            # Display rate if stats is available and io_tag ([4]) == 1
288
            # IO read
289
            io_rs = int((p['io_counters'][0] - p['io_counters'][2]) / p['time_since_update'])
290
            if io_rs == 0:
291
                msg = self.layout_stat['ior'].format("0")
292
            else:
293
                msg = self.layout_stat['ior'].format(self.auto_unit(io_rs,
294
                                                                    low_precision=True))
295
            ret.append(self.curse_add_line(msg, optional=True, additional=True))
296
            # IO write
297
            io_ws = int((p['io_counters'][1] - p['io_counters'][3]) / p['time_since_update'])
298
            if io_ws == 0:
299
                msg = self.layout_stat['iow'].format("0")
300
            else:
301
                msg = self.layout_stat['iow'].format(self.auto_unit(io_ws,
302
                                                                    low_precision=True))
303
            ret.append(self.curse_add_line(msg, optional=True, additional=True))
304
        else:
305
            msg = self.layout_header['ior'].format("?")
306
            ret.append(self.curse_add_line(msg, optional=True, additional=True))
307
            msg = self.layout_header['iow'].format("?")
308
            ret.append(self.curse_add_line(msg, optional=True, additional=True))
309
310
        # Command line
311
        # If no command line for the process is available, fallback to
312
        # the bare process name instead
313
        if 'cmdline' in p:
314
            cmdline = p['cmdline']
315
        else:
316
            cmdline = '?'
317
        try:
318
            if cmdline:
319
                path, cmd, arguments = split_cmdline(cmdline)
320
                if os.path.isdir(path) and not args.process_short_name:
321
                    msg = self.layout_stat['command'].format(path) + os.sep
322
                    ret.append(self.curse_add_line(msg, splittable=True))
323
                    ret.append(self.curse_add_line(cmd, decoration='PROCESS', splittable=True))
324
                else:
325
                    msg = self.layout_stat['command'].format(cmd)
326
                    ret.append(self.curse_add_line(msg, decoration='PROCESS', splittable=True))
327
                if arguments:
328
                    msg = ' ' + self.layout_stat['command'].format(arguments)
329
                    ret.append(self.curse_add_line(msg, splittable=True))
330
            else:
331
                msg = self.layout_stat['name'].format(p['name'])
332
                ret.append(self.curse_add_line(msg, splittable=True))
333
        except (TypeError, UnicodeEncodeError) as e:
334
            # Avoid crach after running fine for several hours #1335
335
            logger.debug("Can not decode command line '{}' ({})".format(cmdline, e))
336
            ret.append(self.curse_add_line('', splittable=True))
337
338
        # Add extended stats but only for the top processes
339
        if first and 'extended_stats' in p and args.enable_process_extended:
340
            # Left padding
341
            xpad = ' ' * 13
342
            # First line is CPU affinity
343
            if 'cpu_affinity' in p and p['cpu_affinity'] is not None:
344
                ret.append(self.curse_new_line())
345
                msg = xpad + 'CPU affinity: ' + str(len(p['cpu_affinity'])) + ' cores'
346
                ret.append(self.curse_add_line(msg, splittable=True))
347
            # Second line is memory info
348
            if 'memory_info' in p and \
349
               p['memory_info'] is not None:
350
                ret.append(self.curse_new_line())
351
                msg = '{}Memory info: {}'.format(xpad, p['memory_info'])
352
                if 'memory_swap' in p and p['memory_swap'] is not None:
353
                    msg += ' swap ' + self.auto_unit(p['memory_swap'], low_precision=False)
354
                ret.append(self.curse_add_line(msg, splittable=True))
355
            # Third line is for open files/network sessions
356
            msg = ''
357
            if 'num_threads' in p and p['num_threads'] is not None:
358
                msg += str(p['num_threads']) + ' threads '
359
            if 'num_fds' in p and p['num_fds'] is not None:
360
                msg += str(p['num_fds']) + ' files '
361
            if 'num_handles' in p and p['num_handles'] is not None:
362
                msg += str(p['num_handles']) + ' handles '
363
            if 'tcp' in p and p['tcp'] is not None:
364
                msg += str(p['tcp']) + ' TCP '
365
            if 'udp' in p and p['udp'] is not None:
366
                msg += str(p['udp']) + ' UDP'
367
            if msg != '':
368
                ret.append(self.curse_new_line())
369
                msg = xpad + 'Open: ' + msg
370
                ret.append(self.curse_add_line(msg, splittable=True))
371
            # Fouth line is IO nice level (only Linux and Windows OS)
372
            if 'ionice' in p and \
373
               p['ionice'] is not None \
374
               and hasattr(p['ionice'], 'ioclass'):
375
                ret.append(self.curse_new_line())
376
                msg = xpad + 'IO nice: '
377
                k = 'Class is '
378
                v = p['ionice'].ioclass
379
                # Linux: The scheduling class. 0 for none, 1 for real time, 2 for best-effort, 3 for idle.
380
                # Windows: On Windows only ioclass is used and it can be set to 2 (normal), 1 (low) or 0 (very low).
381
                if WINDOWS:
382
                    if v == 0:
383
                        msg += k + 'Very Low'
384
                    elif v == 1:
385
                        msg += k + 'Low'
386
                    elif v == 2:
387
                        msg += 'No specific I/O priority'
388
                    else:
389
                        msg += k + str(v)
390
                else:
391
                    if v == 0:
392
                        msg += 'No specific I/O priority'
393
                    elif v == 1:
394
                        msg += k + 'Real Time'
395
                    elif v == 2:
396
                        msg += k + 'Best Effort'
397
                    elif v == 3:
398
                        msg += k + 'IDLE'
399
                    else:
400
                        msg += k + str(v)
401
                #  value is a number which goes from 0 to 7.
402
                # The higher the value, the lower the I/O priority of the process.
403
                if hasattr(p['ionice'], 'value') and p['ionice'].value != 0:
404
                    msg += ' (value %s/7)' % str(p['ionice'].value)
405
                ret.append(self.curse_add_line(msg, splittable=True))
406
407
        return ret
408
409
    def msg_curse(self, args=None, max_width=None):
410
        """Return the dict to display in the curse interface."""
411
        # Init the return message
412
        ret = []
413
414
        # Only process if stats exist and display plugin enable...
415
        if not self.stats or args.disable_process:
416
            return ret
417
418
        # Compute the sort key
419
        process_sort_key = glances_processes.sort_key
420
421
        # Header
422
        self.__msg_curse_header(ret, process_sort_key, args)
423
424
        # Process list
425
        # Loop over processes (sorted by the sort key previously compute)
426
        first = True
427
        for p in self.__sort_stats(process_sort_key):
428
            ret.extend(self.get_process_curses_data(p, first, args))
429
            # End of extended stats
430
            first = False
431
        if glances_processes.process_filter is not None:
432
            if args.reset_minmax_tag:
433
                args.reset_minmax_tag = not args.reset_minmax_tag
434
                self.__mmm_reset()
435
            self.__msg_curse_sum(ret, args=args)
436
            self.__msg_curse_sum(ret, mmm='min', args=args)
437
            self.__msg_curse_sum(ret, mmm='max', args=args)
438
439
        # Return the message with decoration
440
        return ret
441
442
    def __msg_curse_header(self, ret, process_sort_key, args=None):
443
        """Build the header and add it to the ret dict."""
444
        sort_style = 'SORT'
445
446
        if args.disable_irix and 0 < self.nb_log_core < 10:
447
            msg = self.layout_header['cpu'].format('CPU%/' + str(self.nb_log_core))
448
        elif args.disable_irix and self.nb_log_core != 0:
449
            msg = self.layout_header['cpu'].format('CPU%/C')
450
        else:
451
            msg = self.layout_header['cpu'].format('CPU%')
452
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'cpu_percent' else 'DEFAULT'))
453
        msg = self.layout_header['mem'].format('MEM%')
454
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'memory_percent' else 'DEFAULT'))
455
        msg = self.layout_header['virt'].format('VIRT')
456
        ret.append(self.curse_add_line(msg, optional=True))
457
        msg = self.layout_header['res'].format('RES')
458
        ret.append(self.curse_add_line(msg, optional=True))
459
        msg = self.layout_header['pid'].format('PID', width=self.__max_pid_size())
460
        ret.append(self.curse_add_line(msg))
461
        msg = self.layout_header['user'].format('USER')
462
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'username' else 'DEFAULT'))
463
        msg = self.layout_header['time'].format('TIME+')
464
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'cpu_times' else 'DEFAULT', optional=True))
465
        msg = self.layout_header['thread'].format('THR')
466
        ret.append(self.curse_add_line(msg))
467
        msg = self.layout_header['nice'].format('NI')
468
        ret.append(self.curse_add_line(msg))
469
        msg = self.layout_header['status'].format('S')
470
        ret.append(self.curse_add_line(msg))
471
        msg = self.layout_header['ior'].format('R/s')
472
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'io_counters' else 'DEFAULT', optional=True, additional=True))
473
        msg = self.layout_header['iow'].format('W/s')
474
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'io_counters' else 'DEFAULT', optional=True, additional=True))
475
        msg = self.layout_header['command'].format('Command')
476
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'name' else 'DEFAULT'))
477
478
    def __msg_curse_sum(self, ret, sep_char='_', mmm=None, args=None):
479
        """
480
        Build the sum message (only when filter is on) and add it to the ret dict.
481
482
        * ret: list of string where the message is added
483
        * sep_char: define the line separation char
484
        * mmm: display min, max, mean or current (if mmm=None)
485
        * args: Glances args
486
        """
487
        ret.append(self.curse_new_line())
488
        if mmm is None:
489
            ret.append(self.curse_add_line(sep_char * 69))
490
            ret.append(self.curse_new_line())
491
        # CPU percent sum
492
        msg = self.layout_stat['cpu'].format(self.__sum_stats('cpu_percent', mmm=mmm))
493
        ret.append(self.curse_add_line(msg,
494
                                       decoration=self.__mmm_deco(mmm)))
495
        # MEM percent sum
496
        msg = self.layout_stat['mem'].format(self.__sum_stats('memory_percent', mmm=mmm))
497
        ret.append(self.curse_add_line(msg,
498
                                       decoration=self.__mmm_deco(mmm)))
499
        # VIRT and RES memory sum
500
        if 'memory_info' in self.stats[0] and self.stats[0]['memory_info'] is not None and self.stats[0]['memory_info'] != '':
501
            # VMS
502
            msg = self.layout_stat['virt'].format(self.auto_unit(self.__sum_stats('memory_info', indice=1, mmm=mmm), low_precision=False))
503
            ret.append(self.curse_add_line(msg,
504
                                           decoration=self.__mmm_deco(mmm),
505
                                           optional=True))
506
            # RSS
507
            msg = self.layout_stat['res'].format(self.auto_unit(self.__sum_stats('memory_info', indice=0, mmm=mmm), low_precision=False))
508
            ret.append(self.curse_add_line(msg,
509
                                           decoration=self.__mmm_deco(mmm),
510
                                           optional=True))
511
        else:
512
            msg = self.layout_header['virt'].format('')
513
            ret.append(self.curse_add_line(msg))
514
            msg = self.layout_header['res'].format('')
515
            ret.append(self.curse_add_line(msg))
516
        # PID
517
        msg = self.layout_header['pid'].format('', width=self.__max_pid_size())
518
        ret.append(self.curse_add_line(msg))
519
        # USER
520
        msg = self.layout_header['user'].format('')
521
        ret.append(self.curse_add_line(msg))
522
        # TIME+
523
        msg = self.layout_header['time'].format('')
524
        ret.append(self.curse_add_line(msg, optional=True))
525
        # THREAD
526
        msg = self.layout_header['thread'].format('')
527
        ret.append(self.curse_add_line(msg))
528
        # NICE
529
        msg = self.layout_header['nice'].format('')
530
        ret.append(self.curse_add_line(msg))
531
        # STATUS
532
        msg = self.layout_header['status'].format('')
533
        ret.append(self.curse_add_line(msg))
534
        # IO read/write
535
        if 'io_counters' in self.stats[0] and mmm is None:
536
            # IO read
537
            io_rs = int((self.__sum_stats('io_counters', 0) - self.__sum_stats('io_counters', indice=2, mmm=mmm)) / self.stats[0]['time_since_update'])
538
            if io_rs == 0:
539
                msg = self.layout_stat['ior'].format('0')
540
            else:
541
                msg = self.layout_stat['ior'].format(self.auto_unit(io_rs, low_precision=True))
542
            ret.append(self.curse_add_line(msg,
543
                                           decoration=self.__mmm_deco(mmm),
544
                                           optional=True, additional=True))
545
            # IO write
546
            io_ws = int((self.__sum_stats('io_counters', 1) - self.__sum_stats('io_counters', indice=3, mmm=mmm)) / self.stats[0]['time_since_update'])
547
            if io_ws == 0:
548
                msg = self.layout_stat['iow'].format('0')
549
            else:
550
                msg = self.layout_stat['iow'].format(self.auto_unit(io_ws, low_precision=True))
551
            ret.append(self.curse_add_line(msg,
552
                                           decoration=self.__mmm_deco(mmm),
553
                                           optional=True, additional=True))
554
        else:
555
            msg = self.layout_header['ior'].format('')
556
            ret.append(self.curse_add_line(msg, optional=True, additional=True))
557
            msg = self.layout_header['iow'].format('')
558
            ret.append(self.curse_add_line(msg, optional=True, additional=True))
559
        if mmm is None:
560
            msg = ' < {}'.format('current')
561
            ret.append(self.curse_add_line(msg, optional=True))
562
        else:
563
            msg = ' < {}'.format(mmm)
564
            ret.append(self.curse_add_line(msg, optional=True))
565
            msg = ' (\'M\' to reset)'
566
            ret.append(self.curse_add_line(msg, optional=True))
567
568
    def __mmm_deco(self, mmm):
569
        """Return the decoration string for the current mmm status."""
570
        if mmm is not None:
571
            return 'DEFAULT'
572
        else:
573
            return 'FILTER'
574
575
    def __mmm_reset(self):
576
        """Reset the MMM stats."""
577
        self.mmm_min = {}
578
        self.mmm_max = {}
579
580
    def __sum_stats(self, key, indice=None, mmm=None):
581
        """Return the sum of the stats value for the given key.
582
583
        * indice: If indice is set, get the p[key][indice]
584
        * mmm: display min, max, mean or current (if mmm=None)
585
        """
586
        # Compute stats summary
587
        ret = 0
588
        for p in self.stats:
589
            if key not in p:
590
                # Correct issue #1188
591
                continue
592
            if p[key] is None:
593
                # Correct https://github.com/nicolargo/glances/issues/1105#issuecomment-363553788
594
                continue
595
            if indice is None:
596
                ret += p[key]
597
            else:
598
                ret += p[key][indice]
599
600
        # Manage Min/Max/Mean
601
        mmm_key = self.__mmm_key(key, indice)
602
        if mmm == 'min':
603
            try:
604
                if self.mmm_min[mmm_key] > ret:
605
                    self.mmm_min[mmm_key] = ret
606
            except AttributeError:
607
                self.mmm_min = {}
608
                return 0
609
            except KeyError:
610
                self.mmm_min[mmm_key] = ret
611
            ret = self.mmm_min[mmm_key]
612
        elif mmm == 'max':
613
            try:
614
                if self.mmm_max[mmm_key] < ret:
615
                    self.mmm_max[mmm_key] = ret
616
            except AttributeError:
617
                self.mmm_max = {}
618
                return 0
619
            except KeyError:
620
                self.mmm_max[mmm_key] = ret
621
            ret = self.mmm_max[mmm_key]
622
623
        return ret
624
625
    def __mmm_key(self, key, indice):
626
        ret = key
627
        if indice is not None:
628
            ret += str(indice)
629
        return ret
630
631
    def __sort_stats(self, sortedby=None):
632
        """Return the stats (dict) sorted by (sortedby)."""
633
        return sort_stats(self.stats, sortedby,
634
                          reverse=glances_processes.sort_reverse)
635
636
    def __max_pid_size(self):
637
        """Return the maximum PID size in number of char."""
638
        if self.pid_max is not None:
639
            return len(str(self.pid_max))
640
        else:
641
            # By default return 5 (corresponding to 99999 PID number)
642
            return 5
643