1
|
|
|
# -*- coding: utf-8 -*- |
|
|
|
|
2
|
|
|
# |
3
|
|
|
# This file is part of Glances. |
4
|
|
|
# |
5
|
|
|
# Copyright (C) 2019 Nicolargo <[email protected]> |
6
|
|
|
# |
7
|
|
|
# Glances is free software; you can redistribute it and/or modify |
8
|
|
|
# it under the terms of the GNU Lesser General Public License as published by |
9
|
|
|
# the Free Software Foundation, either version 3 of the License, or |
10
|
|
|
# (at your option) any later version. |
11
|
|
|
# |
12
|
|
|
# Glances is distributed in the hope that it will be useful, |
13
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
14
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15
|
|
|
# GNU Lesser General Public License for more details. |
16
|
|
|
# |
17
|
|
|
# You should have received a copy of the GNU Lesser General Public License |
18
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
19
|
|
|
|
20
|
|
|
import operator |
|
|
|
|
21
|
|
|
import os |
|
|
|
|
22
|
|
|
|
23
|
|
|
from glances.compat import iteritems, itervalues, listitems, iterkeys |
|
|
|
|
24
|
|
|
from glances.globals import BSD, LINUX, MACOS, SUNOS, WINDOWS, WSL |
|
|
|
|
25
|
|
|
from glances.timer import Timer, getTimeSinceLastUpdate |
|
|
|
|
26
|
|
|
from glances.filter import GlancesFilter |
|
|
|
|
27
|
|
|
from glances.logger import logger |
|
|
|
|
28
|
|
|
|
29
|
|
|
import psutil |
|
|
|
|
30
|
|
|
|
31
|
|
|
|
32
|
|
|
class GlancesProcesses(object): |
|
|
|
|
33
|
|
|
"""Get processed stats using the psutil library.""" |
34
|
|
|
|
35
|
|
|
def __init__(self, cache_timeout=60): |
36
|
|
|
"""Init the class to collect stats about processes.""" |
37
|
|
|
# Add internals caches because psutil do not cache all the stats |
38
|
|
|
# See: https://code.google.com/p/psutil/issues/detail?id=462 |
39
|
|
|
self.username_cache = {} |
40
|
|
|
self.cmdline_cache = {} |
41
|
|
|
|
42
|
|
|
# The internals caches will be cleaned each 'cache_timeout' seconds |
43
|
|
|
self.cache_timeout = cache_timeout |
44
|
|
|
self.cache_timer = Timer(self.cache_timeout) |
45
|
|
|
|
46
|
|
|
# Init the io dict |
47
|
|
|
# key = pid |
48
|
|
|
# value = [ read_bytes_old, write_bytes_old ] |
49
|
|
|
self.io_old = {} |
50
|
|
|
|
51
|
|
|
# Init stats |
52
|
|
|
self.auto_sort = None |
53
|
|
|
self._sort_key = None |
54
|
|
|
# Default processes sort key is 'auto' |
55
|
|
|
# Can be overwrite from the configuration file (issue#1536) => See glances_processlist.py init |
|
|
|
|
56
|
|
|
self.set_sort_key('auto', auto=True) |
57
|
|
|
self.processlist = [] |
58
|
|
|
self.reset_processcount() |
59
|
|
|
|
60
|
|
|
# Tag to enable/disable the processes stats (to reduce the Glances CPU consumption) |
|
|
|
|
61
|
|
|
# Default is to enable the processes stats |
62
|
|
|
self.disable_tag = False |
63
|
|
|
|
64
|
|
|
# Extended stats for top process is enable by default |
65
|
|
|
self.disable_extended_tag = False |
66
|
|
|
|
67
|
|
|
# Test if the system can grab io_counters |
68
|
|
|
try: |
69
|
|
|
p = psutil.Process() |
|
|
|
|
70
|
|
|
p.io_counters() |
71
|
|
|
except Exception as e: |
|
|
|
|
72
|
|
|
logger.warning('PsUtil can not grab processes io_counters ({})'.format(e)) |
|
|
|
|
73
|
|
|
self.disable_io_counters = True |
74
|
|
|
else: |
75
|
|
|
logger.debug('PsUtil can grab processes io_counters') |
76
|
|
|
self.disable_io_counters = False |
77
|
|
|
|
78
|
|
|
# Test if the system can grab gids |
79
|
|
|
try: |
80
|
|
|
p = psutil.Process() |
|
|
|
|
81
|
|
|
p.gids() |
82
|
|
|
except Exception as e: |
|
|
|
|
83
|
|
|
logger.warning('PsUtil can not grab processes gids ({})'.format(e)) |
|
|
|
|
84
|
|
|
self.disable_gids = True |
85
|
|
|
else: |
86
|
|
|
logger.debug('PsUtil can grab processes gids') |
87
|
|
|
self.disable_gids = False |
88
|
|
|
|
89
|
|
|
# Maximum number of processes showed in the UI (None if no limit) |
90
|
|
|
self._max_processes = None |
91
|
|
|
|
92
|
|
|
# Process filter is a regular expression |
93
|
|
|
self._filter = GlancesFilter() |
94
|
|
|
|
95
|
|
|
# Whether or not to hide kernel threads |
96
|
|
|
self.no_kernel_threads = False |
97
|
|
|
|
98
|
|
|
# Store maximums values in a dict |
99
|
|
|
# Used in the UI to highlight the maximum value |
100
|
|
|
self._max_values_list = ('cpu_percent', 'memory_percent') |
101
|
|
|
# { 'cpu_percent': 0.0, 'memory_percent': 0.0 } |
102
|
|
|
self._max_values = {} |
103
|
|
|
self.reset_max_values() |
104
|
|
|
|
105
|
|
|
def reset_processcount(self): |
106
|
|
|
"""Reset the global process count""" |
107
|
|
|
self.processcount = {'total': 0, |
108
|
|
|
'running': 0, |
109
|
|
|
'sleeping': 0, |
110
|
|
|
'thread': 0, |
111
|
|
|
'pid_max': None} |
112
|
|
|
|
113
|
|
|
def update_processcount(self, plist): |
114
|
|
|
"""Update the global process count from the current processes list""" |
115
|
|
|
# Update the maximum process ID (pid) number |
116
|
|
|
self.processcount['pid_max'] = self.pid_max |
117
|
|
|
# For each key in the processcount dict |
118
|
|
|
# count the number of processes with the same status |
119
|
|
|
for k in iterkeys(self.processcount): |
120
|
|
|
self.processcount[k] = len(list(filter(lambda v: v['status'] is k, |
|
|
|
|
121
|
|
|
plist))) |
122
|
|
|
# Compute thread |
123
|
|
|
self.processcount['thread'] = sum(i['num_threads'] for i in plist |
124
|
|
|
if i['num_threads'] is not None) |
125
|
|
|
# Compute total |
126
|
|
|
self.processcount['total'] = len(plist) |
127
|
|
|
|
128
|
|
|
def enable(self): |
129
|
|
|
"""Enable process stats.""" |
130
|
|
|
self.disable_tag = False |
131
|
|
|
self.update() |
132
|
|
|
|
133
|
|
|
def disable(self): |
134
|
|
|
"""Disable process stats.""" |
135
|
|
|
self.disable_tag = True |
136
|
|
|
|
137
|
|
|
def enable_extended(self): |
138
|
|
|
"""Enable extended process stats.""" |
139
|
|
|
self.disable_extended_tag = False |
140
|
|
|
self.update() |
141
|
|
|
|
142
|
|
|
def disable_extended(self): |
143
|
|
|
"""Disable extended process stats.""" |
144
|
|
|
self.disable_extended_tag = True |
145
|
|
|
|
146
|
|
|
@property |
147
|
|
|
def pid_max(self): |
148
|
|
|
""" |
149
|
|
|
Get the maximum PID value. |
150
|
|
|
|
151
|
|
|
On Linux, the value is read from the `/proc/sys/kernel/pid_max` file. |
152
|
|
|
|
153
|
|
|
From `man 5 proc`: |
154
|
|
|
The default value for this file, 32768, results in the same range of |
155
|
|
|
PIDs as on earlier kernels. On 32-bit platfroms, 32768 is the maximum |
156
|
|
|
value for pid_max. On 64-bit systems, pid_max can be set to any value |
157
|
|
|
up to 2^22 (PID_MAX_LIMIT, approximately 4 million). |
158
|
|
|
|
159
|
|
|
If the file is unreadable or not available for whatever reason, |
160
|
|
|
returns None. |
161
|
|
|
|
162
|
|
|
Some other OSes: |
163
|
|
|
- On FreeBSD and macOS the maximum is 99999. |
164
|
|
|
- On OpenBSD >= 6.0 the maximum is 99999 (was 32766). |
165
|
|
|
- On NetBSD the maximum is 30000. |
166
|
|
|
|
167
|
|
|
:returns: int or None |
168
|
|
|
""" |
169
|
|
|
if LINUX: |
170
|
|
|
# XXX: waiting for https://github.com/giampaolo/psutil/issues/720 |
|
|
|
|
171
|
|
|
try: |
172
|
|
|
with open('/proc/sys/kernel/pid_max', 'rb') as f: |
|
|
|
|
173
|
|
|
return int(f.read()) |
174
|
|
|
except (OSError, IOError): |
175
|
|
|
return None |
176
|
|
|
else: |
177
|
|
|
return None |
178
|
|
|
|
179
|
|
|
@property |
180
|
|
|
def processes_count(self): |
181
|
|
|
"""Get the current number of processes showed in the UI.""" |
182
|
|
|
return min(self._max_processes - 2, glances_processes.processcount['total'] - 1) |
|
|
|
|
183
|
|
|
|
184
|
|
|
@property |
185
|
|
|
def max_processes(self): |
186
|
|
|
"""Get the maximum number of processes showed in the UI.""" |
187
|
|
|
return self._max_processes |
188
|
|
|
|
189
|
|
|
@max_processes.setter |
190
|
|
|
def max_processes(self, value): |
191
|
|
|
"""Set the maximum number of processes showed in the UI.""" |
192
|
|
|
self._max_processes = value |
193
|
|
|
|
194
|
|
|
@property |
195
|
|
|
def process_filter_input(self): |
196
|
|
|
"""Get the process filter (given by the user).""" |
197
|
|
|
return self._filter.filter_input |
198
|
|
|
|
199
|
|
|
@property |
200
|
|
|
def process_filter(self): |
201
|
|
|
"""Get the process filter (current apply filter).""" |
202
|
|
|
return self._filter.filter |
203
|
|
|
|
204
|
|
|
@process_filter.setter |
205
|
|
|
def process_filter(self, value): |
206
|
|
|
"""Set the process filter.""" |
207
|
|
|
self._filter.filter = value |
208
|
|
|
|
209
|
|
|
@property |
210
|
|
|
def process_filter_key(self): |
211
|
|
|
"""Get the process filter key.""" |
212
|
|
|
return self._filter.filter_key |
213
|
|
|
|
214
|
|
|
@property |
215
|
|
|
def process_filter_re(self): |
216
|
|
|
"""Get the process regular expression compiled.""" |
217
|
|
|
return self._filter.filter_re |
218
|
|
|
|
219
|
|
|
def disable_kernel_threads(self): |
220
|
|
|
"""Ignore kernel threads in process list.""" |
221
|
|
|
self.no_kernel_threads = True |
222
|
|
|
|
223
|
|
|
@property |
224
|
|
|
def sort_reverse(self): |
225
|
|
|
"""Return True to sort processes in reverse 'key' order, False instead.""" |
|
|
|
|
226
|
|
|
if self.sort_key == 'name' or self.sort_key == 'username': |
227
|
|
|
return False |
228
|
|
|
|
229
|
|
|
return True |
230
|
|
|
|
231
|
|
|
def max_values(self): |
232
|
|
|
"""Return the max values dict.""" |
233
|
|
|
return self._max_values |
234
|
|
|
|
235
|
|
|
def get_max_values(self, key): |
236
|
|
|
"""Get the maximum values of the given stat (key).""" |
237
|
|
|
return self._max_values[key] |
238
|
|
|
|
239
|
|
|
def set_max_values(self, key, value): |
240
|
|
|
"""Set the maximum value for a specific stat (key).""" |
241
|
|
|
self._max_values[key] = value |
242
|
|
|
|
243
|
|
|
def reset_max_values(self): |
244
|
|
|
"""Reset the maximum values dict.""" |
245
|
|
|
self._max_values = {} |
246
|
|
|
for k in self._max_values_list: |
247
|
|
|
self._max_values[k] = 0.0 |
248
|
|
|
|
249
|
|
|
def update(self): |
|
|
|
|
250
|
|
|
"""Update the processes stats.""" |
251
|
|
|
# Reset the stats |
252
|
|
|
self.processlist = [] |
253
|
|
|
self.reset_processcount() |
254
|
|
|
|
255
|
|
|
# Do not process if disable tag is set |
256
|
|
|
if self.disable_tag: |
257
|
|
|
return |
258
|
|
|
|
259
|
|
|
# Time since last update (for disk_io rate computation) |
260
|
|
|
time_since_update = getTimeSinceLastUpdate('process_disk') |
261
|
|
|
|
262
|
|
|
# Grab standard stats |
263
|
|
|
##################### |
264
|
|
|
standard_attrs = ['cmdline', 'cpu_percent', 'cpu_times', 'memory_info', |
265
|
|
|
'memory_percent', 'name', 'nice', 'pid', 'ppid', |
266
|
|
|
'status', 'username', 'status', 'num_threads'] |
267
|
|
|
if not self.disable_io_counters: |
268
|
|
|
standard_attrs += ['io_counters'] |
269
|
|
|
if not self.disable_gids: |
270
|
|
|
standard_attrs += ['gids'] |
271
|
|
|
|
272
|
|
|
# and build the processes stats list (psutil>=5.3.0) |
273
|
|
|
self.processlist = [p.info for p in psutil.process_iter(attrs=standard_attrs, |
|
|
|
|
274
|
|
|
ad_value=None) |
275
|
|
|
# OS-related processes filter |
276
|
|
|
if not (BSD and p.info['name'] == 'idle') and |
277
|
|
|
not (WINDOWS and p.info['name'] == 'System Idle Process') and |
|
|
|
|
278
|
|
|
not (MACOS and p.info['name'] == 'kernel_task') and |
279
|
|
|
# Kernel threads filter |
280
|
|
|
not (self.no_kernel_threads and LINUX and p.info['gids'].real == 0) and |
|
|
|
|
281
|
|
|
# User filter |
282
|
|
|
not (self._filter.is_filtered(p.info))] |
|
|
|
|
283
|
|
|
|
284
|
|
|
# Sort the processes list by the current sort_key |
285
|
|
|
self.processlist = sort_stats(self.processlist, |
286
|
|
|
sortedby=self.sort_key, |
287
|
|
|
reverse=True) |
288
|
|
|
|
289
|
|
|
# Update the processcount |
290
|
|
|
self.update_processcount(self.processlist) |
291
|
|
|
|
292
|
|
|
# Loop over processes and add metadata |
293
|
|
|
first = True |
294
|
|
|
for proc in self.processlist: |
295
|
|
|
# Get extended stats, only for top processes (see issue #403). |
296
|
|
|
if first and not self.disable_extended_tag: |
297
|
|
|
# - cpu_affinity (Linux, Windows, FreeBSD) |
298
|
|
|
# - ionice (Linux and Windows > Vista) |
299
|
|
|
# - num_ctx_switches (not available on Illumos/Solaris) |
300
|
|
|
# - num_fds (Unix-like) |
301
|
|
|
# - num_handles (Windows) |
302
|
|
|
# - memory_maps (only swap, Linux) |
303
|
|
|
# https://www.cyberciti.biz/faq/linux-which-process-is-using-swap/ |
|
|
|
|
304
|
|
|
# - connections (TCP and UDP) |
305
|
|
|
extended = {} |
306
|
|
|
try: |
307
|
|
|
top_process = psutil.Process(proc['pid']) |
308
|
|
|
extended_stats = ['cpu_affinity', 'ionice', |
309
|
|
|
'num_ctx_switches'] |
310
|
|
|
if LINUX: |
311
|
|
|
# num_fds only avalable on Unix system (see issue #1351) |
312
|
|
|
extended_stats += ['num_fds'] |
313
|
|
|
if WINDOWS: |
314
|
|
|
extended_stats += ['num_handles'] |
315
|
|
|
|
316
|
|
|
# Get the extended stats |
317
|
|
|
extended = top_process.as_dict(attrs=extended_stats, |
318
|
|
|
ad_value=None) |
319
|
|
|
|
320
|
|
|
if LINUX: |
321
|
|
|
try: |
322
|
|
|
extended['memory_swap'] = sum([v.swap for v in top_process.memory_maps()]) |
|
|
|
|
323
|
|
|
except (psutil.NoSuchProcess, KeyError): |
324
|
|
|
# KeyError catch for issue #1551) |
325
|
|
|
pass |
326
|
|
|
except (psutil.AccessDenied, NotImplementedError): |
327
|
|
|
# NotImplementedError: /proc/${PID}/smaps file doesn't exist |
|
|
|
|
328
|
|
|
# on kernel < 2.6.14 or CONFIG_MMU kernel configuration option |
|
|
|
|
329
|
|
|
# is not enabled (see psutil #533/glances #413). |
330
|
|
|
extended['memory_swap'] = None |
331
|
|
|
try: |
332
|
|
|
extended['tcp'] = len(top_process.connections(kind="tcp")) |
|
|
|
|
333
|
|
|
extended['udp'] = len(top_process.connections(kind="udp")) |
|
|
|
|
334
|
|
|
except (psutil.AccessDenied, psutil.NoSuchProcess): |
335
|
|
|
# Manage issue1283 (psutil.AccessDenied) |
336
|
|
|
extended['tcp'] = None |
337
|
|
|
extended['udp'] = None |
338
|
|
|
except (psutil.NoSuchProcess, ValueError, AttributeError) as e: |
|
|
|
|
339
|
|
|
logger.error('Can not grab extended stats ({})'.format(e)) |
|
|
|
|
340
|
|
|
extended['extended_stats'] = False |
341
|
|
|
else: |
342
|
|
|
logger.debug('Grab extended stats for process {}'.format(proc['pid'])) |
|
|
|
|
343
|
|
|
extended['extended_stats'] = True |
344
|
|
|
proc.update(extended) |
345
|
|
|
first = False |
346
|
|
|
# /End of extended stats |
347
|
|
|
|
348
|
|
|
# Time since last update (for disk_io rate computation) |
349
|
|
|
proc['time_since_update'] = time_since_update |
350
|
|
|
|
351
|
|
|
# Process status (only keep the first char) |
352
|
|
|
proc['status'] = str(proc['status'])[:1].upper() |
353
|
|
|
|
354
|
|
|
# Process IO |
355
|
|
|
# procstat['io_counters'] is a list: |
356
|
|
|
# [read_bytes, write_bytes, read_bytes_old, write_bytes_old, io_tag] |
357
|
|
|
# If io_tag = 0 > Access denied or first time (display "?") |
358
|
|
|
# If io_tag = 1 > No access denied (display the IO rate) |
359
|
|
|
if 'io_counters' in proc and proc['io_counters'] is not None: |
360
|
|
|
io_new = [proc['io_counters'].read_bytes, |
361
|
|
|
proc['io_counters'].write_bytes] |
362
|
|
|
# For IO rate computation |
363
|
|
|
# Append saved IO r/w bytes |
364
|
|
|
try: |
365
|
|
|
proc['io_counters'] = io_new + self.io_old[proc['pid']] |
366
|
|
|
io_tag = 1 |
367
|
|
|
except KeyError: |
368
|
|
|
proc['io_counters'] = io_new + [0, 0] |
369
|
|
|
io_tag = 0 |
370
|
|
|
# then save the IO r/w bytes |
371
|
|
|
self.io_old[proc['pid']] = io_new |
372
|
|
|
else: |
373
|
|
|
proc['io_counters'] = [0, 0] + [0, 0] |
374
|
|
|
io_tag = 0 |
375
|
|
|
# Append the IO tag (for display) |
376
|
|
|
proc['io_counters'] += [io_tag] |
377
|
|
|
|
378
|
|
|
# Compute the maximum value for keys in self._max_values_list: CPU, MEM |
379
|
|
|
# Usefull to highlight the processes with maximum values |
380
|
|
|
for k in self._max_values_list: |
381
|
|
|
values_list = [i[k] for i in self.processlist if i[k] is not None] |
382
|
|
|
if values_list != []: |
383
|
|
|
self.set_max_values(k, max(values_list)) |
384
|
|
|
|
385
|
|
|
def getcount(self): |
386
|
|
|
"""Get the number of processes.""" |
387
|
|
|
return self.processcount |
388
|
|
|
|
389
|
|
|
def getlist(self, sortedby=None): |
|
|
|
|
390
|
|
|
"""Get the processlist.""" |
391
|
|
|
return self.processlist |
392
|
|
|
|
393
|
|
|
@property |
394
|
|
|
def sort_key(self): |
395
|
|
|
"""Get the current sort key.""" |
396
|
|
|
return self._sort_key |
397
|
|
|
|
398
|
|
|
def set_sort_key(self, key, auto=True): |
399
|
|
|
"""Set the current sort key.""" |
400
|
|
|
if key == 'auto': |
401
|
|
|
self.auto_sort = True |
402
|
|
|
self._sort_key = 'cpu_percent' |
403
|
|
|
else: |
404
|
|
|
self.auto_sort = auto |
405
|
|
|
self._sort_key = key |
406
|
|
|
|
|
|
|
|
407
|
|
|
def kill(self, pid, timeout=3): |
|
|
|
|
408
|
|
|
"""Kill process with pid""" |
409
|
|
|
assert pid != os.getpid(), "Glances can kill itself..." |
410
|
|
|
p = psutil.Process(pid) |
|
|
|
|
411
|
|
|
logger.debug('Send kill signal to process: {}'.format(p)) |
|
|
|
|
412
|
|
|
p.kill() |
413
|
|
|
return p.wait(timeout) |
414
|
|
|
|
415
|
|
|
|
416
|
|
|
def weighted(value): |
417
|
|
|
"""Manage None value in dict value.""" |
418
|
|
|
return -float('inf') if value is None else value |
419
|
|
|
|
420
|
|
|
|
421
|
|
|
def _sort_io_counters(process, |
422
|
|
|
sortedby='io_counters', |
423
|
|
|
sortedby_secondary='memory_percent'): |
|
|
|
|
424
|
|
|
"""Specific case for io_counters |
425
|
|
|
Sum of io_r + io_w""" |
426
|
|
|
return process[sortedby][0] - process[sortedby][2] + process[sortedby][1] - process[sortedby][3] |
|
|
|
|
427
|
|
|
|
428
|
|
|
|
429
|
|
|
def _sort_cpu_times(process, |
430
|
|
|
sortedby='cpu_times', |
431
|
|
|
sortedby_secondary='memory_percent'): |
|
|
|
|
432
|
|
|
""" Specific case for cpu_times |
433
|
|
|
Patch for "Sorting by process time works not as expected #1321" |
434
|
|
|
By default PsUtil only takes user time into account |
435
|
|
|
see (https://github.com/giampaolo/psutil/issues/1339) |
436
|
|
|
The following implementation takes user and system time into account""" |
437
|
|
|
return process[sortedby][0] + process[sortedby][1] |
438
|
|
|
|
439
|
|
|
|
440
|
|
|
def _sort_lambda(sortedby='cpu_percent', |
441
|
|
|
sortedby_secondary='memory_percent'): |
|
|
|
|
442
|
|
|
"""Return a sort lambda function for the sortedbykey""" |
443
|
|
|
ret = None |
444
|
|
|
if sortedby == 'io_counters': |
445
|
|
|
ret = _sort_io_counters |
446
|
|
|
elif sortedby == 'cpu_times': |
447
|
|
|
ret = _sort_cpu_times |
448
|
|
|
return ret |
449
|
|
|
|
450
|
|
|
|
451
|
|
|
def sort_stats(stats, |
452
|
|
|
sortedby='cpu_percent', |
453
|
|
|
sortedby_secondary='memory_percent', |
454
|
|
|
reverse=True): |
455
|
|
|
"""Return the stats (dict) sorted by (sortedby). |
456
|
|
|
|
457
|
|
|
Reverse the sort if reverse is True. |
458
|
|
|
""" |
459
|
|
|
if sortedby is None and sortedby_secondary is None: |
460
|
|
|
# No need to sort... |
461
|
|
|
return stats |
462
|
|
|
|
463
|
|
|
# Check if a specific sort should be done |
464
|
|
|
sort_lambda = _sort_lambda(sortedby=sortedby, |
465
|
|
|
sortedby_secondary=sortedby_secondary) |
466
|
|
|
|
467
|
|
|
if sort_lambda is not None: |
468
|
|
|
# Specific sort |
469
|
|
|
try: |
470
|
|
|
stats.sort(key=sort_lambda, reverse=reverse) |
471
|
|
|
except Exception: |
|
|
|
|
472
|
|
|
# If an error is detected, fallback to cpu_percent |
473
|
|
|
stats.sort(key=lambda process: (weighted(process['cpu_percent']), |
474
|
|
|
weighted(process[sortedby_secondary])), |
|
|
|
|
475
|
|
|
reverse=reverse) |
476
|
|
|
else: |
477
|
|
|
# Standard sort |
478
|
|
|
try: |
479
|
|
|
stats.sort(key=lambda process: (weighted(process[sortedby]), |
480
|
|
|
weighted(process[sortedby_secondary])), |
|
|
|
|
481
|
|
|
reverse=reverse) |
482
|
|
|
except (KeyError, TypeError): |
483
|
|
|
# Fallback to name |
484
|
|
|
stats.sort(key=lambda process: process['name'] if process['name'] is not None else '~', |
|
|
|
|
485
|
|
|
reverse=False) |
486
|
|
|
|
487
|
|
|
return stats |
488
|
|
|
|
489
|
|
|
|
490
|
|
|
glances_processes = GlancesProcesses() |
|
|
|
|
491
|
|
|
|
The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:
If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.