Test Failed
Push — master ( 1c2b07...0e7919 )
by Nicolas
03:16
created

Plugin.__mmm_reset()   A

Complexity

Conditions 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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