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

glances/plugins/glances_processlist.py (10 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
        '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):
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 (24/15).
Loading history...
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'] != '':
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...
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'] != '':
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...
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'] != '':
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...
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))
0 ignored issues
show
This line is too long as per the coding-style (95/80).

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

Loading history...
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
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...
379
                # 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...
380
                # 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...
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.
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...
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