glances.processes   F
last analyzed

Complexity

Total Complexity 131

Size/Duplication

Total Lines 787
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 413
dl 0
loc 787
rs 2
c 0
b 0
f 0
wmc 131

6 Functions

Rating   Name   Duplication   Size   Complexity  
A _sort_cpu_times() 0 9 1
A _sort_io_counters() 0 6 1
A _sort_lambda() 0 3 1
A weighted() 0 3 2
B sort_stats() 0 29 8
A sort_by_these_keys() 0 2 2

53 Methods

Rating   Name   Duplication   Size   Complexity  
A GlancesProcesses.get_count() 0 3 1
A GlancesProcesses.processes_count() 0 4 1
A GlancesProcesses.export_process_filter() 0 4 1
A GlancesProcesses.is_selected_extended_process() 0 11 1
A GlancesProcesses.process_filter_input() 0 4 1
A GlancesProcesses.process_filter() 0 4 1
B GlancesProcesses.__get_min_max_mean() 0 26 6
A GlancesProcesses.update_list() 0 6 3
A GlancesProcesses.get_extended_stats() 0 9 3
A GlancesProcesses.max_processes() 0 4 1
A GlancesProcesses.get_pid_time_and_status() 0 11 1
A GlancesProcesses.remove_non_running_procs() 0 6 3
A GlancesProcesses.max_values() 0 3 1
A GlancesProcesses.compute_max_value() 0 5 3
A GlancesProcesses.get_io_counters() 0 24 4
A GlancesProcesses.set_sort_key() 0 8 2
A GlancesProcesses.set_max_values() 0 3 1
A GlancesProcesses.enable_extended() 0 4 1
A GlancesProcesses.nice_decrease() 0 10 2
B GlancesProcesses.update() 0 72 6
B GlancesProcesses.set_extended_stats() 0 46 5
A GlancesProcesses.disable() 0 3 1
A GlancesProcesses.get_displayed_attr() 0 5 2
A GlancesProcesses.process_filter_re() 0 4 1
A GlancesProcesses.reset_internal_cache() 0 7 2
A GlancesProcesses.sort_key() 0 4 1
A GlancesProcesses.reset_processcount() 0 3 1
A GlancesProcesses.__get_extended_memory_swap() 0 15 4
A GlancesProcesses.build_process_list() 0 19 2
A GlancesProcesses.get_export() 0 3 1
A GlancesProcesses.__init__() 0 62 1
A GlancesProcesses.get_max_values() 0 3 1
A GlancesProcesses.update_export_list() 0 6 3
A GlancesProcesses.disable_stats() 0 4 1
A GlancesProcesses.disable_extended() 0 3 1
A GlancesProcesses._test_grab() 0 23 5
A GlancesProcesses.process_filter_key() 0 4 1
A GlancesProcesses.kill() 0 7 1
A GlancesProcesses.sort_reverse() 0 7 3
A GlancesProcesses.nice_increase() 0 10 2
A GlancesProcesses.disable_kernel_threads() 0 3 1
A GlancesProcesses.get_cached_attrs() 0 2 1
A GlancesProcesses.set_args() 0 3 1
A GlancesProcesses.update_processcount() 0 15 4
A GlancesProcesses.reset_max_values() 0 5 2
A GlancesProcesses.enable() 0 4 1
A GlancesProcesses.maybe_add_cached_attrs() 0 15 2
A GlancesProcesses.__get_extended_connections() 0 17 3
A GlancesProcesses.get_list() 0 7 2
A GlancesProcesses.get_sorted_attrs() 0 5 2
A GlancesProcesses.get_stats() 0 3 1
A GlancesProcesses.pid_max() 0 32 4
B GlancesProcesses.maybe_add_cached_stats() 0 23 6

How to fix   Complexity   

Complexity

Complex classes like glances.processes 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
#
2
# This file is part of Glances.
3
#
4
# SPDX-FileCopyrightText: 2024 Nicolas Hennion <[email protected]>
5
#
6
# SPDX-License-Identifier: LGPL-3.0-only
7
#
8
9
import os
10
11
import psutil
12
13
from glances.filter import GlancesFilter, GlancesFilterList
14
from glances.globals import (
15
    BSD,
16
    LINUX,
17
    MACOS,
18
    WINDOWS,
19
    dictlist_first_key_value,
20
    list_of_namedtuple_to_list_of_dict,
21
    namedtuple_to_dict,
22
)
23
from glances.logger import logger
24
from glances.programs import processes_to_programs
25
from glances.timer import Timer, getTimeSinceLastUpdate
26
27
psutil_version_info = tuple([int(num) for num in psutil.__version__.split('.')])
28
29
# This constant defines the list of mandatory processes stats. Thoses stats can not be disabled by the user
30
mandatory_processes_stats_list = ['pid', 'name']
31
32
# This constant defines the list of available processes sort key
33
sort_processes_stats_list = ['cpu_percent', 'memory_percent', 'username', 'cpu_times', 'io_counters', 'name']
34
35
# Sort dictionary for human
36
sort_for_human = {
37
    'io_counters': 'disk IO',
38
    'cpu_percent': 'CPU consumption',
39
    'memory_percent': 'memory consumption',
40
    'cpu_times': 'process time',
41
    'username': 'user name',
42
    'name': 'processs name',
43
    None: 'None',
44
}
45
46
47
class GlancesProcesses:
48
    """Get processed stats using the psutil library."""
49
50
    def __init__(self, cache_timeout=60):
51
        """Init the class to collect stats about processes."""
52
        # Init the args, coming from the classes derived from GlancesMode
53
        # Should be set by the set_args method
54
        self.args = None
55
56
        # The internals caches will be cleaned each 'cache_timeout' seconds
57
        self.cache_timeout = cache_timeout
58
        # First iteration, no cache
59
        self.cache_timer = Timer(0)
60
61
        # Init the io_old dict used to compute the IO bitrate
62
        # key = pid
63
        # value = [ read_bytes_old, write_bytes_old ]
64
        self.io_old = {}
65
66
        # Init stats
67
        self.auto_sort = None
68
        self._sort_key = None
69
        # Default processes sort key is 'auto'
70
        # Can be overwrite from the configuration file (issue#1536) => See glances_processlist.py init
71
        self.set_sort_key('auto', auto=True)
72
        self.processlist = []
73
        self.reset_processcount()
74
75
        # Cache is a dict with key=pid and value = dict of cached value
76
        self.processlist_cache = {}
77
78
        # List of processes stats to export
79
        # Only process matching one of the filter will be exported
80
        self._filter_export = GlancesFilterList()
81
        self.processlist_export = []
82
83
        # Tag to enable/disable the processes stats (to reduce the Glances CPU consumption)
84
        # Default is to enable the processes stats
85
        self.disable_tag = False
86
87
        # Extended stats for top process is enable by default
88
        self.disable_extended_tag = False
89
        self.extended_process = None
90
91
        # Tests (and disable if not available) optionals features
92
        self._test_grab()
93
94
        # Maximum number of processes showed in the UI (None if no limit)
95
        self._max_processes = None
96
97
        # Process filter
98
        self._filter = GlancesFilter()
99
100
        # Whether or not to hide kernel threads
101
        self.no_kernel_threads = False
102
103
        # Store maximums values in a dict
104
        # Used in the UI to highlight the maximum value
105
        self._max_values_list = ('cpu_percent', 'memory_percent')
106
        # { 'cpu_percent': 0.0, 'memory_percent': 0.0 }
107
        self._max_values = {}
108
        self.reset_max_values()
109
110
        # Set the key's list be disabled in order to only display specific attribute in the process list
111
        self.disable_stats = []
112
113
    def _test_grab(self):
114
        """Test somes optionals features"""
115
        # Test if the system can grab io_counters
116
        try:
117
            p = psutil.Process()
118
            p.io_counters()
119
        except Exception as e:
120
            logger.warning(f'PsUtil can not grab processes io_counters ({e})')
121
            self.disable_io_counters = True
122
        else:
123
            logger.debug('PsUtil can grab processes io_counters')
124
            self.disable_io_counters = False
125
126
        # Test if the system can grab gids
127
        try:
128
            p = psutil.Process()
129
            p.gids()
130
        except Exception as e:
131
            logger.warning(f'PsUtil can not grab processes gids ({e})')
132
            self.disable_gids = True
133
        else:
134
            logger.debug('PsUtil can grab processes gids')
135
            self.disable_gids = False
136
137
    def set_args(self, args):
138
        """Set args."""
139
        self.args = args
140
141
    def reset_internal_cache(self):
142
        """Reset the internal cache."""
143
        self.cache_timer = Timer(0)
144
        self.processlist_cache = {}
145
        if hasattr(psutil.process_iter, 'cache_clear'):
146
            # Cache clear only available in PsUtil 6 or higher
147
            psutil.process_iter.cache_clear()
148
149
    def reset_processcount(self):
150
        """Reset the global process count"""
151
        self.processcount = {'total': 0, 'running': 0, 'sleeping': 0, 'thread': 0, 'pid_max': None}
152
153
    def update_processcount(self, plist):
154
        """Update the global process count from the current processes list"""
155
        # Update the maximum process ID (pid) number
156
        self.processcount['pid_max'] = self.pid_max
157
        # For each key in the processcount dict
158
        # count the number of processes with the same status
159
        for k in list(self.processcount.keys()):
160
            self.processcount[k] = len(list(filter(lambda v: v.get('status', '?') is k, plist)))
0 ignored issues
show
introduced by
The variable k does not seem to be defined in case the for loop on line 159 is not entered. Are you sure this can never be the case?
Loading history...
161
        # Compute thread
162
        try:
163
            self.processcount['thread'] = sum(i['num_threads'] for i in plist if i['num_threads'] is not None)
164
        except KeyError:
165
            self.processcount['thread'] = None
166
        # Compute total
167
        self.processcount['total'] = len(plist)
168
169
    def enable(self):
170
        """Enable process stats."""
171
        self.disable_tag = False
172
        self.update()
173
174
    def disable(self):
175
        """Disable process stats."""
176
        self.disable_tag = True
177
178
    def enable_extended(self):
179
        """Enable extended process stats."""
180
        self.disable_extended_tag = False
181
        self.update()
182
183
    def disable_extended(self):
184
        """Disable extended process stats."""
185
        self.disable_extended_tag = True
186
187
    @property
188
    def pid_max(self):
189
        """
190
        Get the maximum PID value.
191
192
        On Linux, the value is read from the `/proc/sys/kernel/pid_max` file.
193
194
        From `man 5 proc`:
195
        The default value for this file, 32768, results in the same range of
196
        PIDs as on earlier kernels. On 32-bit platforms, 32768 is the maximum
197
        value for pid_max. On 64-bit systems, pid_max can be set to any value
198
        up to 2^22 (PID_MAX_LIMIT, approximately 4 million).
199
200
        If the file is unreadable or not available for whatever reason,
201
        returns None.
202
203
        Some other OSes:
204
        - On FreeBSD and macOS the maximum is 99999.
205
        - On OpenBSD >= 6.0 the maximum is 99999 (was 32766).
206
        - On NetBSD the maximum is 30000.
207
208
        :returns: int or None
209
        """
210
        if LINUX:
211
            # XXX: waiting for https://github.com/giampaolo/psutil/issues/720
212
            try:
213
                with open('/proc/sys/kernel/pid_max', 'rb') as f:
214
                    return int(f.read())
215
            except OSError:
216
                return None
217
        else:
218
            return None
219
220
    @property
221
    def processes_count(self):
222
        """Get the current number of processes showed in the UI."""
223
        return min(self._max_processes - 2, glances_processes.processcount['total'] - 1)
224
225
    @property
226
    def max_processes(self):
227
        """Get the maximum number of processes showed in the UI."""
228
        return self._max_processes
229
230
    @max_processes.setter
231
    def max_processes(self, value):
232
        """Set the maximum number of processes showed in the UI."""
233
        self._max_processes = value
234
235
    @property
236
    def disable_stats(self):
237
        """Set disable_stats list"""
238
        return self._disable_stats
239
240
    @disable_stats.setter
241
    def disable_stats(self, stats_list):
242
        """Set disable_stats list"""
243
        self._disable_stats = [i for i in stats_list if i not in mandatory_processes_stats_list]
244
245
    @property
246
    def process_filter_input(self):
247
        """Get the process filter (given by the user)."""
248
        return self._filter.filter_input
249
250
    @property
251
    def process_filter(self):
252
        """Get the process filter (current apply filter)."""
253
        return self._filter.filter
254
255
    @process_filter.setter
256
    def process_filter(self, value):
257
        """Set the process filter."""
258
        self._filter.filter = value
259
260
    @property
261
    def process_filter_key(self):
262
        """Get the process filter key."""
263
        return self._filter.filter_key
264
265
    @property
266
    def process_filter_re(self):
267
        """Get the process regular expression compiled."""
268
        return self._filter.filter_re
269
270
    # Export filter
271
272
    @property
273
    def export_process_filter(self):
274
        """Get the export process filter (current export process filter list)."""
275
        return self._filter_export.filter
276
277
    @export_process_filter.setter
278
    def export_process_filter(self, value):
279
        """Set the export process filter list."""
280
        self._filter_export.filter = value
281
282
    # Kernel threads
283
284
    def disable_kernel_threads(self):
285
        """Ignore kernel threads in process list."""
286
        self.no_kernel_threads = True
287
288
    @property
289
    def sort_reverse(self):
290
        """Return True to sort processes in reverse 'key' order, False instead."""
291
        if self.sort_key == 'name' or self.sort_key == 'username':
292
            return False
293
294
        return True
295
296
    def max_values(self):
297
        """Return the max values dict."""
298
        return self._max_values
299
300
    def get_max_values(self, key):
301
        """Get the maximum values of the given stat (key)."""
302
        return self._max_values[key]
303
304
    def set_max_values(self, key, value):
305
        """Set the maximum value for a specific stat (key)."""
306
        self._max_values[key] = value
307
308
    def reset_max_values(self):
309
        """Reset the maximum values dict."""
310
        self._max_values = {}
311
        for k in self._max_values_list:
312
            self._max_values[k] = 0.0
313
314
    def set_extended_stats(self, proc):
315
        """Set the extended stats for the given PID."""
316
        # - cpu_affinity (Linux, Windows, FreeBSD)
317
        # - ionice (Linux and Windows > Vista)
318
        # - num_ctx_switches (not available on Illumos/Solaris)
319
        # - num_fds (Unix-like)
320
        # - num_handles (Windows)
321
        # - memory_maps (only swap, Linux)
322
        #   https://www.cyberciti.biz/faq/linux-which-process-is-using-swap/
323
        # - connections (TCP and UDP)
324
        # - CPU min/max/mean
325
326
        # Set the extended stats list (OS dependent)
327
        extended_stats = ['cpu_affinity', 'ionice', 'num_ctx_switches']
328
        if LINUX:
329
            # num_fds only available on Unix system (see issue #1351)
330
            extended_stats += ['num_fds']
331
        if WINDOWS:
332
            extended_stats += ['num_handles']
333
334
        ret = {}
335
        try:
336
            logger.debug('Grab extended stats for process {}'.format(proc['pid']))
337
338
            # Get PID of the selected process
339
            selected_process = psutil.Process(proc['pid'])
340
341
            # Get the extended stats for the selected process
342
            ret = selected_process.as_dict(attrs=extended_stats, ad_value=None)
343
344
            # Get memory swap for the selected process (Linux Only)
345
            ret['memory_swap'] = self.__get_extended_memory_swap(selected_process)
346
347
            # Get number of TCP and UDP network connections for the selected process
348
            ret['tcp'], ret['udp'] = self.__get_extended_connections(selected_process)
349
        except (psutil.NoSuchProcess, ValueError, AttributeError) as e:
350
            logger.error(f'Can not grab extended stats ({e})')
351
            self.extended_process = None
352
            ret['extended_stats'] = False
353
        else:
354
            # Compute CPU and MEM min/max/mean
355
            # Merge the returned dict with the current on
356
            ret.update(self.__get_min_max_mean(proc))
357
            self.extended_process = ret
358
            ret['extended_stats'] = True
359
        return namedtuple_to_dict(ret)
360
361
    def get_extended_stats(self):
362
        """Return the extended stats.
363
364
        Return the process stat when extended_stats = True
365
        """
366
        for p in self.processlist:
367
            if p.get('extended_stats'):
368
                return p
369
        return None
370
371
    def __get_min_max_mean(self, proc, prefix=['cpu', 'memory']):
372
        """Return the min/max/mean for the given process"""
373
        ret = {}
374
        for stat_prefix in prefix:
375
            min_key = stat_prefix + '_min'
376
            max_key = stat_prefix + '_max'
377
            mean_sum_key = stat_prefix + '_mean_sum'
378
            mean_counter_key = stat_prefix + '_mean_counter'
379
            if min_key not in self.extended_process:
380
                ret[min_key] = proc[stat_prefix + '_percent']
381
            else:
382
                ret[min_key] = min(proc[stat_prefix + '_percent'], self.extended_process[min_key])
383
            if max_key not in self.extended_process:
384
                ret[max_key] = proc[stat_prefix + '_percent']
385
            else:
386
                ret[max_key] = max(proc[stat_prefix + '_percent'], self.extended_process[max_key])
387
            if mean_sum_key not in self.extended_process:
388
                ret[mean_sum_key] = proc[stat_prefix + '_percent']
389
            else:
390
                ret[mean_sum_key] = self.extended_process[mean_sum_key] + proc[stat_prefix + '_percent']
391
            if mean_counter_key not in self.extended_process:
392
                ret[mean_counter_key] = 1
393
            else:
394
                ret[mean_counter_key] = self.extended_process[mean_counter_key] + 1
395
            ret[stat_prefix + '_mean'] = ret[mean_sum_key] / ret[mean_counter_key]
396
        return ret
397
398
    def __get_extended_memory_swap(self, process):
399
        """Return the memory swap for the given process"""
400
        if not LINUX:
401
            return None
402
        try:
403
            memory_swap = sum([v.swap for v in process.memory_maps()])
404
        except (psutil.NoSuchProcess, KeyError):
405
            # (KeyError catch for issue #1551)
406
            pass
407
        except (psutil.AccessDenied, NotImplementedError):
408
            # NotImplementedError: /proc/${PID}/smaps file doesn't exist
409
            # on kernel < 2.6.14 or CONFIG_MMU kernel configuration option
410
            # is not enabled (see psutil #533/glances #413).
411
            memory_swap = None
412
        return memory_swap
413
414
    def __get_extended_connections(self, process):
415
        """Return a tuple with (tcp, udp) connections count
416
        The code is compliant with both PsUtil<6 and Psutil>=6
417
        """
418
        try:
419
            # Hack for issue #2754 (PsUtil 6+)
420
            if psutil_version_info[0] >= 6:
421
                tcp = len(process.net_connections(kind="tcp"))
422
                udp = len(process.net_connections(kind="udp"))
423
            else:
424
                tcp = len(process.connections(kind="tcp"))
425
                udp = len(process.connections(kind="udp"))
426
        except (psutil.AccessDenied, psutil.NoSuchProcess):
427
            # Manage issue1283 (psutil.AccessDenied)
428
            tcp = None
429
            udp = None
430
        return tcp, udp
431
432
    def is_selected_extended_process(self, position):
433
        """Return True if the process is the selected one for extended stats."""
434
        return (
435
            hasattr(self.args, 'programs')
436
            and not self.args.programs
437
            and hasattr(self.args, 'enable_process_extended')
438
            and self.args.enable_process_extended
439
            and not self.disable_extended_tag
440
            and hasattr(self.args, 'cursor_position')
441
            and position == self.args.cursor_position
442
            and not self.args.disable_cursor
443
        )
444
445
    def build_process_list(self, sorted_attrs):
446
        # Build the processes stats list (it is why we need psutil>=5.3.0) (see issue #2755)
447
        processlist = list(
448
            filter(
449
                lambda p: not (BSD and p.info['name'] == 'idle')
450
                and not (WINDOWS and p.info['name'] == 'System Idle Process')
451
                and not (MACOS and p.info['name'] == 'kernel_task')
452
                and not (self.no_kernel_threads and LINUX and p.info['gids'].real == 0),
453
                psutil.process_iter(attrs=sorted_attrs, ad_value=None),
454
            )
455
        )
456
457
        # Only get the info key
458
        # PsUtil 6+ no longer check PID reused #2755 so use is_running in the loop
459
        # Note: not sure it is realy needed but CPU consumption look the same with or without it
460
        processlist = [p.info for p in processlist if p.is_running()]
461
462
        # Sort the processes list by the current sort_key
463
        return sort_stats(processlist, sorted_by=self.sort_key, reverse=True)
464
465
    def get_sorted_attrs(self):
466
        defaults = ['cpu_percent', 'cpu_times', 'memory_percent', 'name', 'status', 'num_threads']
467
        optional = ['io_counters'] if not self.disable_io_counters else []
468
469
        return defaults + optional
470
471
    def get_displayed_attr(self):
472
        defaults = ['memory_info', 'nice', 'pid']
473
        optional = ['gids'] if not self.disable_gids else []
474
475
        return defaults + optional
476
477
    def get_cached_attrs(self):
478
        return ['cmdline', 'username']
479
480
    def maybe_add_cached_attrs(self, sorted_attrs, cached_attrs):
481
        # Some stats are not sort key
482
        # An optimisation can be done be only grabbed displayed_attr
483
        # for displayed processes (but only in standalone mode...)
484
        sorted_attrs.extend(self.get_displayed_attr())
485
        # Some stats are cached (not necessary to be refreshed every time)
486
        if self.cache_timer.finished():
487
            sorted_attrs += cached_attrs
488
            self.cache_timer.set(self.cache_timeout)
489
            self.cache_timer.reset()
490
            is_cached = False
491
        else:
492
            is_cached = True
493
494
        return is_cached, sorted_attrs
495
496
    def get_pid_time_and_status(self, time_since_update, proc):
497
        # PID is the key
498
        proc['key'] = 'pid'
499
500
        # Time since last update (for disk_io rate computation)
501
        proc['time_since_update'] = time_since_update
502
503
        # Process status (only keep the first char)
504
        proc['status'] = str(proc.get('status', '?'))[:1].upper()
505
506
        return proc
507
508
    def get_io_counters(self, proc):
509
        # procstat['io_counters'] is a list:
510
        # [read_bytes, write_bytes, read_bytes_old, write_bytes_old, io_tag]
511
        # If io_tag = 0 > Access denied or first time (display "?")
512
        # If io_tag = 1 > No access denied (display the IO rate)
513
        if 'io_counters' in proc and proc['io_counters'] is not None:
514
            io_new = [proc['io_counters'][2], proc['io_counters'][3]]
515
            # For IO rate computation
516
            # Append saved IO r/w bytes
517
            try:
518
                proc['io_counters'] = io_new + self.io_old[proc['pid']]
519
                io_tag = 1
520
            except KeyError:
521
                proc['io_counters'] = io_new + [0, 0]
522
                io_tag = 0
523
            # then save the IO r/w bytes
524
            self.io_old[proc['pid']] = io_new
525
        else:
526
            proc['io_counters'] = [0, 0] + [0, 0]
527
            io_tag = 0
528
        # Append the IO tag (for display)
529
        proc['io_counters'] += [io_tag]
530
531
        return proc
532
533
    def maybe_add_cached_stats(self, is_cached, cached_attrs, proc):
534
        if is_cached:
535
            # Grab cached values (in case of a new incoming process)
536
            if proc['pid'] not in self.processlist_cache:
537
                try:
538
                    self.processlist_cache[proc['pid']] = psutil.Process(pid=proc['pid']).as_dict(
539
                        attrs=cached_attrs, ad_value=None
540
                    )
541
                except psutil.NoSuchProcess:
542
                    pass
543
            # Add cached value to current stat
544
            try:
545
                proc.update(self.processlist_cache[proc['pid']])
546
            except KeyError:
547
                pass
548
        else:
549
            # Save values to cache
550
            try:
551
                self.processlist_cache[proc['pid']] = {cached: proc[cached] for cached in cached_attrs}
552
            except KeyError:
553
                pass
554
555
        return proc
556
557
    def update(self):
558
        """Update the processes stats."""
559
        # Init new processes stats
560
        processlist = []
561
562
        # Do not process if disable tag is set
563
        if self.disable_tag:
564
            return processlist
565
566
        # Time since last update (for disk_io rate computation)
567
        time_since_update = getTimeSinceLastUpdate('process_disk')
568
569
        # Grab standard stats
570
        #####################
571
        sorted_attrs = self.get_sorted_attrs()
572
573
        # The following attributes are cached and only retrieve every self.cache_timeout seconds
574
        # Warning: 'name' can not be cached because it is used for filtering
575
        cached_attrs = self.get_cached_attrs()
576
577
        is_cached, sorted_attrs = self.maybe_add_cached_attrs(sorted_attrs, cached_attrs)
578
579
        # Remove attributes set by the user in the config file (see #1524)
580
        sorted_attrs = [i for i in sorted_attrs if i not in self.disable_stats]
581
        processlist = self.build_process_list(sorted_attrs)
582
583
        # Update the processcount
584
        self.update_processcount(processlist)
585
586
        # Loop over processes and :
587
        # - add extended stats for selected process
588
        # - add metadata
589
        for position, proc in enumerate(processlist):
590
            # Extended stats
591
            ################
592
593
            # Get the selected process when the 'e' key is pressed
594
            if self.is_selected_extended_process(position):
595
                self.extended_process = proc
596
597
            # Grab extended stats only for the selected process (see issue #2225)
598
            if self.extended_process is not None and proc['pid'] == self.extended_process['pid']:
599
                proc.update(self.set_extended_stats(self.extended_process))
600
                self.extended_process = namedtuple_to_dict(proc)
601
602
            # Meta data
603
            ###########
604
            proc = self.get_pid_time_and_status(time_since_update, proc)
605
606
            # Process IO
607
            proc = self.get_io_counters(proc)
608
609
            # Manage cached information
610
            proc = self.maybe_add_cached_stats(is_cached, cached_attrs, proc)
611
612
        # Remove non running process from the cache (avoid issue #2976)
613
        self.remove_non_running_procs(processlist)
614
615
        # Filter and transform process export list
616
        self.processlist_export = self.update_export_list(processlist)
617
618
        # Filter and transform process list
619
        processlist = self.update_list(processlist)
620
621
        # Compute the maximum value for keys in self._max_values_list: CPU, MEM
622
        # Useful to highlight the processes with maximum values
623
        self.compute_max_value(processlist)
624
625
        # Update the stats
626
        self.processlist = processlist
627
628
        return self.processlist
629
630
    def compute_max_value(self, processlist):
631
        for k in [i for i in self._max_values_list if i not in self.disable_stats]:
632
            values_list = [i[k] for i in processlist if i[k] is not None]
633
            if values_list:
634
                self.set_max_values(k, max(values_list))
635
636
    def remove_non_running_procs(self, processlist):
637
        pids_running = [p['pid'] for p in processlist]
638
        pids_cached = list(self.processlist_cache.keys()).copy()
639
        for pid in pids_cached:
640
            if pid not in pids_running:
641
                self.processlist_cache.pop(pid, None)
642
643
    def update_list(self, processlist):
644
        """Return the process list after filtering and transformation (namedtuple to dict)."""
645
        if self._filter.filter is None:
646
            return list_of_namedtuple_to_list_of_dict(processlist)
647
        ret = list(filter(lambda p: self._filter.is_filtered(p), processlist))
648
        return list_of_namedtuple_to_list_of_dict(ret)
649
650
    def update_export_list(self, processlist):
651
        """Return the process export list after filtering and transformation (namedtuple to dict)."""
652
        if self._filter_export.filter == []:
653
            return []
654
        ret = list(filter(lambda p: self._filter_export.is_filtered(p), processlist))
655
        return list_of_namedtuple_to_list_of_dict(ret)
656
657
    def get_count(self):
658
        """Get the number of processes."""
659
        return self.processcount
660
661
    def get_list(self, sorted_by=None, as_programs=False):
662
        """Get the processlist.
663
        By default, return the list of threads.
664
        If as_programs is True, return the list of programs."""
665
        if as_programs:
666
            return processes_to_programs(self.processlist)
667
        return self.processlist
668
669
    def get_export(self):
670
        """Return the processlist for export."""
671
        return self.processlist_export
672
673
    def get_stats(self, pid):
674
        """Get stats for the given pid."""
675
        return dictlist_first_key_value(self.processlist, 'pid', pid)
676
677
    @property
678
    def sort_key(self):
679
        """Get the current sort key."""
680
        return self._sort_key
681
682
    def set_sort_key(self, key, auto=True):
683
        """Set the current sort key."""
684
        if key == 'auto':
685
            self.auto_sort = True
686
            self._sort_key = 'cpu_percent'
687
        else:
688
            self.auto_sort = auto
689
            self._sort_key = key
690
691
    def nice_decrease(self, pid):
692
        """Decrease nice level
693
        On UNIX this is a number which usually goes from -20 to 20.
694
        The higher the nice value, the lower the priority of the process."""
695
        p = psutil.Process(pid)
696
        try:
697
            p.nice(p.nice() - 1)
698
            logger.info(f'Set nice level of process {pid} to {p.nice()} (higher the priority)')
699
        except psutil.AccessDenied:
700
            logger.warning(f'Can not decrease (higher the priority) the nice level of process {pid} (access denied)')
701
702
    def nice_increase(self, pid):
703
        """Increase nice level
704
        On UNIX this is a number which usually goes from -20 to 20.
705
        The higher the nice value, the lower the priority of the process."""
706
        p = psutil.Process(pid)
707
        try:
708
            p.nice(p.nice() + 1)
709
            logger.info(f'Set nice level of process {pid} to {p.nice()} (lower the priority)')
710
        except psutil.AccessDenied:
711
            logger.warning(f'Can not increase (lower the priority) the nice level of process {pid} (access denied)')
712
713
    def kill(self, pid, timeout=3):
714
        """Kill process with pid"""
715
        assert pid != os.getpid(), "Glances can kill itself..."
716
        p = psutil.Process(pid)
717
        logger.debug(f'Send kill signal to process: {p}')
718
        p.kill()
719
        return p.wait(timeout)
720
721
722
def weighted(value):
723
    """Manage None value in dict value."""
724
    return -float('inf') if value is None else value
725
726
727
def sort_by_these_keys(first, second):
728
    return lambda process: (weighted(process.get(first)), weighted(process.get(second)))
729
730
731
def _sort_io_counters(process, sorted_by='io_counters', sorted_by_secondary='memory_percent'):
732
    """Specific case for io_counters
733
734
    :return: Sum of io_r + io_w
735
    """
736
    return process[sorted_by][0] - process[sorted_by][2] + process[sorted_by][1] - process[sorted_by][3]
737
738
739
def _sort_cpu_times(process, sorted_by='cpu_times', sorted_by_secondary='memory_percent'):
740
    """Specific case for cpu_times
741
742
    Patch for "Sorting by process time works not as expected #1321"
743
    By default PsUtil only takes user time into account
744
    see (https://github.com/giampaolo/psutil/issues/1339)
745
    The following implementation takes user and system time into account
746
    """
747
    return process[sorted_by][0] + process[sorted_by][1]
748
749
750
def _sort_lambda(sorted_by='cpu_percent', sorted_by_secondary='memory_percent'):
751
    """Return a sort lambda function for the sorted_by key"""
752
    return {'io_counters': _sort_io_counters, 'cpu_times': _sort_cpu_times}.get(sorted_by, None)
753
754
755
def sort_stats(stats, sorted_by='cpu_percent', sorted_by_secondary='memory_percent', reverse=True):
756
    """Return the stats (dict) sorted by (sorted_by).
757
    A secondary sort key should be specified.
758
759
    Reverse the sort if reverse is True.
760
    """
761
    if sorted_by is None and sorted_by_secondary is None:
762
        # No need to sort...
763
        return stats
764
765
    # Check if a specific sort should be done
766
    sort_lambda = _sort_lambda(sorted_by=sorted_by, sorted_by_secondary=sorted_by_secondary)
767
768
    if sort_lambda is not None:
769
        # Specific sort
770
        try:
771
            stats = sorted(stats, key=sort_lambda, reverse=reverse)
772
        except Exception:
773
            # If an error is detected, fallback to cpu_percent
774
            stats = sorted(stats, key=sort_by_these_keys('cpu_percent', sorted_by_secondary), reverse=reverse)
775
    else:
776
        # Standard sort
777
        try:
778
            stats = sorted(stats, key=sort_by_these_keys(sorted_by, sorted_by_secondary), reverse=reverse)
779
        except (KeyError, TypeError):
780
            # Fallback to name
781
            stats.sort(key=lambda process: process['name'] if process['name'] is not None else '~', reverse=False)
782
783
    return stats
784
785
786
glances_processes = GlancesProcesses()
787