def test_find_processes(self): """Test the :func:`proc.core.find_processes()` function.""" # Test argument validation. Obscure Python implementation detail: # Because find_processes() returns a generator we need to actually ask # for the first value to be produced in order to invoke the argument # validation. self.assertRaises(TypeError, next, find_processes(obj_type=object)) # Test some basic assumptions about the result of find_processes(). processes = dict((p.pid, p) for p in find_processes()) assert 1 in processes, "init process not found in output of find_processes()!" assert processes[1].comm == 'init', "init isn't called init?!" assert os.getpid( ) in processes, "Current process not found in output of find_processes()!"
def find_graphical_context(): """ Create a command execution context for the current graphical session. :returns: A :class:`~executor.contexts.LocalContext` object. This function scans the process tree for processes that are running in a graphical session and collects information about graphical sessions from each of these processes. The collected information is then ranked by "popularity" (number of occurrences) and the most popular information is used to create a command execution context that targets the graphical session. """ options = {} # Collect information about graphical sessions from running processes. matches = collections.defaultdict(int) for process in find_processes(): environment = dict((k, v) for k, v in process.environ.items() if k in REQUIRED_VARIABLES and v) if environment: hashable_environment = tuple(sorted(environment.items())) matches[(process.user_ids.real, hashable_environment)] += 1 ordered = sorted((counter, key) for key, counter in matches.items()) if ordered: # Pick the most popular graphical session. counter, key = ordered[-1] uid, environment = key # Apply the user ID to the context? if os.getuid() != uid: options['uid'] = uid # Apply the environment to the context. options['environment'] = dict(environment) return LocalContext(**options)
def find_gpg_agent_info(): """ Reconstruct ``$GPG_AGENT_INFO`` based on a running ``gpg-agent`` process. :returns: A string or :data:`None`. This function uses :func:`~proc.core.find_processes()` to search for ``gpg-agent`` processes and runs lsof_ to find out which UNIX socket is being used by the agent. Based on this information it reconstructs the expected value of ``$GPG_AGENT_INFO``. """ logger.debug("Searching for running GPG agent ..") our_uid = os.getuid() for process in find_processes(): if process.exe_name == 'gpg-agent': logger.debug("Found GPG agent with PID %i, checking user id .. ", process.pid) if process.user_ids.real == our_uid: logger.debug( "GPG agent user id matches ours! Using `lsof' to determine socket .." ) # A quick lsof tutorial :-) # -F enables output that is easy to parse, # -p lists the open files of a specific PID, # -a combines -p and -U using AND instead of OR, # -U lists only UNIX domain socket files. output = execute('lsof', '-F', '-p', str(process.pid), '-a', '-U', capture=True, check=False) for line in output.splitlines(): if line and line[0] == 'n': filename = line[1:] if filename: logger.debug( "UNIX domain socket reported by lsof: %s", filename) if os.access(filename, os.W_OK): # We will now reconstruct $GPG_AGENT_INFO based on the # information that we've gathered. We should end up with # an expression like `/tmp/gpg-KE5ZZL/S.gpg-agent:2407:1'. agent_info = ':'.join( [filename, str(process.pid), '1']) logger.debug( "Reconstructed $GPG_AGENT_INFO: %s", agent_info) return agent_info else: logger.debug( "No write access to socket, ignoring process %i.", process.pid) else: logger.debug( "GPG agent user id (%i) doesn't match ours (%i), ignoring process %i.", process.user_ids.real, our_uid, process.pid)
def test_exe_path_fallback(self): """Test the fall back method of :attr:`proc.core.Process.exe_path`.""" candidates = [p for p in find_processes() if p.exe_path and not p.exe] logger.debug("Candidates for Process.exe_path fall back test:\n%s", pformat(candidates)) if not candidates: return self.skipTest( "No processes available on which Process.exe_path fall back can be tested!" ) assert executable(candidates[0].exe_path), \ "Fall back method of Process.exe_path reported invalid executable pathname!"
def find_gpg_agent_info(): """ Reconstruct ``$GPG_AGENT_INFO`` based on a running ``gpg-agent`` process. :returns: A string or :data:`None`. This function uses :func:`~proc.core.find_processes()` to search for ``gpg-agent`` processes and runs lsof_ to find out which UNIX socket is being used by the agent. Based on this information it reconstructs the expected value of ``$GPG_AGENT_INFO``. """ logger.debug("Searching for running GPG agent ..") our_uid = os.getuid() for process in find_processes(): if process.exe_name == 'gpg-agent': logger.debug("Found GPG agent with PID %i, checking user id .. ", process.pid) their_uid = process.user_ids.real if process.user_ids else 'unknown' if our_uid == their_uid: socket_file = None logger.debug( "GPG agent user id matches ours! Looking for UNIX socket .." ) fixed_socket = find_fixed_agent_socket() if validate_unix_socket(fixed_socket): logger.debug("Found GnuPG >= 2.1 compatible socket: %s", fixed_socket) socket_file = fixed_socket else: logger.debug("Using `lsof' to find open UNIX sockets ..") for filename in find_open_unix_sockets(process.pid): logger.debug("UNIX domain socket reported by lsof: %s", filename) if validate_unix_socket(filename): socket_file = filename break # We will now reconstruct $GPG_AGENT_INFO based on the # information that we've gathered. We should end up with # an expression like `/tmp/gpg-KE5ZZL/S.gpg-agent:2407:1'. if socket_file: agent_info = ':'.join([socket_file, str(process.pid), '1']) logger.debug("Reconstructed $GPG_AGENT_INFO: %s", agent_info) return agent_info else: logger.debug( "GPG agent user id (%s) doesn't match ours (%i), ignoring process %i.", their_uid, our_uid, process.pid)
def running_processes() -> Iterator[Tuple[int, str, int, int]]: """ Returns an interator of all processes running on the system. Iterator contains tuples of [@profile_key, @exe, @pid, @tid] """ for p in find_processes(): exe = p.exe pid = p.pgrp tid = p.pid if not exe: continue try: profile_key = calculate_profile_key(exe) except Exception: continue yield (profile_key, exe, pid, tid)
def test_exe_name_fallback(self): """Test the fall back method of :attr:`proc.core.Process.exe_name`.""" if os.getuid() == 0: # Given root privileges all /proc/[pid]/exe symbolic links can be # successfully resolved so we can't test the fall back method. return self.skipTest( "Fall back method of Process.exe_name is useless with root privileges!" ) candidates = [ p for p in find_processes() if p.exe_name and not p.exe_path ] logger.debug("Candidates for Process.exe_name fall back test:\n %s", pformat(candidates)) if not candidates: return self.skipTest( "No processes available on which Process.exe_name fall back can be tested!" ) assert any(which(p.exe_name) for p in candidates), \ "Fall back method of Process.exe_name reported executable base name not available on $PATH?!"
def _stop_force(self): """ The _stop_force method iterates over the /proc and search for toaster path in the process cmdline then send SIGKILL for every matched process. """ pids = [] for p in find_processes(): if len(p.cmdline) > 1 and \ os.path.basename(p.cmdline[0]) == 'python' and \ p.cmdline[1].startswith(self.directory): pids.append(p.pid) for pid in pids: try: os.kill(pid, signal.SIGKILL) except: pass return ''
def get_process_tree(obj_type=ProcessNode): """ Construct a process tree from the result of :func:`~proc.core.find_processes()`. :param obj_type: The type of process objects to construct (expected to be :class:`ProcessNode` or a subclass of :class:`ProcessNode`). :returns: A :class:`ProcessNode` object that forms the root node of the constructed tree (this node represents init_). .. _init: http://en.wikipedia.org/wiki/init """ if not issubclass(obj_type, ProcessNode): raise TypeError("Custom process types should inherit from proc.tree.ProcessNode!") mapping = dict((p.pid, p) for p in find_processes(obj_type=obj_type)) for obj in mapping.values(): if obj.ppid != 0 and obj.ppid in mapping: obj.parent = mapping[obj.ppid] obj.parent.children.append(obj) return mapping[1]
def test_race_conditions(self, timeout=60): """ Test the handling of race conditions in :mod:`proc.core`. Scanning ``/proc`` is inherently vulnerable to race conditions, for example: 1. A listing of available processes in ``/proc`` confirms a process exists, but by the time ``/proc/[pid]/stat`` is read the process has ended and ``/proc/[pid]`` no longer exists. 2. A :class:`proc.core.Process` object is constructed from the information available in ``/proc/[pid]/stat``, but by the time ``/proc/[pid]/cmdline`` is read the process has ended and ``/proc/[pid]`` no longer exists. This test intentionally creates race conditions in the reading of ``/proc/[pid]/stat``, ``/proc/[pid]/cmdline`` and ``/proc/[pid]/environ`` files, to verify that the :mod:`proc.core` module never breaks on a race condition. It works by using the :mod:`multiprocessing` module to quickly spawn and reclaim subprocesses while at the same time scanning through ``/proc`` continuously. The test times out after 60 seconds but in all of the runs I've tried so far it never needs more than 10 seconds to encounter a handful of race conditions. """ # Copy the race condition counters so we can verify all counters have # increased before we consider this test to have passed. logger.info( "Testing handling of race conditions, please be patient :-) ..") timer = Timer() at_start = dict(num_race_conditions) shutdown_event = multiprocessing.Event() manager = multiprocessing.Process(target=race_condition_manager, args=(shutdown_event, )) manager.start() try: while True: # Scan the process tree with the knowledge that subprocesses could # be disappearing any second now :-). for process in find_processes(): if process.ppid == manager.pid: # Force a time window between when /proc/[pid]/stat was # read and when /proc/[pid]/cmdline will be read. time.sleep(0.1) # Read /proc/[pid]/cmdline, /proc/[pid]/environ and # /proc/[pid]/exe even though they may no longer exist. assert isinstance(process.cmdline, list) assert isinstance(process.environ, dict) assert isinstance(process.exe, basestring) assert isinstance(process.status_fields, dict) # Check whether race conditions have been handled. if all(num_race_conditions[k] > at_start[k] for k in at_start): # The test has passed: We were able to simulate at least # one race condition of every type within the timeout. logger.info( "Successfully finished race condition test in %s.", timer) return assert timer.elapsed_time < timeout, "Timeout elapsed before race conditions could be simulated!" # Don't burn CPU cycles too much. time.sleep(0.1) finally: shutdown_event.set() manager.join()
import os from proc.core import find_processes import smtplib from email.mime.text import MIMEText from email.parser import Parser import subprocess from datetime import datetime, timedelta if __name__ == '__main__': page_size = os.sysconf('SC_PAGE_SIZE') physical_pages = os.sysconf('SC_PHYS_PAGES') total_mem = page_size * physical_pages print('Page size = {}, pages = {}, total_mem = {} ({:.1f} MB)'.format( page_size, physical_pages, total_mem, total_mem / (1024**2))) nginx_processes = [x for x in find_processes() if x.comm == 'nginx'] cumulative_percent = 0 for x in nginx_processes: print('nginx process pid = {}, parent_pid = {} , rss = {} '.format( x.pid, x.ppid, x.rss)) process_percent = (x.rss / total_mem) * 100 cumulative_percent = cumulative_percent + process_percent print('Cumulative nginx memory use = ', cumulative_percent) if cumulative_percent > 75: # Take action at the 75% mark - we want to schedule an nginx restart action_taken = 'Scheduling at for restart'
def main(): for p in find_processes(): pid = p.pid mem = p.rss