Test Failed
Push — master ( ce0fc3...e09530 )
by Nicolas
03:36
created

glances.processes.GlancesProcesses.update()   B

Complexity

Conditions 6

Size

Total Lines 72
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 26
nop 1
dl 0
loc 72
rs 8.3226
c 0
b 0
f 0

How to fix   Long Method   

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:

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