Test Failed
Push — master ( 7e7379...128504 )
by Nicolas
03:31
created

Plugin.get_nice_alert()   B

Complexity

Conditions 7

Size

Total Lines 19
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 18
nop 2
dl 0
loc 19
rs 8
c 0
b 0
f 0
1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of Glances.
4
#
5
# Copyright (C) 2022 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 copy
24
25
from glances.logger import logger
26
from glances.globals import WINDOWS
27
from glances.compat import key_exist_value_not_none_not_v
28
from glances.processes import glances_processes, sort_stats
29
from glances.outputs.glances_unicode import unicode_message
30
from glances.plugins.glances_core import Plugin as CorePlugin
31
from glances.plugins.glances_plugin import GlancesPlugin
32
from glances.programs import processes_to_programs
33
34
35
def seconds_to_hms(input_seconds):
36
    """Convert seconds to human-readable time."""
37
    minutes, seconds = divmod(input_seconds, 60)
38
    hours, minutes = divmod(minutes, 60)
39
40
    hours = int(hours)
41
    minutes = int(minutes)
42
    seconds = str(int(seconds)).zfill(2)
43
44
    return hours, minutes, seconds
45
46
47
def split_cmdline(bare_process_name, cmdline):
48
    """Return path, cmd and arguments for a process cmdline based on bare_process_name.
49
50
    If first argument of cmdline starts with the bare_process_name then
51
    cmdline will just be considered cmd and path will be empty (see https://github.com/nicolargo/glances/issues/1795)
52
53
    :param bare_process_name: Name of the process from psutil
54
    :param cmdline: cmdline from psutil
55
    :return: Tuple with three strings, which are path, cmd and arguments of the process
56
    """
57
    if cmdline[0].startswith(bare_process_name):
58
        path, cmd = "", cmdline[0]
59
    else:
60
        path, cmd = os.path.split(cmdline[0])
61
    arguments = ' '.join(cmdline[1:])
62
    return path, cmd, arguments
63
64
65
class Plugin(GlancesPlugin):
66
    """Glances' processes plugin.
67
68
    stats is a list
69
    """
70
71
    # Define the header layout of the processes list columns
72
    layout_header = {
73
        'cpu': '{:<6} ',
74
        'mem': '{:<5} ',
75
        'virt': '{:<5} ',
76
        'res': '{:<5} ',
77
        'pid': '{:>{width}} ',
78
        'user': '{:<10} ',
79
        'time': '{:>8} ',
80
        'thread': '{:<3} ',
81
        'nice': '{:>3} ',
82
        'status': '{:>1} ',
83
        'ior': '{:>4} ',
84
        'iow': '{:<4} ',
85
        'command': '{} {}',
86
    }
87
88
    # Define the stat layout of the processes list columns
89
    layout_stat = {
90
        'cpu': '{:<6.1f}',
91
        'cpu_no_digit': '{:<6.0f}',
92
        'mem': '{:<5.1f} ',
93
        'virt': '{:<5} ',
94
        'res': '{:<5} ',
95
        'pid': '{:>{width}} ',
96
        'user': '{:<10} ',
97
        'time': '{:>8} ',
98
        'thread': '{:<3} ',
99
        'nice': '{:>3} ',
100
        'status': '{:>1} ',
101
        'ior': '{:>4} ',
102
        'iow': '{:<4} ',
103
        'command': '{}',
104
        'name': '[{}]',
105
    }
106
107
    def __init__(self, args=None, config=None):
108
        """Init the plugin."""
109
        super(Plugin, self).__init__(args=args, config=config, stats_init_value=[])
110
111
        # We want to display the stat in the curse interface
112
        self.display_curse = True
113
114
        # Trying to display proc time
115
        self.tag_proc_time = True
116
117
        # Call CorePlugin to get the core number (needed when not in IRIX mode / Solaris mode)
118
        try:
119
            self.nb_log_core = CorePlugin(args=self.args).update()["log"]
120
        except Exception:
121
            self.nb_log_core = 0
122
123
        # Get the max values (dict)
124
        self.max_values = copy.deepcopy(glances_processes.max_values())
125
126
        # Get the maximum PID number
127
        # Use to optimize space (see https://github.com/nicolargo/glances/issues/959)
128
        self.pid_max = glances_processes.pid_max
129
130
        # Set the default sort key if it is defined in the configuration file
131
        if config is not None:
132
            if 'processlist' in config.as_dict() and 'sort_key' in config.as_dict()['processlist']:
133
                logger.debug(
134
                    'Configuration overwrites processes sort key by {}'.format(
135
                        config.as_dict()['processlist']['sort_key']
136
                    )
137
                )
138
                glances_processes.set_sort_key(config.as_dict()['processlist']['sort_key'], False)
139
140
        # The default sort key could also be overwrite by command line (see #1903)
141
        if args.sort_processes_key is not None:
142
            glances_processes.set_sort_key(args.sort_processes_key, False)
143
144
        # Note: 'glances_processes' is already init in the processes.py script
145
146
    def get_key(self):
147
        """Return the key of the list."""
148
        return 'pid'
149
150
    def update(self):
151
        """Update processes stats using the input method."""
152
        # Init new stats
153
        stats = self.get_init_value()
154
155
        if self.input_method == 'local':
156
            # Update stats using the standard system lib
157
            # Note: Update is done in the processcount plugin
158
            # Just return the processes list
159
            stats = glances_processes.getlist()
160
            if self.args.programs:
161
                stats = processes_to_programs(stats)
162
163
        elif self.input_method == 'snmp':
164
            # No SNMP grab for processes
165
            pass
166
167
        # Update the stats
168
        self.stats = stats
169
170
        # Get the max values (dict)
171
        # Use Deep copy to avoid change between update and display
172
        self.max_values = copy.deepcopy(glances_processes.max_values())
173
174
        return self.stats
175
176
    def get_nice_alert(self, value):
177
        """Return the alert relative to the Nice configuration list"""
178
        value = str(value)
179
        try:
180
            if value in self.get_limit('nice_critical'):
181
                return 'CRITICAL'
182
        except KeyError:
183
            pass
184
        try:
185
            if value in self.get_limit('nice_warning'):
186
                return 'WARNING'
187
        except KeyError:
188
            pass
189
        try:
190
            if value in self.get_limit('nice_careful'):
191
                return 'CAREFUL'
192
        except KeyError:
193
            pass
194
        return 'DEFAULT'
195
196
    def _get_process_curses_cpu(self, p, selected, args):
197
        """Return process CPU curses"""
198
        if key_exist_value_not_none_not_v('cpu_percent', p, ''):
199
            cpu_layout = self.layout_stat['cpu'] if p['cpu_percent'] < 100 else self.layout_stat['cpu_no_digit']
200
            if args.disable_irix and self.nb_log_core != 0:
201
                msg = cpu_layout.format(p['cpu_percent'] / float(self.nb_log_core))
202
            else:
203
                msg = cpu_layout.format(p['cpu_percent'])
204
            alert = self.get_alert(
205
                p['cpu_percent'],
206
                highlight_zero=False,
207
                is_max=(p['cpu_percent'] == self.max_values['cpu_percent']),
208
                header="cpu",
209
            )
210
            ret = self.curse_add_line(msg, alert)
211
        else:
212
            msg = self.layout_header['cpu'].format('?')
213
            ret = self.curse_add_line(msg)
214
        return ret
215
216
    def _get_process_curses_mem(self, p, selected, args):
217
        """Return process MEM curses"""
218
        if key_exist_value_not_none_not_v('memory_percent', p, ''):
219
            msg = self.layout_stat['mem'].format(p['memory_percent'])
220
            alert = self.get_alert(
221
                p['memory_percent'],
222
                highlight_zero=False,
223
                is_max=(p['memory_percent'] == self.max_values['memory_percent']),
224
                header="mem",
225
            )
226
            ret = self.curse_add_line(msg, alert)
227
        else:
228
            msg = self.layout_header['mem'].format('?')
229
            ret = self.curse_add_line(msg)
230
        return ret
231
232
    def _get_process_curses_vms(self, p, selected, args):
233
        """Return process VMS curses"""
234
        if key_exist_value_not_none_not_v('memory_info', p, ''):
235
            msg = self.layout_stat['virt'].format(self.auto_unit(p['memory_info'][1], low_precision=False))
236
            ret = self.curse_add_line(msg, optional=True)
237
        else:
238
            msg = self.layout_header['virt'].format('?')
239
            ret = self.curse_add_line(msg)
240
        return ret
241
242
    def _get_process_curses_rss(self, p, selected, args):
243
        """Return process RSS curses"""
244
        if key_exist_value_not_none_not_v('memory_info', p, ''):
245
            msg = self.layout_stat['res'].format(self.auto_unit(p['memory_info'][0], low_precision=False))
246
            ret = self.curse_add_line(msg, optional=True)
247
        else:
248
            msg = self.layout_header['res'].format('?')
249
            ret = self.curse_add_line(msg)
250
        return ret
251
252
    def _get_process_curses_username(self, p, selected, args):
253
        """Return process username curses"""
254
        if 'username' in p:
255
            # docker internal users are displayed as ints only, therefore str()
256
            # Correct issue #886 on Windows OS
257
            msg = self.layout_stat['user'].format(str(p['username'])[:9])
258
            ret = self.curse_add_line(msg)
259
        else:
260
            msg = self.layout_header['user'].format('?')
261
            ret = self.curse_add_line(msg)
262
        return ret
263
264
    def _get_process_curses_time(self, p, selected, args):
265
        """Return process time curses"""
266
        try:
267
            # Sum user and system time
268
            user_system_time = p['cpu_times'][0] + p['cpu_times'][1]
269
        except (OverflowError, TypeError):
270
            # Catch OverflowError on some Amazon EC2 server
271
            # See https://github.com/nicolargo/glances/issues/87
272
            # Also catch TypeError on macOS
273
            # See: https://github.com/nicolargo/glances/issues/622
274
            # logger.debug("Cannot get TIME+ ({})".format(e))
275
            msg = self.layout_header['time'].format('?')
276
            ret = self.curse_add_line(msg, optional=True)
277
        else:
278
            hours, minutes, seconds = seconds_to_hms(user_system_time)
279
            if hours > 99:
280
                msg = '{:<7}h'.format(hours)
281
            elif 0 < hours < 100:
282
                msg = '{}h{}:{}'.format(hours, minutes, seconds)
283
            else:
284
                msg = '{}:{}'.format(minutes, seconds)
285
            msg = self.layout_stat['time'].format(msg)
286
            if hours > 0:
287
                ret = self.curse_add_line(msg, decoration='CPU_TIME', optional=True)
288
            else:
289
                ret = self.curse_add_line(msg, optional=True)
290
        return ret
291
292
    def _get_process_curses_thread(self, p, selected, args):
293
        """Return process thread curses"""
294
        if 'num_threads' in p:
295
            num_threads = p['num_threads']
296
            if num_threads is None:
297
                num_threads = '?'
298
            msg = self.layout_stat['thread'].format(num_threads)
299
            ret = self.curse_add_line(msg)
300
        else:
301
            msg = self.layout_header['thread'].format('?')
302
            ret = self.curse_add_line(msg)
303
        return ret
304
305
    def _get_process_curses_nice(self, p, selected, args):
306
        """Return process nice curses"""
307
        if 'nice' in p:
308
            nice = p['nice']
309
            if nice is None:
310
                nice = '?'
311
            msg = self.layout_stat['nice'].format(nice)
312
            ret = self.curse_add_line(msg, decoration=self.get_nice_alert(nice))
313
        else:
314
            msg = self.layout_header['nice'].format('?')
315
            ret = self.curse_add_line(msg)
316
        return ret
317
318
    def _get_process_curses_status(self, p, selected, args):
319
        """Return process status curses"""
320
        if 'status' in p:
321
            status = p['status']
322
            msg = self.layout_stat['status'].format(status)
323
            if status == 'R':
324
                ret = self.curse_add_line(msg, decoration='STATUS')
325
            else:
326
                ret = self.curse_add_line(msg)
327
        else:
328
            msg = self.layout_header['status'].format('?')
329
            ret = self.curse_add_line(msg)
330
        return ret
331
332
    def _get_process_curses_io(self, p, selected, args, rorw='ior'):
333
        """Return process IO Read or Write curses"""
334
        if 'io_counters' in p and p['io_counters'][4] == 1 and p['time_since_update'] != 0:
335
            # Display rate if stats is available and io_tag ([4]) == 1
336
            # IO
337
            io = int(
338
                (p['io_counters'][0 if rorw == 'ior' else 1] - p['io_counters'][2 if rorw == 'ior' else 3])
339
                / p['time_since_update']
340
            )
341
            if io == 0:
342
                msg = self.layout_stat[rorw].format("0")
343
            else:
344
                msg = self.layout_stat[rorw].format(self.auto_unit(io, low_precision=True))
345
            ret = self.curse_add_line(msg, optional=True, additional=True)
346
        else:
347
            msg = self.layout_header[rorw].format("?")
348
            ret = self.curse_add_line(msg, optional=True, additional=True)
349
        return ret
350
351
    def _get_process_curses_io_read(self, p, selected, args):
352
        """Return process IO Read curses"""
353
        return self._get_process_curses_io(p, selected, args, rorw='ior')
354
355
    def _get_process_curses_io_write(self, p, selected, args):
356
        """Return process IO Write curses"""
357
        return self._get_process_curses_io(p, selected, args, rorw='iow')
358
359
    def get_process_curses_data(self, p, selected, args):
360
        """Get curses data to display for a process.
361
362
        - p is the process to display
363
        - selected is a tag=True if the selected process
364
        """
365
        ret = [self.curse_new_line()]
366
        # When a process is selected:
367
        # * display a special character at the beginning of the line
368
        # * underline the command name
369
        if args.is_standalone:
370
            ret.append(self.curse_add_line(unicode_message('PROCESS_SELECTOR') if selected else ' ', 'SELECTED'))
371
372
        # CPU
373
        ret.append(self._get_process_curses_cpu(p, selected, args))
374
375
        # MEM
376
        ret.append(self._get_process_curses_mem(p, selected, args))
377
        ret.append(self._get_process_curses_vms(p, selected, args))
378
        ret.append(self._get_process_curses_rss(p, selected, args))
379
380
        # PID
381
        if not self.args.programs:
382
            # Display processes, so the PID should be displayed
383
            msg = self.layout_stat['pid'].format(p['pid'], width=self.__max_pid_size())
384
        else:
385
            # Display programs, so the PID should not be displayed
386
            # Instead displays the number of childrens
387
            msg = self.layout_stat['pid'].format(
388
                len(p['childrens']) if 'childrens' in p else '_', width=self.__max_pid_size()
389
            )
390
        ret.append(self.curse_add_line(msg))
391
392
        # USER
393
        ret.append(self._get_process_curses_username(p, selected, args))
394
395
        # TIME+
396
        ret.append(self._get_process_curses_time(p, selected, args))
397
398
        # THREAD
399
        ret.append(self._get_process_curses_thread(p, selected, args))
400
401
        # NICE
402
        ret.append(self._get_process_curses_nice(p, selected, args))
403
404
        # STATUS
405
        ret.append(self._get_process_curses_status(p, selected, args))
406
407
        # IO read/write
408
        ret.append(self._get_process_curses_io_read(p, selected, args))
409
        ret.append(self._get_process_curses_io_write(p, selected, args))
410
411
        # Command line
412
        # If no command line for the process is available, fallback to the bare process name instead
413
        bare_process_name = p['name']
414
        cmdline = p.get('cmdline', '?')
415
416
        try:
417
            process_decoration = 'PROCESS_SELECTED' if (selected and args.is_standalone) else 'PROCESS'
418
            if cmdline:
419
                path, cmd, arguments = split_cmdline(bare_process_name, cmdline)
420
                # Manage end of line in arguments (see #1692)
421
                arguments.replace('\r\n', ' ')
422
                arguments.replace('\n', ' ')
423
                if os.path.isdir(path) and not args.process_short_name:
424
                    msg = self.layout_stat['command'].format(path) + os.sep
425
                    ret.append(self.curse_add_line(msg, splittable=True))
426
                    ret.append(self.curse_add_line(cmd, decoration=process_decoration, splittable=True))
427
                else:
428
                    msg = self.layout_stat['command'].format(cmd)
429
                    ret.append(self.curse_add_line(msg, decoration=process_decoration, splittable=True))
430
                if arguments:
431
                    msg = ' ' + self.layout_stat['command'].format(arguments)
432
                    ret.append(self.curse_add_line(msg, splittable=True))
433
            else:
434
                msg = self.layout_stat['name'].format(bare_process_name)
435
                ret.append(self.curse_add_line(msg, decoration=process_decoration, splittable=True))
436
        except (TypeError, UnicodeEncodeError) as e:
437
            # Avoid crash after running fine for several hours #1335
438
            logger.debug("Can not decode command line '{}' ({})".format(cmdline, e))
439
            ret.append(self.curse_add_line('', splittable=True))
440
441
        # Add extended stats but only for the top processes
442
        if args.cursor_position == 0 and 'extended_stats' in p and args.enable_process_extended:
443
            # Left padding
444
            xpad = ' ' * 13
445
            # First line is CPU affinity
446
            if 'cpu_affinity' in p and p['cpu_affinity'] is not None:
447
                ret.append(self.curse_new_line())
448
                msg = xpad + 'CPU affinity: ' + str(len(p['cpu_affinity'])) + ' cores'
449
                ret.append(self.curse_add_line(msg, splittable=True))
450
            # Second line is memory info
451
            if 'memory_info' in p and p['memory_info'] is not None:
452
                ret.append(self.curse_new_line())
453
                msg = '{}Memory info: {}'.format(xpad, p['memory_info'])
454
                if 'memory_swap' in p and p['memory_swap'] is not None:
455
                    msg += ' swap ' + self.auto_unit(p['memory_swap'], low_precision=False)
456
                ret.append(self.curse_add_line(msg, splittable=True))
457
            # Third line is for open files/network sessions
458
            msg = ''
459
            if 'num_threads' in p and p['num_threads'] is not None:
460
                msg += str(p['num_threads']) + ' threads '
461
            if 'num_fds' in p and p['num_fds'] is not None:
462
                msg += str(p['num_fds']) + ' files '
463
            if 'num_handles' in p and p['num_handles'] is not None:
464
                msg += str(p['num_handles']) + ' handles '
465
            if 'tcp' in p and p['tcp'] is not None:
466
                msg += str(p['tcp']) + ' TCP '
467
            if 'udp' in p and p['udp'] is not None:
468
                msg += str(p['udp']) + ' UDP'
469
            if msg != '':
470
                ret.append(self.curse_new_line())
471
                msg = xpad + 'Open: ' + msg
472
                ret.append(self.curse_add_line(msg, splittable=True))
473
            # Fourth line is IO nice level (only Linux and Windows OS)
474
            if 'ionice' in p and p['ionice'] is not None and hasattr(p['ionice'], 'ioclass'):
475
                ret.append(self.curse_new_line())
476
                msg = xpad + 'IO nice: '
477
                k = 'Class is '
478
                v = p['ionice'].ioclass
479
                # Linux: The scheduling class. 0 for none, 1 for real time, 2 for best-effort, 3 for idle.
480
                # Windows: On Windows only ioclass is used and it can be set to 2 (normal), 1 (low) or 0 (very low).
481
                if WINDOWS:
482
                    if v == 0:
483
                        msg += k + 'Very Low'
484
                    elif v == 1:
485
                        msg += k + 'Low'
486
                    elif v == 2:
487
                        msg += 'No specific I/O priority'
488
                    else:
489
                        msg += k + str(v)
490
                else:
491
                    if v == 0:
492
                        msg += 'No specific I/O priority'
493
                    elif v == 1:
494
                        msg += k + 'Real Time'
495
                    elif v == 2:
496
                        msg += k + 'Best Effort'
497
                    elif v == 3:
498
                        msg += k + 'IDLE'
499
                    else:
500
                        msg += k + str(v)
501
                #  value is a number which goes from 0 to 7.
502
                # The higher the value, the lower the I/O priority of the process.
503
                if hasattr(p['ionice'], 'value') and p['ionice'].value != 0:
504
                    msg += ' (value %s/7)' % str(p['ionice'].value)
505
                ret.append(self.curse_add_line(msg, splittable=True))
506
507
        return ret
508
509
    def msg_curse(self, args=None, max_width=None):
510
        """Return the dict to display in the curse interface."""
511
        # Init the return message
512
        ret = []
513
514
        # Only process if stats exist and display plugin enable...
515
        if not self.stats or args.disable_process:
516
            return ret
517
518
        # Compute the sort key
519
        process_sort_key = glances_processes.sort_key
520
521
        # Header
522
        self.__msg_curse_header(ret, process_sort_key, args)
523
524
        # Process list
525
        # Loop over processes (sorted by the sort key previously compute)
526
        i = 0
527
        for p in self.__sort_stats(process_sort_key):
528
            ret.extend(self.get_process_curses_data(p, i == args.cursor_position, args))
529
            i += 1
530
531
        # A filter is set Display the stats summaries
532
        if glances_processes.process_filter is not None:
533
            if args.reset_minmax_tag:
534
                args.reset_minmax_tag = not args.reset_minmax_tag
535
                self.__mmm_reset()
536
            self.__msg_curse_sum(ret, args=args)
537
            self.__msg_curse_sum(ret, mmm='min', args=args)
538
            self.__msg_curse_sum(ret, mmm='max', args=args)
539
540
        # Return the message with decoration
541
        return ret
542
543
    def __msg_curse_header(self, ret, process_sort_key, args=None):
544
        """Build the header and add it to the ret dict."""
545
        sort_style = 'SORT'
546
547
        if args.disable_irix and 0 < self.nb_log_core < 10:
548
            msg = self.layout_header['cpu'].format('CPU%/' + str(self.nb_log_core))
549
        elif args.disable_irix and self.nb_log_core != 0:
550
            msg = self.layout_header['cpu'].format('CPU%/C')
551
        else:
552
            msg = self.layout_header['cpu'].format('CPU%')
553
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'cpu_percent' else 'DEFAULT'))
554
        msg = self.layout_header['mem'].format('MEM%')
555
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'memory_percent' else 'DEFAULT'))
556
        msg = self.layout_header['virt'].format('VIRT')
557
        ret.append(self.curse_add_line(msg, optional=True))
558
        msg = self.layout_header['res'].format('RES')
559
        ret.append(self.curse_add_line(msg, optional=True))
560
        if not self.args.programs:
561
            msg = self.layout_header['pid'].format('PID', width=self.__max_pid_size())
562
        else:
563
            msg = self.layout_header['pid'].format('NPROCS', width=self.__max_pid_size())
564
        ret.append(self.curse_add_line(msg))
565
        msg = self.layout_header['user'].format('USER')
566
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'username' else 'DEFAULT'))
567
        msg = self.layout_header['time'].format('TIME+')
568
        ret.append(
569
            self.curse_add_line(msg, sort_style if process_sort_key == 'cpu_times' else 'DEFAULT', optional=True)
570
        )
571
        msg = self.layout_header['thread'].format('THR')
572
        ret.append(self.curse_add_line(msg))
573
        msg = self.layout_header['nice'].format('NI')
574
        ret.append(self.curse_add_line(msg))
575
        msg = self.layout_header['status'].format('S')
576
        ret.append(self.curse_add_line(msg))
577
        msg = self.layout_header['ior'].format('R/s')
578
        ret.append(
579
            self.curse_add_line(
580
                msg, sort_style if process_sort_key == 'io_counters' else 'DEFAULT', optional=True, additional=True
581
            )
582
        )
583
        msg = self.layout_header['iow'].format('W/s')
584
        ret.append(
585
            self.curse_add_line(
586
                msg, sort_style if process_sort_key == 'io_counters' else 'DEFAULT', optional=True, additional=True
587
            )
588
        )
589
        if not self.args.programs:
590
            msg = self.layout_header['command'].format('Command', "('k' to kill)" if args.is_standalone else "")
591
        else:
592
            msg = self.layout_header['command'].format('Programs', "('k' to kill)" if args.is_standalone else "")
593
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'name' else 'DEFAULT'))
594
595
    def __msg_curse_sum(self, ret, sep_char='_', mmm=None, args=None):
596
        """
597
        Build the sum message (only when filter is on) and add it to the ret dict.
598
599
        :param ret: list of string where the message is added
600
        :param sep_char: define the line separation char
601
        :param mmm: display min, max, mean or current (if mmm=None)
602
        :param args: Glances args
603
        """
604
        ret.append(self.curse_new_line())
605
        if mmm is None:
606
            ret.append(self.curse_add_line(sep_char * 69))
607
            ret.append(self.curse_new_line())
608
        # CPU percent sum
609
        msg = self.layout_stat['cpu'].format(self.__sum_stats('cpu_percent', mmm=mmm))
610
        ret.append(self.curse_add_line(msg, decoration=self.__mmm_deco(mmm)))
611
        # MEM percent sum
612
        msg = self.layout_stat['mem'].format(self.__sum_stats('memory_percent', mmm=mmm))
613
        ret.append(self.curse_add_line(msg, decoration=self.__mmm_deco(mmm)))
614
        # VIRT and RES memory sum
615
        if (
616
            'memory_info' in self.stats[0]
617
            and self.stats[0]['memory_info'] is not None
618
            and self.stats[0]['memory_info'] != ''
619
        ):
620
            # VMS
621
            msg = self.layout_stat['virt'].format(
622
                self.auto_unit(self.__sum_stats('memory_info', indice=1, mmm=mmm), low_precision=False)
623
            )
624
            ret.append(self.curse_add_line(msg, decoration=self.__mmm_deco(mmm), optional=True))
625
            # RSS
626
            msg = self.layout_stat['res'].format(
627
                self.auto_unit(self.__sum_stats('memory_info', indice=0, mmm=mmm), low_precision=False)
628
            )
629
            ret.append(self.curse_add_line(msg, decoration=self.__mmm_deco(mmm), optional=True))
630
        else:
631
            msg = self.layout_header['virt'].format('')
632
            ret.append(self.curse_add_line(msg))
633
            msg = self.layout_header['res'].format('')
634
            ret.append(self.curse_add_line(msg))
635
        # PID
636
        msg = self.layout_header['pid'].format('', width=self.__max_pid_size())
637
        ret.append(self.curse_add_line(msg))
638
        # USER
639
        msg = self.layout_header['user'].format('')
640
        ret.append(self.curse_add_line(msg))
641
        # TIME+
642
        msg = self.layout_header['time'].format('')
643
        ret.append(self.curse_add_line(msg, optional=True))
644
        # THREAD
645
        msg = self.layout_header['thread'].format('')
646
        ret.append(self.curse_add_line(msg))
647
        # NICE
648
        msg = self.layout_header['nice'].format('')
649
        ret.append(self.curse_add_line(msg))
650
        # STATUS
651
        msg = self.layout_header['status'].format('')
652
        ret.append(self.curse_add_line(msg))
653
        # IO read/write
654
        if 'io_counters' in self.stats[0] and mmm is None:
655
            # IO read
656
            io_rs = int(
657
                (self.__sum_stats('io_counters', 0) - self.__sum_stats('io_counters', indice=2, mmm=mmm))
658
                / self.stats[0]['time_since_update']
659
            )
660
            if io_rs == 0:
661
                msg = self.layout_stat['ior'].format('0')
662
            else:
663
                msg = self.layout_stat['ior'].format(self.auto_unit(io_rs, low_precision=True))
664
            ret.append(self.curse_add_line(msg, decoration=self.__mmm_deco(mmm), optional=True, additional=True))
665
            # IO write
666
            io_ws = int(
667
                (self.__sum_stats('io_counters', 1) - self.__sum_stats('io_counters', indice=3, mmm=mmm))
668
                / self.stats[0]['time_since_update']
669
            )
670
            if io_ws == 0:
671
                msg = self.layout_stat['iow'].format('0')
672
            else:
673
                msg = self.layout_stat['iow'].format(self.auto_unit(io_ws, low_precision=True))
674
            ret.append(self.curse_add_line(msg, decoration=self.__mmm_deco(mmm), optional=True, additional=True))
675
        else:
676
            msg = self.layout_header['ior'].format('')
677
            ret.append(self.curse_add_line(msg, optional=True, additional=True))
678
            msg = self.layout_header['iow'].format('')
679
            ret.append(self.curse_add_line(msg, optional=True, additional=True))
680
        if mmm is None:
681
            msg = ' < {}'.format('current')
682
            ret.append(self.curse_add_line(msg, optional=True))
683
        else:
684
            msg = ' < {}'.format(mmm)
685
            ret.append(self.curse_add_line(msg, optional=True))
686
            msg = ' (\'M\' to reset)'
687
            ret.append(self.curse_add_line(msg, optional=True))
688
689
    def __mmm_deco(self, mmm):
690
        """Return the decoration string for the current mmm status."""
691
        if mmm is not None:
692
            return 'DEFAULT'
693
        else:
694
            return 'FILTER'
695
696
    def __mmm_reset(self):
697
        """Reset the MMM stats."""
698
        self.mmm_min = {}
699
        self.mmm_max = {}
700
701
    def __sum_stats(self, key, indice=None, mmm=None):
702
        """Return the sum of the stats value for the given key.
703
704
        :param indice: If indice is set, get the p[key][indice]
705
        :param mmm: display min, max, mean or current (if mmm=None)
706
        """
707
        # Compute stats summary
708
        ret = 0
709
        for p in self.stats:
710
            if key not in p:
711
                # Correct issue #1188
712
                continue
713
            if p[key] is None:
714
                # Correct https://github.com/nicolargo/glances/issues/1105#issuecomment-363553788
715
                continue
716
            if indice is None:
717
                ret += p[key]
718
            else:
719
                ret += p[key][indice]
720
721
        # Manage Min/Max/Mean
722
        mmm_key = self.__mmm_key(key, indice)
723
        if mmm == 'min':
724
            try:
725
                if self.mmm_min[mmm_key] > ret:
726
                    self.mmm_min[mmm_key] = ret
727
            except AttributeError:
728
                self.mmm_min = {}
729
                return 0
730
            except KeyError:
731
                self.mmm_min[mmm_key] = ret
732
            ret = self.mmm_min[mmm_key]
733
        elif mmm == 'max':
734
            try:
735
                if self.mmm_max[mmm_key] < ret:
736
                    self.mmm_max[mmm_key] = ret
737
            except AttributeError:
738
                self.mmm_max = {}
739
                return 0
740
            except KeyError:
741
                self.mmm_max[mmm_key] = ret
742
            ret = self.mmm_max[mmm_key]
743
744
        return ret
745
746
    def __mmm_key(self, key, indice):
747
        ret = key
748
        if indice is not None:
749
            ret += str(indice)
750
        return ret
751
752
    def __sort_stats(self, sorted_by=None):
753
        """Return the stats (dict) sorted by (sorted_by)."""
754
        return sort_stats(self.stats, sorted_by, reverse=glances_processes.sort_reverse)
755
756
    def __max_pid_size(self):
757
        """Return the maximum PID size in number of char."""
758
        if self.pid_max is not None:
759
            return len(str(self.pid_max))
760
        else:
761
            # By default return 5 (corresponding to 99999 PID number)
762
            return 5
763