|
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
|
|
|
|