1
|
|
|
# -*- coding: utf-8 -*- |
2
|
|
|
# |
3
|
|
|
# This file is part of Glances. |
4
|
|
|
# |
5
|
|
|
# Copyright (C) 2017 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 |
24
|
|
|
from glances.globals import BSD, LINUX, MACOS, WINDOWS |
25
|
|
|
from glances.timer import Timer, getTimeSinceLastUpdate |
26
|
|
|
from glances.processes_tree import ProcessTreeNode |
27
|
|
|
from glances.filter import GlancesFilter |
28
|
|
|
from glances.logger import logger |
29
|
|
|
|
30
|
|
|
import psutil |
31
|
|
|
|
32
|
|
|
|
33
|
|
|
def is_kernel_thread(proc): |
34
|
|
|
"""Return True if proc is a kernel thread, False instead.""" |
35
|
|
|
try: |
36
|
|
|
return os.getpgid(proc.pid) == 0 |
37
|
|
|
# Python >= 3.3 raises ProcessLookupError, which inherits OSError |
38
|
|
|
except OSError: |
39
|
|
|
# return False is process is dead |
40
|
|
|
return False |
41
|
|
|
|
42
|
|
|
|
43
|
|
|
class GlancesProcesses(object): |
44
|
|
|
|
45
|
|
|
"""Get processed stats using the psutil library.""" |
46
|
|
|
|
47
|
|
|
def __init__(self, cache_timeout=60): |
48
|
|
|
"""Init the class to collect stats about processes.""" |
49
|
|
|
# Add internals caches because PSUtil do not cache all the stats |
50
|
|
|
# See: https://code.google.com/p/psutil/issues/detail?id=462 |
51
|
|
|
self.username_cache = {} |
52
|
|
|
self.cmdline_cache = {} |
53
|
|
|
|
54
|
|
|
# The internals caches will be cleaned each 'cache_timeout' seconds |
55
|
|
|
self.cache_timeout = cache_timeout |
56
|
|
|
self.cache_timer = Timer(self.cache_timeout) |
57
|
|
|
|
58
|
|
|
# Init the io dict |
59
|
|
|
# key = pid |
60
|
|
|
# value = [ read_bytes_old, write_bytes_old ] |
61
|
|
|
self.io_old = {} |
62
|
|
|
|
63
|
|
|
# Wether or not to enable process tree |
64
|
|
|
self._enable_tree = False |
65
|
|
|
self.process_tree = None |
66
|
|
|
|
67
|
|
|
# Init stats |
68
|
|
|
self.auto_sort = True |
69
|
|
|
self._sort_key = 'cpu_percent' |
70
|
|
|
self.allprocesslist = [] |
71
|
|
|
self.processlist = [] |
72
|
|
|
self.reset_processcount() |
73
|
|
|
|
74
|
|
|
# Tag to enable/disable the processes stats (to reduce the Glances CPU consumption) |
75
|
|
|
# Default is to enable the processes stats |
76
|
|
|
self.disable_tag = False |
77
|
|
|
|
78
|
|
|
# Extended stats for top process is enable by default |
79
|
|
|
self.disable_extended_tag = False |
80
|
|
|
|
81
|
|
|
# Maximum number of processes showed in the UI (None if no limit) |
82
|
|
|
self._max_processes = None |
83
|
|
|
|
84
|
|
|
# Process filter is a regular expression |
85
|
|
|
self._filter = GlancesFilter() |
86
|
|
|
|
87
|
|
|
# Whether or not to hide kernel threads |
88
|
|
|
self.no_kernel_threads = False |
89
|
|
|
|
90
|
|
|
# Store maximums values in a dict |
91
|
|
|
# Used in the UI to highlight the maximum value |
92
|
|
|
self._max_values_list = ('cpu_percent', 'memory_percent') |
93
|
|
|
# { 'cpu_percent': 0.0, 'memory_percent': 0.0 } |
94
|
|
|
self._max_values = {} |
95
|
|
|
self.reset_max_values() |
96
|
|
|
|
97
|
|
|
def reset_processcount(self): |
98
|
|
|
self.processcount = {'total': 0, |
99
|
|
|
'running': 0, |
100
|
|
|
'sleeping': 0, |
101
|
|
|
'thread': 0, |
102
|
|
|
'pid_max': None} |
103
|
|
|
|
104
|
|
|
def enable(self): |
105
|
|
|
"""Enable process stats.""" |
106
|
|
|
self.disable_tag = False |
107
|
|
|
self.update() |
108
|
|
|
|
109
|
|
|
def disable(self): |
110
|
|
|
"""Disable process stats.""" |
111
|
|
|
self.disable_tag = True |
112
|
|
|
|
113
|
|
|
def enable_extended(self): |
114
|
|
|
"""Enable extended process stats.""" |
115
|
|
|
self.disable_extended_tag = False |
116
|
|
|
self.update() |
117
|
|
|
|
118
|
|
|
def disable_extended(self): |
119
|
|
|
"""Disable extended process stats.""" |
120
|
|
|
self.disable_extended_tag = True |
121
|
|
|
|
122
|
|
|
@property |
123
|
|
|
def pid_max(self): |
124
|
|
|
""" |
125
|
|
|
Get the maximum PID value. |
126
|
|
|
|
127
|
|
|
On Linux, the value is read from the `/proc/sys/kernel/pid_max` file. |
128
|
|
|
|
129
|
|
|
From `man 5 proc`: |
130
|
|
|
The default value for this file, 32768, results in the same range of |
131
|
|
|
PIDs as on earlier kernels. On 32-bit platfroms, 32768 is the maximum |
132
|
|
|
value for pid_max. On 64-bit systems, pid_max can be set to any value |
133
|
|
|
up to 2^22 (PID_MAX_LIMIT, approximately 4 million). |
134
|
|
|
|
135
|
|
|
If the file is unreadable or not available for whatever reason, |
136
|
|
|
returns None. |
137
|
|
|
|
138
|
|
|
Some other OSes: |
139
|
|
|
- On FreeBSD and macOS the maximum is 99999. |
140
|
|
|
- On OpenBSD >= 6.0 the maximum is 99999 (was 32766). |
141
|
|
|
- On NetBSD the maximum is 30000. |
142
|
|
|
|
143
|
|
|
:returns: int or None |
144
|
|
|
""" |
145
|
|
|
if LINUX: |
146
|
|
|
# XXX: waiting for https://github.com/giampaolo/psutil/issues/720 |
147
|
|
|
try: |
148
|
|
|
with open('/proc/sys/kernel/pid_max', 'rb') as f: |
149
|
|
|
return int(f.read()) |
150
|
|
|
except (OSError, IOError): |
151
|
|
|
return None |
152
|
|
|
|
153
|
|
|
@property |
154
|
|
|
def max_processes(self): |
155
|
|
|
"""Get the maximum number of processes showed in the UI.""" |
156
|
|
|
return self._max_processes |
157
|
|
|
|
158
|
|
|
@max_processes.setter |
159
|
|
|
def max_processes(self, value): |
160
|
|
|
"""Set the maximum number of processes showed in the UI.""" |
161
|
|
|
self._max_processes = value |
162
|
|
|
|
163
|
|
|
@property |
164
|
|
|
def process_filter_input(self): |
165
|
|
|
"""Get the process filter (given by the user).""" |
166
|
|
|
return self._filter.filter_input |
167
|
|
|
|
168
|
|
|
@property |
169
|
|
|
def process_filter(self): |
170
|
|
|
"""Get the process filter (current apply filter).""" |
171
|
|
|
return self._filter.filter |
172
|
|
|
|
173
|
|
|
@process_filter.setter |
174
|
|
|
def process_filter(self, value): |
175
|
|
|
"""Set the process filter.""" |
176
|
|
|
self._filter.filter = value |
177
|
|
|
|
178
|
|
|
@property |
179
|
|
|
def process_filter_key(self): |
180
|
|
|
"""Get the process filter key.""" |
181
|
|
|
return self._filter.filter_key |
182
|
|
|
|
183
|
|
|
@property |
184
|
|
|
def process_filter_re(self): |
185
|
|
|
"""Get the process regular expression compiled.""" |
186
|
|
|
return self._filter.filter_re |
187
|
|
|
|
188
|
|
|
def disable_kernel_threads(self): |
189
|
|
|
"""Ignore kernel threads in process list.""" |
190
|
|
|
self.no_kernel_threads = True |
191
|
|
|
|
192
|
|
|
def enable_tree(self): |
193
|
|
|
"""Enable process tree.""" |
194
|
|
|
self._enable_tree = True |
195
|
|
|
|
196
|
|
|
def is_tree_enabled(self): |
197
|
|
|
"""Return True if process tree is enabled, False instead.""" |
198
|
|
|
return self._enable_tree |
199
|
|
|
|
200
|
|
|
@property |
201
|
|
|
def sort_reverse(self): |
202
|
|
|
"""Return True to sort processes in reverse 'key' order, False instead.""" |
203
|
|
|
if self.sort_key == 'name' or self.sort_key == 'username': |
204
|
|
|
return False |
205
|
|
|
|
206
|
|
|
return True |
207
|
|
|
|
208
|
|
|
def max_values(self): |
209
|
|
|
"""Return the max values dict.""" |
210
|
|
|
return self._max_values |
211
|
|
|
|
212
|
|
|
def get_max_values(self, key): |
213
|
|
|
"""Get the maximum values of the given stat (key).""" |
214
|
|
|
return self._max_values[key] |
215
|
|
|
|
216
|
|
|
def set_max_values(self, key, value): |
217
|
|
|
"""Set the maximum value for a specific stat (key).""" |
218
|
|
|
self._max_values[key] = value |
219
|
|
|
|
220
|
|
|
def reset_max_values(self): |
221
|
|
|
"""Reset the maximum values dict.""" |
222
|
|
|
self._max_values = {} |
223
|
|
|
for k in self._max_values_list: |
224
|
|
|
self._max_values[k] = 0.0 |
225
|
|
|
|
226
|
|
|
def __get_mandatory_stats(self, proc, procstat): |
227
|
|
|
""" |
228
|
|
|
Get mandatory_stats: for all processes. |
229
|
|
|
Needed for the sorting/filter step. |
230
|
|
|
|
231
|
|
|
Stats grabbed inside this method: |
232
|
|
|
* 'name', 'cpu_times', 'status', 'ppid' |
233
|
|
|
* 'username', 'cpu_percent', 'memory_percent' |
234
|
|
|
""" |
235
|
|
|
procstat['mandatory_stats'] = True |
236
|
|
|
|
237
|
|
|
# Name, cpu_times, status and ppid stats are in the same /proc file |
238
|
|
|
# Optimisation fir issue #958 |
239
|
|
|
try: |
240
|
|
|
procstat.update(proc.as_dict( |
241
|
|
|
attrs=['name', 'cpu_times', 'status', 'ppid'], |
242
|
|
|
ad_value='')) |
243
|
|
|
except psutil.NoSuchProcess: |
244
|
|
|
# Try/catch for issue #432 (process no longer exist) |
245
|
|
|
return None |
246
|
|
|
else: |
247
|
|
|
procstat['status'] = str(procstat['status'])[:1].upper() |
248
|
|
|
|
249
|
|
|
try: |
250
|
|
|
procstat.update(proc.as_dict( |
251
|
|
|
attrs=['username', 'cpu_percent', 'memory_percent'], |
252
|
|
|
ad_value='')) |
253
|
|
|
except psutil.NoSuchProcess: |
254
|
|
|
# Try/catch for issue #432 (process no longer exist) |
255
|
|
|
return None |
256
|
|
|
|
257
|
|
|
if procstat['cpu_percent'] == '' or procstat['memory_percent'] == '': |
258
|
|
|
# Do not display process if we cannot get the basic |
259
|
|
|
# cpu_percent or memory_percent stats |
260
|
|
|
return None |
261
|
|
|
|
262
|
|
|
# Compute the maximum value for cpu_percent and memory_percent |
263
|
|
|
for k in self._max_values_list: |
264
|
|
|
if procstat[k] > self.get_max_values(k): |
265
|
|
|
self.set_max_values(k, procstat[k]) |
266
|
|
|
|
267
|
|
|
# Process command line (cached with internal cache) |
268
|
|
|
if procstat['pid'] not in self.cmdline_cache: |
269
|
|
|
# Patch for issue #391 |
270
|
|
|
try: |
271
|
|
|
self.cmdline_cache[procstat['pid']] = proc.cmdline() |
272
|
|
|
except (AttributeError, UnicodeDecodeError, psutil.AccessDenied, psutil.NoSuchProcess): |
273
|
|
|
self.cmdline_cache[procstat['pid']] = "" |
274
|
|
|
procstat['cmdline'] = self.cmdline_cache[procstat['pid']] |
275
|
|
|
|
276
|
|
|
# Process IO |
277
|
|
|
# procstat['io_counters'] is a list: |
278
|
|
|
# [read_bytes, write_bytes, read_bytes_old, write_bytes_old, io_tag] |
279
|
|
|
# If io_tag = 0 > Access denied (display "?") |
280
|
|
|
# If io_tag = 1 > No access denied (display the IO rate) |
281
|
|
|
# Availability: all platforms except macOS and Illumos/Solaris |
282
|
|
|
try: |
283
|
|
|
# Get the process IO counters |
284
|
|
|
proc_io = proc.io_counters() |
285
|
|
|
io_new = [proc_io.read_bytes, proc_io.write_bytes] |
286
|
|
|
except (psutil.AccessDenied, psutil.NoSuchProcess, NotImplementedError): |
287
|
|
|
# Access denied to process IO (no root account) |
288
|
|
|
# NoSuchProcess (process die between first and second grab) |
289
|
|
|
# Put 0 in all values (for sort) and io_tag = 0 (for display) |
290
|
|
|
procstat['io_counters'] = [0, 0] + [0, 0] |
291
|
|
|
io_tag = 0 |
292
|
|
|
except AttributeError: |
293
|
|
|
return procstat |
294
|
|
|
else: |
295
|
|
|
# For IO rate computation |
296
|
|
|
# Append saved IO r/w bytes |
297
|
|
|
try: |
298
|
|
|
procstat['io_counters'] = io_new + self.io_old[procstat['pid']] |
299
|
|
|
except KeyError: |
300
|
|
|
procstat['io_counters'] = io_new + [0, 0] |
301
|
|
|
# then save the IO r/w bytes |
302
|
|
|
self.io_old[procstat['pid']] = io_new |
303
|
|
|
io_tag = 1 |
304
|
|
|
|
305
|
|
|
# Append the IO tag (for display) |
306
|
|
|
procstat['io_counters'] += [io_tag] |
307
|
|
|
|
308
|
|
|
return procstat |
309
|
|
|
|
310
|
|
|
def __get_standard_stats(self, proc, procstat): |
311
|
|
|
""" |
312
|
|
|
Get standard_stats: only for displayed processes. |
313
|
|
|
|
314
|
|
|
Stats grabbed inside this method: |
315
|
|
|
* nice and memory_info |
316
|
|
|
""" |
317
|
|
|
procstat['standard_stats'] = True |
318
|
|
|
|
319
|
|
|
# Process nice and memory_info (issue #926) |
320
|
|
|
try: |
321
|
|
|
procstat.update( |
322
|
|
|
proc.as_dict(attrs=['nice', 'memory_info'])) |
323
|
|
|
except psutil.NoSuchProcess: |
324
|
|
|
pass |
325
|
|
|
|
326
|
|
|
return procstat |
327
|
|
|
|
328
|
|
|
def __get_extended_stats(self, proc, procstat): |
329
|
|
|
""" |
330
|
|
|
Get extended stats, only for top processes (see issue #403). |
331
|
|
|
|
332
|
|
|
- cpu_affinity (Linux, Windows, FreeBSD) |
333
|
|
|
- ionice (Linux and Windows > Vista) |
334
|
|
|
- memory_full_info (Linux) |
335
|
|
|
- num_ctx_switches (not available on Illumos/Solaris) |
336
|
|
|
- num_fds (Unix-like) |
337
|
|
|
- num_handles (Windows) |
338
|
|
|
- num_threads (not available on *BSD) |
339
|
|
|
- memory_maps (only swap, Linux) |
340
|
|
|
https://www.cyberciti.biz/faq/linux-which-process-is-using-swap/ |
341
|
|
|
- connections (TCP and UDP) |
342
|
|
|
""" |
343
|
|
|
procstat['extended_stats'] = True |
344
|
|
|
|
345
|
|
|
for stat in ['cpu_affinity', 'ionice', 'memory_full_info', |
346
|
|
|
'num_ctx_switches', 'num_fds', 'num_handles', |
347
|
|
|
'num_threads']: |
348
|
|
|
try: |
349
|
|
|
procstat.update(proc.as_dict(attrs=[stat])) |
350
|
|
|
except psutil.NoSuchProcess: |
351
|
|
|
pass |
352
|
|
|
# XXX: psutil>=4.3.1 raises ValueError while <4.3.1 raises AttributeError |
353
|
|
|
except (ValueError, AttributeError): |
354
|
|
|
procstat[stat] = None |
355
|
|
|
|
356
|
|
|
if LINUX: |
357
|
|
|
try: |
358
|
|
|
procstat['memory_swap'] = sum([v.swap for v in proc.memory_maps()]) |
359
|
|
|
except psutil.NoSuchProcess: |
360
|
|
|
pass |
361
|
|
|
except (psutil.AccessDenied, TypeError, NotImplementedError): |
362
|
|
|
# NotImplementedError: /proc/${PID}/smaps file doesn't exist |
363
|
|
|
# on kernel < 2.6.14 or CONFIG_MMU kernel configuration option |
364
|
|
|
# is not enabled (see psutil #533/glances #413). |
365
|
|
|
# XXX: Remove TypeError once we'll drop psutil < 3.0.0. |
366
|
|
|
procstat['memory_swap'] = None |
367
|
|
|
|
368
|
|
|
try: |
369
|
|
|
procstat['tcp'] = len(proc.connections(kind="tcp")) |
370
|
|
|
procstat['udp'] = len(proc.connections(kind="udp")) |
371
|
|
|
except psutil.AccessDenied: |
372
|
|
|
procstat['tcp'] = None |
373
|
|
|
procstat['udp'] = None |
374
|
|
|
|
375
|
|
|
return procstat |
376
|
|
|
|
377
|
|
|
def __get_process_stats(self, proc, |
378
|
|
|
mandatory_stats=True, |
379
|
|
|
standard_stats=True, |
380
|
|
|
extended_stats=False): |
381
|
|
|
"""Get stats of a running processes.""" |
382
|
|
|
# Process ID (always) |
383
|
|
|
procstat = proc.as_dict(attrs=['pid']) |
384
|
|
|
|
385
|
|
|
if mandatory_stats: |
386
|
|
|
procstat = self.__get_mandatory_stats(proc, procstat) |
387
|
|
|
|
388
|
|
|
if procstat is not None and standard_stats: |
389
|
|
|
procstat = self.__get_standard_stats(proc, procstat) |
390
|
|
|
|
391
|
|
|
if procstat is not None and extended_stats and not self.disable_extended_tag: |
392
|
|
|
procstat = self.__get_extended_stats(proc, procstat) |
393
|
|
|
|
394
|
|
|
return procstat |
395
|
|
|
|
396
|
|
|
def update(self): |
397
|
|
|
"""Update the processes stats.""" |
398
|
|
|
# Reset the stats |
399
|
|
|
self.processlist = [] |
400
|
|
|
self.reset_processcount() |
401
|
|
|
|
402
|
|
|
# Do not process if disable tag is set |
403
|
|
|
if self.disable_tag: |
404
|
|
|
return |
405
|
|
|
|
406
|
|
|
# Get the time since last update |
407
|
|
|
time_since_update = getTimeSinceLastUpdate('process_disk') |
408
|
|
|
|
409
|
|
|
# Reset the max dict |
410
|
|
|
self.reset_max_values() |
411
|
|
|
|
412
|
|
|
# Update the maximum process ID (pid) number |
413
|
|
|
self.processcount['pid_max'] = self.pid_max |
414
|
|
|
|
415
|
|
|
# Build an internal dict with only mandatories stats (sort keys) |
416
|
|
|
processdict = {} |
417
|
|
|
excluded_processes = set() |
418
|
|
|
for proc in psutil.process_iter(): |
419
|
|
|
# Ignore kernel threads if needed |
420
|
|
|
if self.no_kernel_threads and not WINDOWS and is_kernel_thread(proc): |
421
|
|
|
continue |
422
|
|
|
|
423
|
|
|
# If self.max_processes is None: Only retrieve mandatory stats |
424
|
|
|
# Else: retrieve mandatory and standard stats |
425
|
|
|
s = self.__get_process_stats(proc, |
426
|
|
|
mandatory_stats=True, |
427
|
|
|
standard_stats=self.max_processes is None) |
428
|
|
|
# Check if s is note None (issue #879) |
429
|
|
|
# ignore the 'idle' process on Windows and *BSD |
430
|
|
|
# ignore the 'kernel_task' process on macOS |
431
|
|
|
# waiting for upstream patch from psutil |
432
|
|
|
if (s is None or |
433
|
|
|
BSD and s['name'] == 'idle' or |
434
|
|
|
WINDOWS and s['name'] == 'System Idle Process' or |
435
|
|
|
MACOS and s['name'] == 'kernel_task'): |
436
|
|
|
continue |
437
|
|
|
# Continue to the next process if it has to be filtered |
438
|
|
|
if self._filter.is_filtered(s): |
439
|
|
|
excluded_processes.add(proc) |
440
|
|
|
continue |
441
|
|
|
|
442
|
|
|
# Ok add the process to the list |
443
|
|
|
processdict[proc] = s |
444
|
|
|
# Update processcount (global statistics) |
445
|
|
|
try: |
446
|
|
|
self.processcount[str(proc.status())] += 1 |
447
|
|
|
except KeyError: |
448
|
|
|
# Key did not exist, create it |
449
|
|
|
try: |
450
|
|
|
self.processcount[str(proc.status())] = 1 |
451
|
|
|
except psutil.NoSuchProcess: |
452
|
|
|
pass |
453
|
|
|
except psutil.NoSuchProcess: |
454
|
|
|
pass |
455
|
|
|
else: |
456
|
|
|
self.processcount['total'] += 1 |
457
|
|
|
# Update thread number (global statistics) |
458
|
|
|
try: |
459
|
|
|
self.processcount['thread'] += proc.num_threads() |
460
|
|
|
except Exception: |
461
|
|
|
pass |
462
|
|
|
|
463
|
|
|
if self._enable_tree: |
464
|
|
|
self.process_tree = ProcessTreeNode.build_tree(processdict, |
465
|
|
|
self.sort_key, |
466
|
|
|
self.sort_reverse, |
467
|
|
|
self.no_kernel_threads, |
468
|
|
|
excluded_processes) |
469
|
|
|
|
470
|
|
|
for i, node in enumerate(self.process_tree): |
471
|
|
|
# Only retreive stats for visible processes (max_processes) |
472
|
|
|
if self.max_processes is not None and i >= self.max_processes: |
473
|
|
|
break |
474
|
|
|
|
475
|
|
|
# add standard stats |
476
|
|
|
new_stats = self.__get_process_stats(node.process, |
477
|
|
|
mandatory_stats=False, |
478
|
|
|
standard_stats=True, |
479
|
|
|
extended_stats=False) |
480
|
|
|
if new_stats is not None: |
481
|
|
|
node.stats.update(new_stats) |
482
|
|
|
|
483
|
|
|
# Add a specific time_since_update stats for bitrate |
484
|
|
|
node.stats['time_since_update'] = time_since_update |
485
|
|
|
|
486
|
|
|
else: |
487
|
|
|
# Process optimization |
488
|
|
|
# Only retreive stats for visible processes (max_processes) |
489
|
|
|
if self.max_processes is not None: |
490
|
|
|
# Sort the internal dict and cut the top N (Return a list of tuple) |
491
|
|
|
# tuple=key (proc), dict (returned by __get_process_stats) |
492
|
|
|
try: |
493
|
|
|
processiter = sorted(iteritems(processdict), |
494
|
|
|
key=lambda x: x[1][self.sort_key], |
495
|
|
|
reverse=self.sort_reverse) |
496
|
|
|
except (KeyError, TypeError) as e: |
497
|
|
|
logger.error("Cannot sort process list by {}: {}".format(self.sort_key, e)) |
498
|
|
|
logger.error('{}'.format(listitems(processdict)[0])) |
499
|
|
|
# Fallback to all process (issue #423) |
500
|
|
|
processloop = iteritems(processdict) |
501
|
|
|
first = False |
502
|
|
|
else: |
503
|
|
|
processloop = processiter[0:self.max_processes] |
504
|
|
|
first = True |
505
|
|
|
else: |
506
|
|
|
# Get all processes stats |
507
|
|
|
processloop = iteritems(processdict) |
508
|
|
|
first = False |
509
|
|
|
|
510
|
|
|
for i in processloop: |
511
|
|
|
# Already existing mandatory stats |
512
|
|
|
procstat = i[1] |
513
|
|
|
if self.max_processes is not None: |
514
|
|
|
# Update with standard stats |
515
|
|
|
# and extended stats but only for TOP (first) process |
516
|
|
|
s = self.__get_process_stats(i[0], |
517
|
|
|
mandatory_stats=False, |
518
|
|
|
standard_stats=True, |
519
|
|
|
extended_stats=first) |
520
|
|
|
if s is None: |
521
|
|
|
continue |
522
|
|
|
procstat.update(s) |
523
|
|
|
# Add a specific time_since_update stats for bitrate |
524
|
|
|
procstat['time_since_update'] = time_since_update |
525
|
|
|
# Update process list |
526
|
|
|
self.processlist.append(procstat) |
527
|
|
|
# Next... |
528
|
|
|
first = False |
529
|
|
|
|
530
|
|
|
# Build the all processes list used by the AMPs |
531
|
|
|
self.allprocesslist = [p for p in itervalues(processdict)] |
532
|
|
|
|
533
|
|
|
# Clean internals caches if timeout is reached |
534
|
|
|
if self.cache_timer.finished(): |
535
|
|
|
self.username_cache = {} |
536
|
|
|
self.cmdline_cache = {} |
537
|
|
|
# Restart the timer |
538
|
|
|
self.cache_timer.reset() |
539
|
|
|
|
540
|
|
|
def getcount(self): |
541
|
|
|
"""Get the number of processes.""" |
542
|
|
|
return self.processcount |
543
|
|
|
|
544
|
|
|
def getalllist(self): |
545
|
|
|
"""Get the allprocesslist.""" |
546
|
|
|
return self.allprocesslist |
547
|
|
|
|
548
|
|
|
def getlist(self, sortedby=None): |
549
|
|
|
"""Get the processlist.""" |
550
|
|
|
return self.processlist |
551
|
|
|
|
552
|
|
|
def gettree(self): |
553
|
|
|
"""Get the process tree.""" |
554
|
|
|
return self.process_tree |
555
|
|
|
|
556
|
|
|
@property |
557
|
|
|
def sort_key(self): |
558
|
|
|
"""Get the current sort key.""" |
559
|
|
|
return self._sort_key |
560
|
|
|
|
561
|
|
|
@sort_key.setter |
562
|
|
|
def sort_key(self, key): |
563
|
|
|
"""Set the current sort key.""" |
564
|
|
|
self._sort_key = key |
565
|
|
|
|
566
|
|
|
|
567
|
|
|
# TODO: move this global function (also used in glances_processlist |
568
|
|
|
# and logs) inside the GlancesProcesses class |
569
|
|
|
def sort_stats(stats, sortedby=None, tree=False, reverse=True): |
570
|
|
|
"""Return the stats (dict) sorted by (sortedby) |
571
|
|
|
Reverse the sort if reverse is True.""" |
572
|
|
|
if sortedby is None: |
573
|
|
|
# No need to sort... |
574
|
|
|
return stats |
575
|
|
|
|
576
|
|
|
if sortedby == 'io_counters' and not tree: |
577
|
|
|
# Specific case for io_counters |
578
|
|
|
# Sum of io_r + io_w |
579
|
|
|
try: |
580
|
|
|
# Sort process by IO rate (sum IO read + IO write) |
581
|
|
|
stats.sort(key=lambda process: process[sortedby][0] - |
582
|
|
|
process[sortedby][2] + process[sortedby][1] - |
583
|
|
|
process[sortedby][3], |
584
|
|
|
reverse=reverse) |
585
|
|
|
except Exception: |
586
|
|
|
stats.sort(key=operator.itemgetter('cpu_percent'), |
587
|
|
|
reverse=reverse) |
588
|
|
|
else: |
589
|
|
|
# Others sorts |
590
|
|
|
if tree: |
591
|
|
|
stats.set_sorting(sortedby, reverse) |
592
|
|
|
else: |
593
|
|
|
try: |
594
|
|
|
stats.sort(key=operator.itemgetter(sortedby), |
595
|
|
|
reverse=reverse) |
596
|
|
|
except (KeyError, TypeError): |
597
|
|
|
stats.sort(key=operator.itemgetter('name'), |
598
|
|
|
reverse=False) |
599
|
|
|
|
600
|
|
|
return stats |
601
|
|
|
|
602
|
|
|
|
603
|
|
|
glances_processes = GlancesProcesses() |
604
|
|
|
|