API documentation

This API documentation is based on the source code of version 0.12 of the proc package.

Generic modules

The following generic modules are available:

The proc module

The top level proc module contains only a version number.

proc.__version__

The version number of the proc package (a string).

The proc.unix module

The proc.unix module manipulates UNIX processes using signals.

This module contains no Linux-specific details, instead it relies only on process IDs and common UNIX signal semantics to:

  1. Determine whether a given process ID is still alive (signal 0);
  2. gracefully (SIGTERM) and forcefully (SIGKILL) terminate processes;
  3. suspend (SIGSTOP) and resume (SIGCONT) processes.
class proc.unix.UnixProcess(**kw)

Integration between executor.process.ControllableProcess and common UNIX signals.

UnixProcess extends ControllableProcess which means all of the process manipulation supported by ControllableProcess is also supported by UnixProcess objects.

is_running

True if the process is currently running, False otherwise.

This implementation sends the signal number zero to pid and uses the result to infer whether the process is alive or not (this technique is documented in man kill):

  • If the sending of the signal doesn’t raise an exception the process received the signal just fine and so must it exist.
  • If an OSError exception with error number EPERM is raised we don’t have permission to signal the process, which implies that the process is alive.
  • If an OSError exception with error number ESRCH is raised we know that no process with the given id exists.

An advantage of this approach (on UNIX systems) is that you don’t need to be a parent of the process in question. A disadvantage of this approach is that it is never going to work on Windows (if you’re serious about portability consider using a package like psutil).

Warning

After a process has been terminated but before the parent process has reclaimed its child process this property returns True. Usually this is a small time window, but when it isn’t it can be really confusing.

kill_helper()

Forcefully kill the process (by sending it a SIGKILL signal).

Raises:OSError when the signal can’t be delivered.

The SIGKILL signal cannot be intercepted or ignored and causes the immediate termination of the process (under regular circumstances). Non-regular circumstances are things like blocking I/O calls on an NFS share while your file server is down (fun times!).

pid

The process ID of the process (an integer).

Note

The pid property is a required_property. You are required to provide a value for this property by calling the constructor of the class that defines the property with a keyword argument named pid (unless a custom constructor is defined, in this case please refer to the documentation of that constructor). You can change the value of this property using normal attribute assignment syntax.

resume()

Resume a process that was previously paused using suspend().

Raises:OSError when the signal can’t be delivered.

The resume() method sends a SIGCONT signal to the process. This signal resumes a process that was previously paused using SIGSTOP (e.g. using suspend()).

suspend()

Suspend the process so that its execution can be resumed later.

Raises:OSError when the signal can’t be delivered.

The suspend() method sends a SIGSTOP signal to the process. This signal cannot be intercepted or ignored and has the effect of completely pausing the process until you call resume().

terminate_helper()

Gracefully terminate the process (by sending it a SIGTERM signal).

Raises:OSError when the signal can’t be delivered.

Processes can choose to intercept SIGTERM to allow for graceful termination (many daemon processes work like this) however the default action is to simply exit immediately.

The proc.core module

The proc.core module contains the core functionality of the proc package.

This module provides a simple interface to the process information available in /proc. It takes care of the text parsing that’s necessary to gather process information from /proc but it doesn’t do much more than that. The functions in this module produce Process objects.

If you’re just getting started with this module I suggest you jump to the documentation of find_processes() because this function provides the “top level entry point” into most of the functionality provided by this module.

class proc.core.OwnerIDs

A set of user or group IDs found in /proc/[pid]/status.

OwnerIDs objects are named tuples containing four integer numbers called real, effective, saved and fs.

class proc.core.Process(proc_tree, stat_fields)

Process information based on /proc/[pid]/stat and similar files.

Process objects are constructed using find_processes() and Process.from_path(). You shouldn’t be using the Process constructor directly unless you know what you’re doing.

The Process class extends UnixProcess which means all of the process manipulation supported by UnixProcess is also supported by Process objects.

Comparison to official /proc documentation

Quite a few of the instance properties of this class are based on (and named after) fields extracted from /proc/[pid]/stat. The following table lists these properties and the zero based index of the corresponding field in /proc/[pid]/stat:

Property Index
pid 0
comm 1
state 2
ppid 3
pgrp 4
session 5
starttime 21
vsize 22
rss 23

As you can see from the indexes in the table above quite a few fields from /proc/[pid]/stat are not currently exposed by Process objects. In fact /proc/[pid]/stat contains 44 fields! Some of these fields are no longer maintained by the Linux kernel and remain only for backwards compatibility (so exposing them is not useful) while other fields are not exposed because I didn’t consider them relevant to a Python API. If your use case requires fields that are not yet exposed, feel free to suggest additional fields to expose in the issue tracker.

The documentation on the properties of this class quotes from and paraphrases the text in man 5 proc so if things are unclear and you’re feeling up to it, dive into the huge manual page for clarifications :-).

cmdline

The complete command line for the process (a list of strings).

Availability:

  • This property is parsed from the contents of /proc/[pid]/cmdline the first time it is referenced, after that its value is cached so it will always be available (although by then it may no longer be up to date because processes can change their command line at runtime on Linux).
  • If this property is first referenced after the process turns into a zombie or the process ends then it’s too late to read the contents of /proc/[pid]/cmdline and an empty list is returned.

Note

In Linux it is possible for a process to change its command line after it has started. Modern daemons tend to do this in order to communicate their status. Here’s an example of how the Nginx web server uses this feature:

>>> from proc.core import find_processes
>>> from pprint import pprint
>>> pprint([(p.pid, p.cmdline) for p in find_processes() if p.comm == 'nginx'])
[(2662, ['nginx: master process /usr/sbin/nginx']),
 (25100, ['nginx: worker process']),
 (25101, ['nginx: worker process']),
 (25102, ['nginx: worker process']),
 (25103, ['nginx: worker process'])]

What this means is that (depending on the behavior of the process in question) it may be impossible to determine the effective command line that was used to start a process. If you’re just interested in the pathname of the executable consider using the exe property instead:

>>> from proc.core import find_processes
>>> from pprint import pprint
>>> pprint([(p.pid, p.exe) for p in find_processes() if p.comm == 'nginx'])
[(2662, '/usr/sbin/nginx'),
 (25100, '/usr/sbin/nginx'),
 (25101, '/usr/sbin/nginx'),
 (25102, '/usr/sbin/nginx'),
 (25103, '/usr/sbin/nginx')]

Note

The cmdline property is a lazy_property. This property’s value is computed once (the first time it is accessed) and the result is cached.

comm

The filename of the executable.

Availability: This property is parsed from the contents of /proc/[pid]/stat and is always available.

The filename is not enclosed in parentheses like it is in /proc/[pid]/stat because the parentheses are an implementation detail of the encoding of /proc/[pid]/stat and the whole point of proc.core is to hide ugly encoding details like this :-).

Note

This field can be truncated by the Linux kernel so strictly speaking you can’t rely on this field unless you know that the executables you’re interested in have short names. Here’s an example of what I’m talking about:

>>> from proc.core import find_processes
>>> next(p for p in find_processes() if p.comm.startswith('console'))
Process(pid=2753,
        comm='console-kit-dae',
        state='S',
        ppid=1,
        pgrp=1632,
        session=1632,
        vsize=2144198656,
        rss=733184,
        cmdline=['/usr/sbin/console-kit-daemon', '--no-daemon'])

As you can see in the example above the executable name console-kit-daemon is truncated to console-kit-dae. If you need a reliable way to find the executable name consider using the cmdline and/or exe properties.

Note

The comm property is a lazy_property. This property’s value is computed once (the first time it is accessed) and the result is cached.

command_line

An alias for the cmdline property.

This alias exists so that ControllableProcess can log process ids and command lines (this helps to make the log output more human friendly).

environ

The environment of the process (a dictionary with string key/value pairs).

Availability:

  • This property is parsed from the contents of /proc/[pid]/environ the first time it is referenced, after that its value is cached so it will always be available.
  • If this property is first referenced after the process turns into a zombie or the process ends then it’s too late to read the contents of /proc/[pid]/environ and an empty dictionary is returned.

Note

The environ property is a lazy_property. This property’s value is computed once (the first time it is accessed) and the result is cached.

exe

The actual pathname of the executed command (a string).

Availability:

  • This property is constructed by dereferencing the symbolic link /proc/[pid]/exe the first time the property is referenced, after that its value is cached so it will always be available.
  • If this property is referenced after the process has ended then it’s too late to dereference the symbolic link and an empty string is returned.
  • If an exception is encountered while dereferencing the symbolic link (for example because you don’t have permission to dereference the symbolic link) the exception is swallowed and an empty string is returned.

Note

The exe property is a lazy_property. This property’s value is computed once (the first time it is accessed) and the result is cached.

exe_name

The base name of the executable (a string).

It can be tricky to reliably determine the name of the executable of an arbitrary process and this property tries to make it easier. Its value is based on the first of the following methods that works:

  1. If exe_path is available then the base name of this pathname is returned.
    • Pro: When the exe_path property is available it is fairly reliable.
    • Con: The exe_path property can be unavailable (refer to its documentation for details).
  2. If the first string in cmdline contains a name that is available on the executable search path ($PATH) then this name is returned.
    • Pro: As long as cmdline contains the name of an executable available on the $PATH this method works.
    • Con: This method can fail because a process has changed its own command line (after it was started).
  3. If both of the methods above fail comm is returned.
    • Pro: The comm field is always available.
    • Con: The comm field may be truncated.

Note

The exe_name property is a lazy_property. This property’s value is computed once (the first time it is accessed) and the result is cached.

exe_path

The absolute pathname of the executable (a string).

It can be tricky to reliably determine the pathname of the executable of an arbitrary process and this property tries to make it easier. Its value is based on the first of the following methods that works:

  1. If exe is available then this pathname is returned.
    • Pro: This method provides the most reliable way to determine the absolute pathname of the executed command because (as far as I know) it always provides an absolute pathname.
    • Con: This method can fail because you don’t have permission to dereference the /proc/[pid]/exe symbolic link.
  2. If the first string in cmdline contains the absolute pathname of an executable file then this pathname is returned.
    • Pro: This method doesn’t require the same permissions that method one requires.
    • Con: This method can fail because a process has changed its own command line (after it was started) or because the first string in the command line isn’t an absolute pathname.
  3. If both of the methods above fail an empty string is returned.

Note

The exe_path property is a lazy_property. This property’s value is computed once (the first time it is accessed) and the result is cached.

classmethod from_path(directory)

Construct a process information object from a numerical subdirectory of /proc.

Parameters:directory – The absolute pathname of the numerical subdirectory of /proc to get process information from (a string).
Returns:A process information object or None (in case the process ends before its information can be read).

This class method is used by find_processes() to construct Process objects. It’s exposed as a separate method because it may sometimes be useful to call directly. For example:

>>> from proc.core import Process
>>> Process.from_path('/proc/self')
Process(pid=1468,
        comm='python',
        state='R',
        ppid=21982,
        pgrp=1468,
        session=21982,
        vsize=40431616,
        rss=8212480,
        cmdline=['python'],
        exe='/home/peter/.virtualenvs/proc/bin/python')
classmethod from_pid(pid)

Construct a process information object based on a process ID.

Parameters:pid – The process ID (an integer).
Returns:A process information object or None (in case the process ends before its information can be read).
group

The name of the real group ID (a string).

Availability: Refer to group_ids. None is returned if group_ids is unavailable or gid_to_name() fails.

Note

The group property is a lazy_property. This property’s value is computed once (the first time it is accessed) and the result is cached.

group_ids

The real, effective, saved set, and filesystem GIDs of the process (an OwnerIDs object).

Availability: Refer to status_fields. None is returned if status_fields is unavailable.

Note

The group_ids property is a lazy_property. This property’s value is computed once (the first time it is accessed) and the result is cached.

is_alive

True if the process is still alive, False otherwise.

This property reads the /proc/[pid]/stat file each time the property is referenced to make sure that the process still exists and has not turned into a zombie process.

See also suspend(), resume(), terminate() and kill().

is_running

An alias for is_alive.

This alias makes UnixProcess objects aware of zombie processes so that e.g. killing of a zombie process doesn’t hang indefinitely (waiting for a zombie that will never die).

pgrp

The process group ID of the process (an integer).

Availability: This property is parsed from the contents of /proc/[pid]/stat and is always available.

Note

The pgrp property is a lazy_property. This property’s value is computed once (the first time it is accessed) and the result is cached.

pid

The process ID (an integer).

Availability: This property is parsed from the contents of /proc/[pid]/stat and is always available.

Note

The pid property is a lazy_property. This property’s value is computed once (the first time it is accessed) and the result is cached.

ppid

The process ID of the parent process (an integer).

Availability: This property is parsed from the contents of /proc/[pid]/stat and is always available.

This field is zero when the process doesn’t have a parent process (same as in /proc/[pid]/stat). Because Python treats the integer 0 as False this field can be used as follows to find processes without a parent process:

>>> from proc.core import find_processes
>>> pprint([p for p in find_processes() if not p.ppid])
[Process(pid=1, comm='init', state='S', pgrp=1, session=1, vsize=25174016, rss=1667072, cmdline=['/sbin/init']),
 Process(pid=2, comm='kthreadd', state='S', pgrp=0, session=0, vsize=0, rss=0)]

Note

The ppid property is a lazy_property. This property’s value is computed once (the first time it is accessed) and the result is cached.

rss

The resident set size of the process in bytes (an integer).

Quoting from man 5 proc:

Number of pages the process has in real memory. This is just the pages which count toward text, data, or stack space. This does not include pages which have not been demand-loaded in, or which are swapped out.

This property translates pages to bytes by multiplying the value extracted from /proc/[pid]/stat with the result of:

os.sysconf('SC_PAGESIZE')

Availability: This property is parsed from the contents of /proc/[pid]/stat and is always available.

Note

The rss property is a lazy_property. This property’s value is computed once (the first time it is accessed) and the result is cached.

runtime

The time in seconds since the process started (a float).

This property is calculated based on starttime every time it’s requested (so it will always be up to date).

Warning

The runtime will not stop growing when the process ends because doing so would require a background thread just to monitor when the process ends... This is an unfortunate side effect of the architecture of /proc – processes disappear from /proc the moment they end so the information about when the process ended is lost!

session

The session ID of the process (an integer).

Availability: This property is parsed from the contents of /proc/[pid]/stat and is always available.

Note

The session property is a lazy_property. This property’s value is computed once (the first time it is accessed) and the result is cached.

starttime

The time at which the process was started (a float).

Paraphrasing from man 5 proc:

The time the process started after system boot. In kernels before Linux 2.6, this value was expressed in jiffies. Since Linux 2.6, the value is expressed in clock ticks.

This property translates clock ticks to seconds by dividing the value extracted from /proc/[pid]/stat with the result of:

os.sysconf('SC_CLK_TCK')

After the conversion to seconds the system’s uptime is used to determine the absolute start time of the process (the number of seconds since the Unix epoch).

See also the runtime property.

Availability: This property is calculated from the contents of /proc/[pid]/stat and /proc/uptime and is always available.

Note

The starttime property is a lazy_property. This property’s value is computed once (the first time it is accessed) and the result is cached.

state

A single uppercase character describing the state of the process (a string).

Quoting from man 5 proc:

One character from the string “RSDZTW” where R is running, S is sleeping in an interruptible wait, D is waiting in uninterruptible disk sleep, Z is zombie_, T is traced or stopped (on a signal), and W is paging.

Availability: This property is parsed from the contents of /proc/[pid]/stat and is always available.

Note

The state property is a lazy_property. This property’s value is computed once (the first time it is accessed) and the result is cached.

status_fields

Detailed process information (a dictionary with string key/value pairs).

The dictionaries constructed by this property are based on the contents of /proc/[pid]/status, which man 5 proc describes as follows:

Provides much of the information in /proc/[pid]/stat and /proc/[pid]/statm in a format that’s easier for humans to parse.

While it’s true that there is quite a lot of overlap between /proc/[pid]/stat and /proc/[pid]/status, the latter also exposes important information that isn’t available elsewhere (e.g. user_ids and group_ids).

Availability:

  • This property is parsed from the contents of /proc/[pid]/status the first time it is referenced, after that its value is cached so it will always be available.
  • If this property is first referenced after the process turns into a zombie or the process ends then it’s too late to read the contents of /proc/[pid]/status and an empty dictionary is returned.

Note

The status_fields property is a lazy_property. This property’s value is computed once (the first time it is accessed) and the result is cached.

user

The username of the real user ID (a string).

Availability: Refer to user_ids. None is returned if user_ids is unavailable or uid_to_name() fails.

Note

The user property is a lazy_property. This property’s value is computed once (the first time it is accessed) and the result is cached.

user_ids

The real, effective, saved set, and filesystem UIDs of the process (an OwnerIDs object).

Availability: Refer to status_fields. None is returned if status_fields is unavailable.

Note

The user_ids property is a lazy_property. This property’s value is computed once (the first time it is accessed) and the result is cached.

vsize

The virtual memory size of the process in bytes (an integer).

Availability: This property is parsed from the contents of /proc/[pid]/stat and is always available.

Note

The vsize property is a lazy_property. This property’s value is computed once (the first time it is accessed) and the result is cached.

class proc.core.ProtectedAccess(key, action)

Context manager that deals with permission errors and race conditions.

proc.core.find_processes(obj_type=<class 'proc.core.Process'>)

Scan the numerical subdirectories of /proc for process information.

Parameters:obj_type – The type of process objects to construct (expected to be Process or a subclass of Process).
Returns:A generator of Process objects.
proc.core.find_system_uptime()

Find the system’s uptime.

Returns:The uptime in seconds (a float).

This function returns the first number found in /proc/uptime.

proc.core.gid_to_name(gid)

Find the group name associated with a group ID.

Parameters:gid – The group ID (an integer).
Returns:The group name (a string) or None if grp.getgrgid() fails to locate a group for the given ID.
proc.core.parse_process_cmdline(directory)

Read and tokenize a /proc/[pid]/cmdline file.

Parameters:directory – The absolute pathname of the numerical subdirectory of /proc to get process information from (a string).
Returns:A list of strings containing the tokenized command line. If the /proc/[pid]/cmdline file disappears before it can be read an empty list is returned (in this case a warning is logged).
proc.core.parse_process_status(directory, silent=False)

Read and tokenize a /proc/[pid]/stat file.

Parameters:directory – The absolute pathname of the numerical subdirectory of /proc to get process information from (a string).
Returns:A list of strings containing the tokenized fields or None if the /proc/[pid]/stat file disappears before it can be read (in this case a warning is logged).
proc.core.sorted_by_pid(processes)

Sort the given processes by their process ID.

Parameters:processes – An iterable of Process objects.
Returns:A list of Process objects sorted by their process ID.
proc.core.uid_to_name(uid)

Find the username associated with a user ID.

Parameters:uid – The user ID (an integer).
Returns:The username (a string) or None if pwd.getpwuid() fails to locate a user for the given ID.

The proc.tree module

The proc.tree module builds tree data structures based on process trees.

This module builds on top of proc.core to provide a tree data structure that mirrors the process tree implied by the process information reported by find_processes() (specifically the ppid attributes). Here’s a simple example that shows how much code you need to find the cron daemon, it’s workers and the children of those workers (cron jobs):

>>> from proc.tree import get_process_tree
>>> init = get_process_tree()
>>> cron_daemon = init.find(exe_name='cron')
>>> cron_workers = cron_daemon.children
>>> cron_jobs = cron_daemon.grandchildren

After the above five lines the cron_jobs variable will contain a collection of ProcessNode objects, one for each cron job that cron is executing. The proc.cron module contains a more full fledged example of using the proc.tree module.

class proc.tree.ProcessNode(proc_tree, stat_fields)

Process information including relationships that model the process tree.

ProcessNode is a subclass of Process that adds relationships between processes to model the process tree as a tree data structure. This makes it easier and more intuitive to extract information from the process tree by analyzing the relationships:

children

A list of ProcessNode objects with the children of this process.

Note

The children property is a lazy_property. This property’s value is computed once (the first time it is accessed) and the result is cached.

descendants

Find the descendants of this process.

Returns:A generator of ProcessNode objects.
find(*args, **kw)

Find the first child process of this process that matches one or more criteria.

This method accepts the same parameters as the find_all() method.

Returns:The ProcessNode object of the first process that matches the given criteria or None if no processes match.
find_all(pid=None, exe_name=None, exe_path=None, recursive=False)

Find child processes of this process that match one or more criteria.

Parameters:
  • pid – If this parameter is given, only processes with the given pid will be returned.
  • exe_name – If this parameter is given, only processes with the given exe_name will be returned.
  • exe_path – If this parameter is given, only processes with the given exe_path will be returned.
  • recursive – If this is True (not the default) all processes in descendants will be searched, otherwise only the processes in children are searched (the default).
Returns:

A generator of ProcessNode objects.

grandchildren

Find the grandchildren of this process.

Returns:A generator of ProcessNode objects.
parent

The ProcessNode object of the parent of this process.

Based on the ppid attribute. None when the process doesn’t have a parent.

Note

The parent property is a writable_property. You can change the value of this property using normal attribute assignment syntax.

proc.tree.get_process_tree(obj_type=<class 'proc.tree.ProcessNode'>)

Construct a process tree from the result of find_processes().

Parameters:obj_type – The type of process objects to construct (expected to be ProcessNode or a subclass of ProcessNode).
Returns:A ProcessNode object that forms the root node of the constructed tree (this node represents init).

Application modules

Each of the following modules integrates with a specific application:

These modules are meant to actually be used (for those who are interested in them) but they also function as examples of how to use the proc.core and proc.tree modules.

The proc.apache module

The proc.apache module monitors the memory usage of Apache workers.

This module builds on top of the proc.tree module as an example of how memory usage of web server workers can be monitored using the proc package.

The main entry point of this module is find_apache_memory_usage() which provides you with the minimum, average, median and maximum memory usage of the discovered Apache worker processes (it also provides the raw rss value of each worker, in case you don’t trust the aggregates ;-).

Note

This module only works if you’ve configured your Apache web server to use an MPM based on processes (not threads). The main reason for this is that proc.core doesn’t expose information about the individual threads in a process based on /proc/[pid]/task yet (I’m still on the fence about whether to expose this information or not).

exception proc.apache.ApacheDaemonNotRunning

Exception raised by find_apache_workers() when it cannot locate the Apache daemon process.

class proc.apache.MaybeApacheWorker(proc_tree, stat_fields)

Subclass of ProcessNode that understands Apache workers.

wsgi_process_group

The name of the mod_wsgi process group (a string).

This property makes two assumptions about the way you’ve configured Apache’s mod_wsgi module (which is required to reliably differentiate WSGI workers from regular Apache workers):

  1. The display-name option for the WSGIDaemonProcess directive is used to customize the process names of WSGI daemon processes.
  2. The configured display-name is of the form (wsgi:...). The closing parenthesis is not actually significant because the names of WSGI process groups can be truncated (refer to the documentation of the WSGIDaemonProcess directive for details) and in such cases the trailing parenthesis is truncated as well.

If the first string in cmdline doesn’t start with (wsgi: this property returns an empty string.

class proc.apache.StatsList

Subclass of list that provides some simple statistics.

average

The average value from a list of numbers (a float).

Raises:ValueError when the list is empty.
max

The maximum value from a list of numbers (a number).

Raises:ValueError when the list is empty.
median

The median value from a list of numbers (a number).

Raises:ValueError when the list is empty.
min

The minimum value from a list of numbers (a number).

Raises:ValueError when the list is empty.
proc.apache.find_apache_memory_usage(exe_name='apache2')

Find the memory usage of Apache workers.

Parameters:exe_name – The base name of the Apache executable (a string).
Returns:A tuple of two values:
  1. A StatsList of integers with the resident set size of Apache workers that are not WSGI daemon processes.
  2. A dictionary of key/value pairs, as follows:
    • Each key is a WSGI process group name (see the wsgi_process_group property).
    • Each value is a StatsList of integers with the resident set size of the workers belonging to the WSGI process group.
proc.apache.find_apache_workers(exe_name='apache2')

Find Apache workers in the process tree reported by get_process_tree().

Parameters:exe_name – The base name of the Apache executable (a string).
Returns:A generator of MaybeApacheWorker objects.
Raises:ApacheDaemonNotRunning when the Apache master process cannot be found.

The proc.cron module

The proc.cron module implements graceful termination of cron.

Introduction to cron

The cron daemon is ubiquitous on Linux (UNIX) systems. It’s responsible for executing user defined “jobs” at fixed times, dates or intervals. It’s used for system maintenance tasks, periodic monitoring, production job servers for IT companies around the world, etc.

Problem statement

One thing that has always bothered me about cron is that there is no simple and robust way to stop cron and wait for all running cron jobs to finish what they were doing. You might be wondering why that would be useful...

Imagine you have to perform disruptive system maintenance on a job server that’s responsible for running dozens or even hundreds of important cron jobs. Of course you can just run sudo service cron stop to stop cron from starting new cron jobs, but what do you do about cron jobs that have already started and are still running? Some options:

  1. You just don’t care and start your disruptive maintenance. In this case you can stop reading because what I’m proposing won’t interest you! :-)
  2. You stare at an interactive process monitor like top, htop, etc. until everything that looks like a cron job has disappeared from the screen. Good for you for being diligent about your work, but this is not a nice task to perform! Imagine you have to do it on a handful of job servers before starting disruptive maintenance on shared infrastructure like a central database server...
  3. You automate your work with shell scripts or one-liners that involve grepping the output of ps or similar gymnastics that work most of the time but not quite always... (hi me from a few years ago! :-)

Of course there are dozens (hundreds?) of alternative job schedulers that could make things easier but the thing is that cron is already here and widely used, so migrating a handful of job servers with hundreds of jobs could be way more work than it’s ever going to be worth...

A robust solution: cron-graceful

The proc.cron module implements the command line program cron-graceful which gracefully stops cron daemons. This module builds on top of the proc.tree module as a demonstration of the possibilities of the proc package and as a practical tool that is ready to be used on any Linux system that has Python and cron installed.

The following command prints a usage message:

$ cron-graceful --help

To use the program you simply run it with super user privileges:

$ sudo cron-graceful

Internal documentation of proc.cron

proc.cron.ADDITIONS_SCRIPT_NAME = 'cron-graceful-additions'

The name of the external command that’s run by cron-graceful (a string).

Refer to run_additions() for details about how ADDITIONS_SCRIPT_NAME is used.

exception proc.cron.CronDaemonNotRunning

Exception raised by find_cron_daemon() when it cannot locate the cron daemon process.

proc.cron.cron_graceful(arguments)

Command line interface for the cron-graceful program.

proc.cron.ensure_root_privileges()

Make sure we have root privileges.

proc.cron.find_cron_daemon()

Find the cron daemon process.

Returns:A ProcessNode object.
Raises:CronDaemonNotRunning when the cron daemon process cannot be located.
proc.cron.main()

Wrapper for cron_graceful() that feeds it sys.argv.

proc.cron.parse_arguments(arguments)

Parse the command line arguments.

Parameters:arguments – A list of strings with command line arguments.
Returns:True if a dry run was requested, False otherwise.
proc.cron.run_additions()

Allow local additions to the behavior of cron-graceful.

If a command with the name of ADDITIONS_SCRIPT_NAME exists in the $PATH it will be executed directly after the cron daemon is paused by cron_graceful(). This allows you to inject custom logic into the graceful shutdown process. If the command fails a warning will be logged but the cron-graceful program will continue.

proc.cron.terminate_cron_daemon(cron_daemon)

Terminate the cron daemon.

Parameters:cron_daemon – The ProcessNode of the cron daemon process.
proc.cron.wait_for_processes(processes)

Wait for the given processes to end.

Prints an overview of running processes to the terminal once a second so the user knows what they are waiting for.

This function is not specific to proc.cron at all (it doesn’t even need to know what cron jobs are), it just waits until all of the given processes have ended.

Parameters:processes – A list of ProcessNode objects.

The proc.gpg module

The proc.gpg module provides a smart wrapper for gpg-agent –daemon.

Introduction to gpg-agent

The gpg-agent is used to keep secret keys unlocked in between multiple invocations of the gpg command to avoid retyping the same password. It’s usually started from a script that initializes your graphical session, ensuring that all processes from that point onward inherit the environment variable $GPG_AGENT_INFO. This variable together with the command line option gpg --use-agent enable the use of the gpg-agent daemon.

Note

This applies to GnuPG versions before 2.1, refer to the What’s new in GnuPG 2.1 page for details. In GnuPG 2.1 the use of $GPG_AGENT_INFO was removed because it proved too cumbersome for users :-).

Problem statement

Making sure that $GPG_AGENT_INFO is always set correctly can be a hassle. For example I frequently use SSH to connect between my personal laptop and work laptop and the interactive shells spawned by the SSH daemon have no relation to any graphical session so they don’t have $GPG_AGENT_INFO set.

Of course I can just execute eval $(gpg-agent --daemon) in an interactive session to spawn a gpg-agent on the spot, but that will remain running in the background indefinitely after I close the interactive session, without any simple means of reconnecting.

Initial solution

Somewhere in 2016 I developed a Python script that used proc.core to search for $GPG_AGENT_INFO values in /proc so I could easily reconnect to previously spawned gpg-agents. It mostly worked but it could pick the wrong $GPG_AGENT_INFO when references remained to a crashed or killed agent, so eventually I added checks that ensured the UNIX socket and agent process still existed.

After using this for a while I discovered that when I started a new gpg-agent from an interactive shell spawned by the SSH daemon, I would lose all means of connecting to the agent as soon as I logged out of the interactive shell, even though the agent remained running :-).

Revised solution

After taking a step back I realized that the problem could be approached from a completely different angle: Why not search for an existing gpg-agent process and infer the required $GPG_AGENT_INFO value by inspecting the process using lsof?

The revised solution has worked quite well for me and so I’m now publishing it as the proc.gpg module which implements the command line program with-gpg-agent.

Internal documentation of proc.gpg

proc.gpg.enable_gpg_agent()

Update os.environ with the variables collected by get_gpg_variables().

proc.gpg.find_gpg_agent_info()

Reconstruct $GPG_AGENT_INFO based on a running gpg-agent process.

Returns:A string or None.

This function uses 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.

proc.gpg.get_gpg_variables()

Prepare the environment variable(s) required by the gpg program.

Returns:A dictionary with environment variables.

This function tries to figure out the correct values of two environment variables that are used by the gpg program:

proc.gpg.main()

Wrapper for with_gpg_agent() that feeds it sys.argv.

proc.gpg.parse_arguments(arguments)

Parse the command line arguments.

Parameters:arguments – A list of strings with command line options and/or arguments.
Returns:A list of strings with the positional arguments.
proc.gpg.start_gpg_agent()

Start a new gpg-agent daemon in the background.

proc.gpg.with_gpg_agent(arguments)

Command line interface for the with-gpg-agent program.

The proc.notify module

The proc.notify module implements a headless notify-send program.

The notify-send program can be used to send desktop notifications to the user from the command line. It’s great for use in otherwise non-interactive programs to unobtrusively inform the user about something, for example I use it to show a notification when a system backup is starting and when it has completed.

One problem is that notify-send needs access to a few environment variables from the desktop session in order to deliver its message. The values of these environment variables change every time a desktop session is started. This complicates the use of notify-send from e.g. system daemons and cron jobs (say for an automated backup solution :-).

This module builds on top of the proc.core module as a trivial (but already useful :-) example of how the proc package can be used to search through the environments of all available processes. It looks for the variables in REQUIRED_VARIABLES in the environments of all available processes and uses the values it finds to run the notify-send program. It’s available on the command line as notify-send-headless (which accepts the same arguments as notify-send). Given super-user privileges this should work fine out of the box on any Linux system.

proc.notify.REQUIRED_VARIABLES = ('DBUS_SESSION_BUS_ADDRESS', 'DISPLAY', 'XAUTHORITY')

The names of environment variables required by notify-send (a tuple of strings).

proc.notify.find_graphical_context()

Create a command execution context for the current graphical session.

Returns:A 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.

proc.notify.main()

Command line interface for notify-send-headless.

proc.notify.notify_desktop(body, summary=None, **options)

Python API for headless notify-send commands.

Parameters:
  • body – The notification’s message / details (a string).
  • summary – The notification’s summary / title (a string, defaults to None).
  • options – Any keyword arguments are translated into optional arguments to the notify-send command (see the examples below).

This function is a wrapper around notify-send that knows how to run the notify-send command in the execution environment required to deliver notifications to the current graphical session, even if the current process is not part of a graphical session. Here’s an example:

>>> from proc.notify import notify_desktop
>>> notify_desktop(summary="Battery low", body="Your laptop is about to die!", urgency="critical")