Test Failed
Push — develop ( 30cc9f...48251c )
by Nicolas
02:03
created

PluginModel.__msg_curse_header()   F

Complexity

Conditions 17

Size

Total Lines 55
Code Lines 44

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 17
eloc 44
nop 4
dl 0
loc 55
rs 1.8
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like glances.plugins.processlist.PluginModel.__msg_curse_header() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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