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