1 | # -*- coding: utf-8 -*- |
||
2 | # |
||
3 | # This file is part of Glances. |
||
4 | # |
||
5 | # SPDX-FileCopyrightText: 2023 Nicolas Hennion <[email protected]> |
||
6 | # |
||
7 | # SPDX-License-Identifier: LGPL-3.0-only |
||
8 | # |
||
9 | |||
10 | import os |
||
11 | |||
12 | from glances.compat import iterkeys |
||
13 | from glances.globals import BSD, LINUX, MACOS, WINDOWS |
||
14 | from glances.timer import Timer, getTimeSinceLastUpdate |
||
15 | from glances.filter import GlancesFilter |
||
16 | from glances.programs import processes_to_programs |
||
17 | from glances.logger import logger |
||
18 | |||
19 | import psutil |
||
20 | |||
21 | # This constant defines the list of available processes sort key |
||
22 | sort_processes_key_list = ['cpu_percent', 'memory_percent', 'username', 'cpu_times', 'io_counters', 'name'] |
||
23 | |||
24 | # Sort dictionary for human |
||
25 | sort_for_human = { |
||
26 | 'io_counters': 'disk IO', |
||
27 | 'cpu_percent': 'CPU consumption', |
||
28 | 'memory_percent': 'memory consumption', |
||
29 | 'cpu_times': 'process time', |
||
30 | 'username': 'user name', |
||
31 | 'name': 'processs name', |
||
32 | None: 'None', |
||
33 | } |
||
34 | |||
35 | |||
36 | class GlancesProcesses(object): |
||
37 | """Get processed stats using the psutil library.""" |
||
38 | |||
39 | def __init__(self, cache_timeout=60): |
||
40 | """Init the class to collect stats about processes.""" |
||
41 | # Init the args, coming from the GlancesStandalone class |
||
42 | # Should be set by the set_args method |
||
43 | self.args = None |
||
44 | |||
45 | # Add internals caches because psutil do not cache all the stats |
||
46 | # See: https://github.com/giampaolo/psutil/issues/462 |
||
47 | self.username_cache = {} |
||
48 | self.cmdline_cache = {} |
||
49 | |||
50 | # The internals caches will be cleaned each 'cache_timeout' seconds |
||
51 | self.cache_timeout = cache_timeout |
||
52 | # First iteration, no cache |
||
53 | self.cache_timer = Timer(0) |
||
54 | |||
55 | # Init the io_old dict used to compute the IO bitrate |
||
56 | # key = pid |
||
57 | # value = [ read_bytes_old, write_bytes_old ] |
||
58 | self.io_old = {} |
||
59 | |||
60 | # Init stats |
||
61 | self.auto_sort = None |
||
62 | self._sort_key = None |
||
63 | # Default processes sort key is 'auto' |
||
64 | # Can be overwrite from the configuration file (issue#1536) => See glances_processlist.py init |
||
65 | self.set_sort_key('auto', auto=True) |
||
66 | self.processlist = [] |
||
67 | self.reset_processcount() |
||
68 | |||
69 | # Cache is a dict with key=pid and value = dict of cached value |
||
70 | self.processlist_cache = {} |
||
71 | |||
72 | # Tag to enable/disable the processes stats (to reduce the Glances CPU consumption) |
||
73 | # Default is to enable the processes stats |
||
74 | self.disable_tag = False |
||
75 | |||
76 | # Extended stats for top process is enable by default |
||
77 | self.disable_extended_tag = False |
||
78 | self.extended_process = None |
||
79 | |||
80 | # Test if the system can grab io_counters |
||
81 | try: |
||
82 | p = psutil.Process() |
||
83 | p.io_counters() |
||
84 | except Exception as e: |
||
85 | logger.warning('PsUtil can not grab processes io_counters ({})'.format(e)) |
||
86 | self.disable_io_counters = True |
||
87 | else: |
||
88 | logger.debug('PsUtil can grab processes io_counters') |
||
89 | self.disable_io_counters = False |
||
90 | |||
91 | # Test if the system can grab gids |
||
92 | try: |
||
93 | p = psutil.Process() |
||
94 | p.gids() |
||
95 | except Exception as e: |
||
96 | logger.warning('PsUtil can not grab processes gids ({})'.format(e)) |
||
97 | self.disable_gids = True |
||
98 | else: |
||
99 | logger.debug('PsUtil can grab processes gids') |
||
100 | self.disable_gids = False |
||
101 | |||
102 | # Maximum number of processes showed in the UI (None if no limit) |
||
103 | self._max_processes = None |
||
104 | |||
105 | # Process filter is a regular expression |
||
106 | self._filter = GlancesFilter() |
||
107 | |||
108 | # Whether or not to hide kernel threads |
||
109 | self.no_kernel_threads = False |
||
110 | |||
111 | # Store maximums values in a dict |
||
112 | # Used in the UI to highlight the maximum value |
||
113 | self._max_values_list = ('cpu_percent', 'memory_percent') |
||
114 | # { 'cpu_percent': 0.0, 'memory_percent': 0.0 } |
||
115 | self._max_values = {} |
||
116 | self.reset_max_values() |
||
117 | |||
118 | def set_args(self, args): |
||
119 | """Set args.""" |
||
120 | self.args = args |
||
121 | |||
122 | def reset_processcount(self): |
||
123 | """Reset the global process count""" |
||
124 | self.processcount = {'total': 0, 'running': 0, 'sleeping': 0, 'thread': 0, 'pid_max': None} |
||
125 | |||
126 | def update_processcount(self, plist): |
||
127 | """Update the global process count from the current processes list""" |
||
128 | # Update the maximum process ID (pid) number |
||
129 | self.processcount['pid_max'] = self.pid_max |
||
130 | # For each key in the processcount dict |
||
131 | # count the number of processes with the same status |
||
132 | for k in iterkeys(self.processcount): |
||
133 | self.processcount[k] = len(list(filter(lambda v: v['status'] is k, plist))) |
||
0 ignored issues
–
show
introduced
by
Loading history...
|
|||
134 | # Compute thread |
||
135 | self.processcount['thread'] = sum(i['num_threads'] for i in plist if i['num_threads'] is not None) |
||
136 | # Compute total |
||
137 | self.processcount['total'] = len(plist) |
||
138 | |||
139 | def enable(self): |
||
140 | """Enable process stats.""" |
||
141 | self.disable_tag = False |
||
142 | self.update() |
||
143 | |||
144 | def disable(self): |
||
145 | """Disable process stats.""" |
||
146 | self.disable_tag = True |
||
147 | |||
148 | def enable_extended(self): |
||
149 | """Enable extended process stats.""" |
||
150 | self.disable_extended_tag = False |
||
151 | self.update() |
||
152 | |||
153 | def disable_extended(self): |
||
154 | """Disable extended process stats.""" |
||
155 | self.disable_extended_tag = True |
||
156 | |||
157 | @property |
||
158 | def pid_max(self): |
||
159 | """ |
||
160 | Get the maximum PID value. |
||
161 | |||
162 | On Linux, the value is read from the `/proc/sys/kernel/pid_max` file. |
||
163 | |||
164 | From `man 5 proc`: |
||
165 | The default value for this file, 32768, results in the same range of |
||
166 | PIDs as on earlier kernels. On 32-bit platforms, 32768 is the maximum |
||
167 | value for pid_max. On 64-bit systems, pid_max can be set to any value |
||
168 | up to 2^22 (PID_MAX_LIMIT, approximately 4 million). |
||
169 | |||
170 | If the file is unreadable or not available for whatever reason, |
||
171 | returns None. |
||
172 | |||
173 | Some other OSes: |
||
174 | - On FreeBSD and macOS the maximum is 99999. |
||
175 | - On OpenBSD >= 6.0 the maximum is 99999 (was 32766). |
||
176 | - On NetBSD the maximum is 30000. |
||
177 | |||
178 | :returns: int or None |
||
179 | """ |
||
180 | if LINUX: |
||
181 | # XXX: waiting for https://github.com/giampaolo/psutil/issues/720 |
||
182 | try: |
||
183 | with open('/proc/sys/kernel/pid_max', 'rb') as f: |
||
184 | return int(f.read()) |
||
185 | except (OSError, IOError): |
||
186 | return None |
||
187 | else: |
||
188 | return None |
||
189 | |||
190 | @property |
||
191 | def processes_count(self): |
||
192 | """Get the current number of processes showed in the UI.""" |
||
193 | return min(self._max_processes - 2, glances_processes.processcount['total'] - 1) |
||
194 | |||
195 | @property |
||
196 | def max_processes(self): |
||
197 | """Get the maximum number of processes showed in the UI.""" |
||
198 | return self._max_processes |
||
199 | |||
200 | @max_processes.setter |
||
201 | def max_processes(self, value): |
||
202 | """Set the maximum number of processes showed in the UI.""" |
||
203 | self._max_processes = value |
||
204 | |||
205 | @property |
||
206 | def process_filter_input(self): |
||
207 | """Get the process filter (given by the user).""" |
||
208 | return self._filter.filter_input |
||
209 | |||
210 | @property |
||
211 | def process_filter(self): |
||
212 | """Get the process filter (current apply filter).""" |
||
213 | return self._filter.filter |
||
214 | |||
215 | @process_filter.setter |
||
216 | def process_filter(self, value): |
||
217 | """Set the process filter.""" |
||
218 | self._filter.filter = value |
||
219 | |||
220 | @property |
||
221 | def process_filter_key(self): |
||
222 | """Get the process filter key.""" |
||
223 | return self._filter.filter_key |
||
224 | |||
225 | @property |
||
226 | def process_filter_re(self): |
||
227 | """Get the process regular expression compiled.""" |
||
228 | return self._filter.filter_re |
||
229 | |||
230 | def disable_kernel_threads(self): |
||
231 | """Ignore kernel threads in process list.""" |
||
232 | self.no_kernel_threads = True |
||
233 | |||
234 | @property |
||
235 | def sort_reverse(self): |
||
236 | """Return True to sort processes in reverse 'key' order, False instead.""" |
||
237 | if self.sort_key == 'name' or self.sort_key == 'username': |
||
238 | return False |
||
239 | |||
240 | return True |
||
241 | |||
242 | def max_values(self): |
||
243 | """Return the max values dict.""" |
||
244 | return self._max_values |
||
245 | |||
246 | def get_max_values(self, key): |
||
247 | """Get the maximum values of the given stat (key).""" |
||
248 | return self._max_values[key] |
||
249 | |||
250 | def set_max_values(self, key, value): |
||
251 | """Set the maximum value for a specific stat (key).""" |
||
252 | self._max_values[key] = value |
||
253 | |||
254 | def reset_max_values(self): |
||
255 | """Reset the maximum values dict.""" |
||
256 | self._max_values = {} |
||
257 | for k in self._max_values_list: |
||
258 | self._max_values[k] = 0.0 |
||
259 | |||
260 | def get_extended_stats(self, proc): |
||
261 | """Get the extended stats for the given PID.""" |
||
262 | # - cpu_affinity (Linux, Windows, FreeBSD) |
||
263 | # - ionice (Linux and Windows > Vista) |
||
264 | # - num_ctx_switches (not available on Illumos/Solaris) |
||
265 | # - num_fds (Unix-like) |
||
266 | # - num_handles (Windows) |
||
267 | # - memory_maps (only swap, Linux) |
||
268 | # https://www.cyberciti.biz/faq/linux-which-process-is-using-swap/ |
||
269 | # - connections (TCP and UDP) |
||
270 | # - CPU min/max/mean |
||
271 | |||
272 | # Set the extended stats list (OS dependant) |
||
273 | extended_stats = ['cpu_affinity', 'ionice', 'num_ctx_switches'] |
||
274 | if LINUX: |
||
275 | # num_fds only available on Unix system (see issue #1351) |
||
276 | extended_stats += ['num_fds'] |
||
277 | if WINDOWS: |
||
278 | extended_stats += ['num_handles'] |
||
279 | |||
280 | ret = {} |
||
281 | try: |
||
282 | # Get the extended stats |
||
283 | selected_process = psutil.Process(proc['pid']) |
||
284 | ret = selected_process.as_dict(attrs=extended_stats, ad_value=None) |
||
285 | |||
286 | if LINUX: |
||
287 | try: |
||
288 | ret['memory_swap'] = sum([v.swap for v in selected_process.memory_maps()]) |
||
289 | except (psutil.NoSuchProcess, KeyError): |
||
290 | # (KeyError catch for issue #1551) |
||
291 | pass |
||
292 | except (psutil.AccessDenied, NotImplementedError): |
||
293 | # NotImplementedError: /proc/${PID}/smaps file doesn't exist |
||
294 | # on kernel < 2.6.14 or CONFIG_MMU kernel configuration option |
||
295 | # is not enabled (see psutil #533/glances #413). |
||
296 | ret['memory_swap'] = None |
||
297 | try: |
||
298 | ret['tcp'] = len(selected_process.connections(kind="tcp")) |
||
299 | ret['udp'] = len(selected_process.connections(kind="udp")) |
||
300 | except (psutil.AccessDenied, psutil.NoSuchProcess): |
||
301 | # Manage issue1283 (psutil.AccessDenied) |
||
302 | ret['tcp'] = None |
||
303 | ret['udp'] = None |
||
304 | except (psutil.NoSuchProcess, ValueError, AttributeError) as e: |
||
305 | logger.error('Can not grab extended stats ({})'.format(e)) |
||
306 | self.extended_process = None |
||
307 | ret['extended_stats'] = False |
||
308 | else: |
||
309 | logger.debug('Grab extended stats for process {}'.format(proc['pid'])) |
||
310 | |||
311 | # Compute CPU and MEM min/max/mean |
||
312 | for stat_prefix in ['cpu', 'memory']: |
||
313 | if stat_prefix + '_min' not in self.extended_process: |
||
314 | ret[stat_prefix + '_min'] = proc[stat_prefix + '_percent'] |
||
315 | else: |
||
316 | ret[stat_prefix + '_min'] = ( |
||
317 | proc[stat_prefix + '_percent'] |
||
318 | if proc[stat_prefix + '_min'] > proc[stat_prefix + '_percent'] |
||
319 | else proc[stat_prefix + '_min'] |
||
320 | ) |
||
321 | if stat_prefix + '_max' not in self.extended_process: |
||
322 | ret[stat_prefix + '_max'] = proc[stat_prefix + '_percent'] |
||
323 | else: |
||
324 | ret[stat_prefix + '_max'] = ( |
||
325 | proc[stat_prefix + '_percent'] |
||
326 | if proc[stat_prefix + '_max'] < proc[stat_prefix + '_percent'] |
||
327 | else proc[stat_prefix + '_max'] |
||
328 | ) |
||
329 | if stat_prefix + '_mean_sum' not in self.extended_process: |
||
330 | ret[stat_prefix + '_mean_sum'] = proc[stat_prefix + '_percent'] |
||
331 | else: |
||
332 | ret[stat_prefix + '_mean_sum'] = proc[stat_prefix + '_mean_sum'] + proc[stat_prefix + '_percent'] |
||
333 | if stat_prefix + '_mean_counter' not in self.extended_process: |
||
334 | ret[stat_prefix + '_mean_counter'] = 1 |
||
335 | else: |
||
336 | ret[stat_prefix + '_mean_counter'] = proc[stat_prefix + '_mean_counter'] + 1 |
||
337 | ret[stat_prefix + '_mean'] = ret[stat_prefix + '_mean_sum'] / ret[stat_prefix + '_mean_counter'] |
||
338 | |||
339 | ret['extended_stats'] = True |
||
340 | return ret |
||
341 | |||
342 | def is_selected_extended_process(self, position): |
||
343 | """Return True if the process is the selected one for extended stats.""" |
||
344 | return ( |
||
345 | hasattr(self.args, 'programs') |
||
346 | and not self.args.programs |
||
347 | and hasattr(self.args, 'enable_process_extended') |
||
348 | and self.args.enable_process_extended |
||
349 | and not self.disable_extended_tag |
||
350 | and hasattr(self.args, 'cursor_position') |
||
351 | and position == self.args.cursor_position |
||
352 | and not self.args.disable_cursor |
||
353 | ) |
||
354 | |||
355 | def update(self): |
||
356 | """Update the processes stats.""" |
||
357 | # Reset the stats |
||
358 | self.processlist = [] |
||
359 | self.reset_processcount() |
||
360 | |||
361 | # Do not process if disable tag is set |
||
362 | if self.disable_tag: |
||
363 | return |
||
364 | |||
365 | # Time since last update (for disk_io rate computation) |
||
366 | time_since_update = getTimeSinceLastUpdate('process_disk') |
||
367 | |||
368 | # Grab standard stats |
||
369 | ##################### |
||
370 | sorted_attrs = ['cpu_percent', 'cpu_times', 'memory_percent', 'name', 'status', 'num_threads'] |
||
371 | displayed_attr = ['memory_info', 'nice', 'pid'] |
||
372 | # 'name' can not be cached because it is used for filtering |
||
373 | cached_attrs = ['cmdline', 'username'] |
||
374 | |||
375 | # Some stats are optional |
||
376 | if not self.disable_io_counters: |
||
377 | sorted_attrs.append('io_counters') |
||
378 | if not self.disable_gids: |
||
379 | displayed_attr.append('gids') |
||
380 | # Some stats are not sort key |
||
381 | # An optimisation can be done be only grabbed displayed_attr |
||
382 | # for displayed processes (but only in standalone mode...) |
||
383 | sorted_attrs.extend(displayed_attr) |
||
384 | # Some stats are cached (not necessary to be refreshed every time) |
||
385 | if self.cache_timer.finished(): |
||
386 | sorted_attrs += cached_attrs |
||
387 | self.cache_timer.set(self.cache_timeout) |
||
388 | self.cache_timer.reset() |
||
389 | is_cached = False |
||
390 | else: |
||
391 | is_cached = True |
||
392 | |||
393 | # Build the processes stats list (it is why we need psutil>=5.3.0) |
||
394 | # This is one of the main bottleneck of Glances (see flame graph) |
||
395 | # Filter processes |
||
396 | self.processlist = list( |
||
397 | filter( |
||
398 | lambda p: not (BSD and p.info['name'] == 'idle') |
||
399 | and not (WINDOWS and p.info['name'] == 'System Idle Process') |
||
400 | and not (MACOS and p.info['name'] == 'kernel_task') |
||
401 | and not (self.no_kernel_threads and LINUX and p.info['gids'].real == 0), |
||
402 | psutil.process_iter(attrs=sorted_attrs, ad_value=None), |
||
403 | ) |
||
404 | ) |
||
405 | # Only get the info key |
||
406 | self.processlist = [p.info for p in self.processlist] |
||
407 | # Sort the processes list by the current sort_key |
||
408 | self.processlist = sort_stats(self.processlist, sorted_by=self.sort_key, reverse=True) |
||
409 | |||
410 | # Update the processcount |
||
411 | self.update_processcount(self.processlist) |
||
412 | |||
413 | # Loop over processes and : |
||
414 | # - add extended stats for selected process |
||
415 | # - add metadata |
||
416 | for position, proc in enumerate(self.processlist): |
||
417 | # Extended stats |
||
418 | ################ |
||
419 | |||
420 | # Get the selected process when the 'e' key is pressed |
||
421 | if self.is_selected_extended_process(position): |
||
422 | self.extended_process = proc |
||
423 | |||
424 | # Grab extended stats only for the selected process (see issue #2225) |
||
425 | if self.extended_process is not None and proc['pid'] == self.extended_process['pid']: |
||
426 | proc.update(self.get_extended_stats(self.extended_process)) |
||
427 | self.extended_process = proc |
||
428 | |||
429 | # Meta data |
||
430 | ########### |
||
431 | |||
432 | # PID is the key |
||
433 | proc['key'] = 'pid' |
||
434 | |||
435 | # Time since last update (for disk_io rate computation) |
||
436 | proc['time_since_update'] = time_since_update |
||
437 | |||
438 | # Process status (only keep the first char) |
||
439 | proc['status'] = str(proc['status'])[:1].upper() |
||
440 | |||
441 | # Process IO |
||
442 | # procstat['io_counters'] is a list: |
||
443 | # [read_bytes, write_bytes, read_bytes_old, write_bytes_old, io_tag] |
||
444 | # If io_tag = 0 > Access denied or first time (display "?") |
||
445 | # If io_tag = 1 > No access denied (display the IO rate) |
||
446 | if 'io_counters' in proc and proc['io_counters'] is not None: |
||
447 | io_new = [proc['io_counters'].read_bytes, proc['io_counters'].write_bytes] |
||
448 | # For IO rate computation |
||
449 | # Append saved IO r/w bytes |
||
450 | try: |
||
451 | proc['io_counters'] = io_new + self.io_old[proc['pid']] |
||
452 | io_tag = 1 |
||
453 | except KeyError: |
||
454 | proc['io_counters'] = io_new + [0, 0] |
||
455 | io_tag = 0 |
||
456 | # then save the IO r/w bytes |
||
457 | self.io_old[proc['pid']] = io_new |
||
458 | else: |
||
459 | proc['io_counters'] = [0, 0] + [0, 0] |
||
460 | io_tag = 0 |
||
461 | # Append the IO tag (for display) |
||
462 | proc['io_counters'] += [io_tag] |
||
463 | |||
464 | # Manage cached information |
||
465 | if is_cached: |
||
466 | # Grab cached values (in case of a new incoming process) |
||
467 | if proc['pid'] not in self.processlist_cache: |
||
468 | try: |
||
469 | self.processlist_cache[proc['pid']] = psutil.Process(pid=proc['pid']).as_dict( |
||
470 | attrs=cached_attrs, ad_value=None |
||
471 | ) |
||
472 | except psutil.NoSuchProcess: |
||
473 | pass |
||
474 | # Add cached value to current stat |
||
475 | try: |
||
476 | proc.update(self.processlist_cache[proc['pid']]) |
||
477 | except KeyError: |
||
478 | pass |
||
479 | else: |
||
480 | # Save values to cache |
||
481 | self.processlist_cache[proc['pid']] = {cached: proc[cached] for cached in cached_attrs} |
||
482 | |||
483 | # Apply user filter |
||
484 | self.processlist = list(filter(lambda p: not self._filter.is_filtered(p), self.processlist)) |
||
485 | |||
486 | # Compute the maximum value for keys in self._max_values_list: CPU, MEM |
||
487 | # Useful to highlight the processes with maximum values |
||
488 | for k in self._max_values_list: |
||
489 | values_list = [i[k] for i in self.processlist if i[k] is not None] |
||
490 | if values_list: |
||
491 | self.set_max_values(k, max(values_list)) |
||
492 | |||
493 | def get_count(self): |
||
494 | """Get the number of processes.""" |
||
495 | return self.processcount |
||
496 | |||
497 | def getlist(self, sorted_by=None, as_programs=False): |
||
498 | """Get the processlist. |
||
499 | By default, return the list of threads. |
||
500 | If as_programs is True, return the list of programs.""" |
||
501 | if as_programs: |
||
502 | return processes_to_programs(self.processlist) |
||
503 | else: |
||
504 | return self.processlist |
||
505 | |||
506 | @property |
||
507 | def sort_key(self): |
||
508 | """Get the current sort key.""" |
||
509 | return self._sort_key |
||
510 | |||
511 | def set_sort_key(self, key, auto=True): |
||
512 | """Set the current sort key.""" |
||
513 | if key == 'auto': |
||
514 | self.auto_sort = True |
||
515 | self._sort_key = 'cpu_percent' |
||
516 | else: |
||
517 | self.auto_sort = auto |
||
518 | self._sort_key = key |
||
519 | |||
520 | def nice_decrease(self, pid): |
||
521 | """Decrease nice level |
||
522 | On UNIX this is a number which usually goes from -20 to 20. |
||
523 | The higher the nice value, the lower the priority of the process.""" |
||
524 | p = psutil.Process(pid) |
||
525 | try: |
||
526 | p.nice(p.nice() - 1) |
||
527 | logger.info('Set nice level of process {} to {} (higher the priority)'.format(pid, p.nice())) |
||
528 | except psutil.AccessDenied: |
||
529 | logger.warning( |
||
530 | 'Can not decrease (higher the priority) the nice level of process {} (access denied)'.format(pid) |
||
531 | ) |
||
532 | |||
533 | def nice_increase(self, pid): |
||
534 | """Increase nice level |
||
535 | On UNIX this is a number which usually goes from -20 to 20. |
||
536 | The higher the nice value, the lower the priority of the process.""" |
||
537 | p = psutil.Process(pid) |
||
538 | try: |
||
539 | p.nice(p.nice() + 1) |
||
540 | logger.info('Set nice level of process {} to {} (lower the priority)'.format(pid, p.nice())) |
||
541 | except psutil.AccessDenied: |
||
542 | logger.warning( |
||
543 | 'Can not increase (lower the priority) the nice level of process {} (access denied)'.format(pid) |
||
544 | ) |
||
545 | |||
546 | def kill(self, pid, timeout=3): |
||
547 | """Kill process with pid""" |
||
548 | assert pid != os.getpid(), "Glances can kill itself..." |
||
549 | p = psutil.Process(pid) |
||
550 | logger.debug('Send kill signal to process: {}'.format(p)) |
||
551 | p.kill() |
||
552 | return p.wait(timeout) |
||
553 | |||
554 | |||
555 | def weighted(value): |
||
556 | """Manage None value in dict value.""" |
||
557 | return -float('inf') if value is None else value |
||
558 | |||
559 | |||
560 | def _sort_io_counters(process, sorted_by='io_counters', sorted_by_secondary='memory_percent'): |
||
561 | """Specific case for io_counters |
||
562 | |||
563 | :return: Sum of io_r + io_w |
||
564 | """ |
||
565 | return process[sorted_by][0] - process[sorted_by][2] + process[sorted_by][1] - process[sorted_by][3] |
||
566 | |||
567 | |||
568 | def _sort_cpu_times(process, sorted_by='cpu_times', sorted_by_secondary='memory_percent'): |
||
569 | """Specific case for cpu_times |
||
570 | |||
571 | Patch for "Sorting by process time works not as expected #1321" |
||
572 | By default PsUtil only takes user time into account |
||
573 | see (https://github.com/giampaolo/psutil/issues/1339) |
||
574 | The following implementation takes user and system time into account |
||
575 | """ |
||
576 | return process[sorted_by][0] + process[sorted_by][1] |
||
577 | |||
578 | |||
579 | def _sort_lambda(sorted_by='cpu_percent', sorted_by_secondary='memory_percent'): |
||
580 | """Return a sort lambda function for the sorted_by key""" |
||
581 | ret = None |
||
582 | if sorted_by == 'io_counters': |
||
583 | ret = _sort_io_counters |
||
584 | elif sorted_by == 'cpu_times': |
||
585 | ret = _sort_cpu_times |
||
586 | return ret |
||
587 | |||
588 | |||
589 | def sort_stats(stats, sorted_by='cpu_percent', sorted_by_secondary='memory_percent', reverse=True): |
||
590 | """Return the stats (dict) sorted by (sorted_by). |
||
591 | |||
592 | Reverse the sort if reverse is True. |
||
593 | """ |
||
594 | if sorted_by is None and sorted_by_secondary is None: |
||
595 | # No need to sort... |
||
596 | return stats |
||
597 | |||
598 | # Check if a specific sort should be done |
||
599 | sort_lambda = _sort_lambda(sorted_by=sorted_by, sorted_by_secondary=sorted_by_secondary) |
||
600 | |||
601 | if sort_lambda is not None: |
||
602 | # Specific sort |
||
603 | try: |
||
604 | stats.sort(key=sort_lambda, reverse=reverse) |
||
605 | except Exception: |
||
606 | # If an error is detected, fallback to cpu_percent |
||
607 | stats.sort( |
||
608 | key=lambda process: (weighted(process['cpu_percent']), weighted(process[sorted_by_secondary])), |
||
609 | reverse=reverse, |
||
610 | ) |
||
611 | else: |
||
612 | # Standard sort |
||
613 | try: |
||
614 | stats.sort( |
||
615 | key=lambda process: (weighted(process[sorted_by]), weighted(process[sorted_by_secondary])), |
||
616 | reverse=reverse, |
||
617 | ) |
||
618 | except (KeyError, TypeError): |
||
619 | # Fallback to name |
||
620 | stats.sort(key=lambda process: process['name'] if process['name'] is not None else '~', reverse=False) |
||
621 | |||
622 | return stats |
||
623 | |||
624 | |||
625 | glances_processes = GlancesProcesses() |
||
626 |