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