proc: Linux process information interface¶
Welcome to the documentation of the Python package proc version 1.0. The following sections are available:
User documentation¶
The first part of the documentation is the readme which contains general information about the proc package and getting started instructions. Here are the topics discussed in the readme:
proc: Linux process information interface¶
The Python package proc exposes process information available in the Linux
process information pseudo-file system available at /proc
. The proc
package is currently tested on cPython 2.7, 3.5+ and PyPy (2.7). The automated
test suite regularly runs on Ubuntu Linux but other Linux variants (also those
not based on Debian Linux) should work fine. For usage instructions please
refer to the documentation.
Installation¶
The proc package is available on PyPI which means installation should be as simple as:
$ pip install proc
There’s actually a multitude of ways to install Python packages (e.g. the per user site-packages directory, virtual environments or just installing system wide) and I have no intention of getting into that discussion here, so if this intimidates you then read up on your options before returning to these instructions ;-).
Once you’ve installed the proc package head over to the documentation for some examples of how the proc package can be used.
Design choices¶
The proc package was created with the following considerations in mind:
- Completely specialized to Linux
- It parses
/proc
and nothing else ;-). - Fully implemented in Python
- No binary/compiled components, as opposed to psutil which is way more portable but requires a compiler for installation.
- Very well documented
- The documentation should make it easy to get started (as opposed to procfs which I evaluated and eventually gave up on because I had to resort to reading through its source code just to be disappointed in its implementation).
- Robust implementation
- Reading
/proc
is inherently sensitive to race conditions and the proc package takes this into account, in fact the test suite contains a test that creates race conditions in order to verify that they are handled correctly. The API of the proc package hides race conditions as much as possible and where this is not possible the consequences are clearly documented. - Layered API design (where each layer is documented)
Builds higher level abstractions on top of lower level abstractions:
- The proc.unix module
- Defines a simple process class that combines process IDs and common UNIX signals to implement process control primitives like waiting for a process to end and gracefully or forcefully terminating a process.
- The proc.core module
- Builds on top of the
proc.unix
module to provide a simple, fast and easy to use API for the process information available in/proc
. If you’re looking for a simple and/or fast interface to/proc
that does the heavy lifting (parsing) for you then this is what you’re looking for. - The proc.tree module
- Builds on top of the
proc.core
module to provide an in-memory tree data structure that mimics the actual process tree, enabling easy searching and navigation through the process tree. - The proc.apache module
- Builds on top of the
proc.tree
module to implement an easy to use Python API that does metrics collection for monitoring of Apache web server worker memory usage, including support for WSGI process groups. - The proc.cron module
- Implements the command line program
cron-graceful
which gracefully terminates cron daemons. This module builds on top of theproc.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 proc.notify module
- Implements the command line program
notify-send-headless
which can be used to run the programnotify-send
in headless environments like cron jobs and system daemons.
History¶
I’ve been writing shell and Python scripts that parse /proc
for years now
(it seems so temptingly easy when you get started ;-). Sometimes I resorted to
copy/pasting snippets of Python code between personal and work projects because
the code was basically done, just not available in an easy to share form.
Once I started fixing bugs in diverging copies of that code I decided it was time to combine all of the features I’d grown to appreciate into a single well tested and well documented Python package with an easy to use API and share it with the world.
This means that, although I made my first commit on the proc package in March 2015, much of its code has existed for years in various forms.
Similar projects¶
Below are several other Python libraries that expose process information. If the proc package isn’t working out for you consider trying one of these. The summaries are copied and/or paraphrased from the documentation of each package:
- psutil
- A cross-platform library for retrieving information on running processes and system utilization (CPU, memory, disks, network) in Python.
- procpy
- A Python wrapper for the procps library and a module containing higher level classes (with some extensions compared to procps).
- procfs
- Python API for the Linux
/proc
virtual filesystem.
Contact¶
The latest version of proc is available on PyPI and GitHub. The documentation is hosted on Read the Docs and includes a changelog. For bug reports please create an issue on GitHub. If you have questions, suggestions, etc. feel free to send me an e-mail at peter@peterodding.com.
API documentation¶
The following API documentation is automatically generated from the source code:
API documentation¶
This API documentation is based on the source code of version 1.0 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:
- Determine whether a given process ID is still alive (signal 0);
- gracefully (SIGTERM) and forcefully (SIGKILL) terminate processes;
- suspend (SIGSTOP) and resume (SIGCONT) processes.
-
class
proc.unix.
UnixProcess
(**kw)¶ Integration between
executor.process.ControllableProcess
and common UNIX signals.UnixProcess
extendsControllableProcess
which means all of the process manipulation supported byControllableProcess
is also supported byUnixProcess
objects.Here’s an overview of the
UnixProcess
class:Superclass: ControllableProcess
Public methods: kill_helper()
,resume()
,suspend()
andterminate_helper()
Properties: is_running
andpid
When you initialize a
UnixProcess
object you are required to provide a value for thepid
property. You can set the value of thepid
property by passing a keyword argument to the class initializer.-
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 numberEPERM
is raised we don’t have permission to signal the process, which implies that the process is alive. - If an
OSError
exception with error numberESRCH
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 arequired_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. usingsuspend()
).
-
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 callresume()
.
-
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.
Note
Deprecated names
The following alias exists to preserve backwards compatibility, however a DeprecationWarning
is triggered when it is accessed, because this alias will be removed in a future release.
-
proc.core.
num_race_conditions
¶ Alias for
proc.core.NUM_RACE_CONDITIONS
.
-
proc.core.
NUM_RACE_CONDITIONS
= {'cmdline': 0, 'environ': 0, 'exe': 0, 'stat': 0, 'status': 0}¶ A dictionary with string keys and integer values that’s used to keep global counters that track the number of detected race conditions. This is only useful for the test suite, because it intentionally creates race conditions to verify that they are properly handled.
-
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 usingfind_processes()
andProcess.from_path()
. You shouldn’t be using theProcess
constructor directly unless you know what you’re doing.The
Process
class extendsUnixProcess
which means all of the process manipulation supported byUnixProcess
is also supported byProcess
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 byProcess
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 :-).
Here’s an overview of the
Process
class:Superclass: UnixProcess
Special methods: __init__()
and__repr__()
Properties: cmdline
,comm
,command_line
,cwd
,environ
,exe
,exe_name
,exe_path
,group
,group_ids
,is_alive
,is_running
,pgrp
,pid
,ppid
,rss
,runtime
,session
,starttime
,state
,status_fields
,user
,user_ids
andvsize
-
__init__
(proc_tree, stat_fields)¶ Initialize a
Process
object.Parameters: - proc_tree – The absolute pathname of the numerical subdirectory
of
/proc
on which the process information is based (a string). - stat_fields – The tokenized fields from
/proc/[pid]/stat
(a list of strings).
- proc_tree – The absolute pathname of the numerical subdirectory
of
-
__repr__
()¶ Create a human readable representation of a process information object.
Returns: A string containing what looks like a Process
constructor, but showing public properties instead of internal properties.
-
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 alazy_property
. This property’s value is computed once (the first time it is accessed) and the result is cached.- This property is parsed from the contents of
-
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 ofproc.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 toconsole-kit-dae
. If you need a reliable way to find the executable name consider using thecmdline
and/orexe
properties.Note
The
comm
property is alazy_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).
-
cwd
¶ The working directory of the process (a string or
None
).Availability:
- This property is constructed by dereferencing the symbolic link
/proc/[pid]/cwd
each time the property is referenced (because the working directory may change at any time). - 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.
- This property is constructed by dereferencing the symbolic link
-
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 alazy_property
. This property’s value is computed once (the first time it is accessed) and the result is cached.- This property is parsed from the contents of
-
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 alazy_property
. This property’s value is computed once (the first time it is accessed) and the result is cached.- This property is constructed by dereferencing the symbolic link
-
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:
- If
exe_path
is available then the base name of this pathname is returned. - 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).
- Pro: As long as
- If both of the methods above fail
comm
is returned.
Note
The
exe_name
property is alazy_property
. This property’s value is computed once (the first time it is accessed) and the result is cached.- If
-
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:
- 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.
- 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.
- If both of the methods above fail an empty string is returned.
Note
The
exe_path
property is alazy_property
. This property’s value is computed once (the first time it is accessed) and the result is cached.- If
-
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 constructProcess
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 ifgroup_ids
is unavailable orgid_to_name()
fails.Note
The
group
property is alazy_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 ifstatus_fields
is unavailable.Note
The
group_ids
property is alazy_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()
andkill()
.
-
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 alazy_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 alazy_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 asFalse
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 alazy_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 alazy_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 alazy_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 alazy_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 alazy_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
andgroup_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 alazy_property
. This property’s value is computed once (the first time it is accessed) and the result is cached.- This property is parsed from the contents of
-
user
¶ The username of the real user ID (a string).
Availability: Refer to
user_ids
.None
is returned ifuser_ids
is unavailable oruid_to_name()
fails.Note
The
user
property is alazy_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 ifstatus_fields
is unavailable.Note
The
user_ids
property is alazy_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 alazy_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.
-
__enter__
()¶ Enter the context (does nothing).
-
__exit__
(exc_type=None, exc_value=None, traceback=None)¶ Log and swallow exceptions and count race conditions.
-
__init__
(key, action)¶ Initialize a
ProtectedAccess
object.Parameters: - key – The key in
NUM_RACE_CONDITIONS
(a string). - action – A verb followed by a noun describing what kind of access is being protected (a string)
- key – The key in
-
-
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 ofProcess
).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
ifgrp.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
ifpwd.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 ofProcess
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:- To construct a tree you use
get_process_tree()
. This function connects all of the nodes in the tree before returning the root node (this node represents init). - To navigate the tree you can use the
parent
,children
,grandchildren
anddescendants
properties. - If you’re looking for specific descendant processes consider using
find()
orfind_all()
.
Here’s an overview of the
ProcessNode
class:Superclass: Process
Public methods: find()
andfind_all()
Properties: children
,descendants
,grandchildren
andparent
You can set the value of the
parent
property by passing a keyword argument to the class initializer.-
children
¶ A list of
ProcessNode
objects with the children of this process.Note
The
children
property is alazy_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 orNone
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 indescendants
will be searched, otherwise only the processes inchildren
are searched (the default).
Returns: A generator of
ProcessNode
objects.- pid – If this parameter is given, only processes with the given
-
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 awritable_property
. You can change the value of this property using normal attribute assignment syntax.
- To construct a tree you use
-
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 ofProcessNode
).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:
- The
proc.apache
module - The
proc.cron
module - The
proc.gpg
module - The
proc.notify
module
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.Here’s an overview of the
MaybeApacheWorker
class:Superclass: ProcessNode
Properties: wsgi_process_group
-
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):
- The display-name option for the WSGIDaemonProcess directive is used to customize the process names of WSGI daemon processes.
- 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: - A
StatsList
of integers with the resident set size of Apache workers that are not WSGI daemon processes. - 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.
- Each key is a WSGI process group name (see the
- A
-
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:
- 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! :-)
- 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…
- 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 howADDITIONS_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 itsys.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 bycron_graceful()
. This allows you to inject custom logic into the graceful shutdown process. If the command fails a warning will be logged but thecron-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.
LAUNCH_TIMEOUT
= 30¶ The timeout for a newly launched GPG agent daemon to come online (a number).
This gives the maximum number of seconds to wait for a newly launched GPG agent daemon to come online.
-
proc.gpg.
NEW_STYLE_SOCKET
= '~/.gnupg/S.gpg-agent'¶ The location of the GPG agent socket for GnuPG 2.1 and newer (a string).
-
proc.gpg.
enable_gpg_agent
(**options)¶ Update
os.environ
with the variables collected byget_gpg_variables()
.Parameters: options – Optional keyword arguments for get_gpg_variables()
.
-
proc.gpg.
find_fixed_agent_socket
()¶ Search for a GPG agent UNIX socket in one of the “fixed locations”.
Returns: The pathname of the found socket file (a string) or None
.Two locations are searched, in the given order (the first that is found is returned):
- Starting from GnuPG 2.1.13 the location
/run/user/$UID/gnupg/S.gpg-agent
is used (only when the directory/run/user/$UID
exists). - GnuPG 2.1 removed the
$GPG_AGENT_INFO
related code and switched to the fixed location~/.gnupg/S.gpg-agent
.
- Starting from GnuPG 2.1.13 the location
-
proc.gpg.
find_gpg_agent_info
()¶ Reconstruct
$GPG_AGENT_INFO
based on a runninggpg-agent
process.Returns: A string or None
.This function uses
find_processes()
to search forgpg-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.
find_open_unix_sockets
(pid)¶ Find the pathnames of any UNIX sockets held open by a process.
Parameters: pid – The process ID (a number). Returns: A generator of pathnames (strings).
-
proc.gpg.
get_gpg_variables
(timeout=30)¶ Prepare the environment variable(s) required by the gpg program.
Parameters: timeout – The timeout for a newly launched GPG agent daemon to start (a number, defaults to LAUNCH_TIMEOUT
).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:
$GPG_AGENT_INFO
is generated usingfind_gpg_agent_info()
, spawning a new agent iffind_gpg_agent_info()
initially returnsNone
.$GPG_TTY
is generated using /usr/bin/tty.
-
proc.gpg.
have_agent_program
()¶ Check whether the
gpg-agent
program is installed.Returns: True
when thegpg-agent
program is available on the$PATH
,False
otherwise.
-
proc.gpg.
have_valid_agent_info
()¶ Check if the existing
$GPG_AGENT_INFO
value is usable.Returns: True
if the existing$GPG_AGENT_INFO
is valid,False
otherwise.This function parses the
$GPG_AGENT_INFO
environment variable and validates the resulting UNIX socket filename usingvalidate_unix_socket()
.
-
proc.gpg.
main
()¶ Wrapper for
with_gpg_agent()
that feeds itsys.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
(timeout=30)¶ Start a new gpg-agent daemon in the background.
Parameters: timeout – The timeout for the newly launched GPG agent daemon to start (a number, defaults to LAUNCH_TIMEOUT
).Returns: The return value of find_gpg_agent_info()
.
-
proc.gpg.
validate_unix_socket
(pathname)¶ Check whether a filename points to a writable UNIX socket.
Parameters: pathname – The pathname of the socket file (a string). Returns: True
if the socket exists and is writable,False
in all other cases.
-
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.
Introduction to notify-send¶
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 (see my rsync-system-backup package).
Problems using notify-send¶
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 :-).
The notify-send-headless program¶
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.
The with-gui-environment program¶
This module also implements the with-gui-environment
program which uses the
same algorithm as notify-send-headless
to identify the desktop session but
instead of running the notify-send command it can execute arbitrary commands.
My personal use case for the with-gui-environment
program is to execute
programs like xrandr in my desktop session from custom udev rules (which by
default run commands as root, disconnected from the desktop session).
-
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 thenotify-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")
-
proc.notify.
with_gui_environment
()¶ Command line interface for
with-gui-environment
.
Change log¶
The change log lists notable changes to the project:
Changelog¶
The purpose of this document is to list all of the notable changes to this project. The format was inspired by Keep a Changelog. This project adheres to semantic versioning.
- Release 1.0 (2020-04-26)
- Release 0.17 (2018-06-27)
- Release 0.16 (2018-06-21)
- Release 0.15 (2018-06-21)
- Release 0.14 (2017-06-24)
- Release 0.13 (2017-06-24)
- Release 0.12 (2017-02-14)
- Release 0.11 (2017-01-24)
- Release 0.10.1 (2016-11-13)
- Release 0.10 (2016-11-12)
- Release 0.9.1 (2016-06-13)
- Release 0.9 (2016-06-01)
- Release 0.8.5 (2016-05-27)
- Release 0.8.4 (2016-04-22)
- Release 0.8.3 (2016-04-21)
- Release 0.8.2 (2016-04-21)
- Release 0.8.1 (2016-04-21)
- Release 0.8 (2016-04-21)
- Release 0.7 (2016-01-29)
- Release 0.6 (2016-01-28)
- Release 0.5.1 (2015-11-19)
- Release 0.5 (2015-11-19)
- Release 0.4.1 (2015-11-10)
- Release 0.4 (2015-11-10)
- Release 0.3 (2015-09-25)
- Release 0.2.3 (2015-09-25)
- Release 0.2.2 (2015-06-26)
- Release 0.2.1 (2015-04-16)
- Release 0.2 (2015-03-30)
- Release 0.1.1 (2015-03-30)
- Release 0.1 (2015-03-29)
Release 1.0 (2020-04-26)¶
- Merged pull request #2 which enables Python 3.7+ compatibility by changing
the executor integration to stop using the old
async
option and start using the newasynchronous
option (given thatasync
became a keyword in Python 3.7). - Updated Python compatibility:
- Python 3.6, 3.7 and 3.8 are now tested and supported.
- Python 2.6 and 3.4 are no longer supported.
- Defined
__all__
for existing Python modules. - Fixed humanfriendly deprecation warnings and bumped dependencies that went through the same process.
- Fixed all Sphinx broken references and configured Sphinx to report broken references as errors instead of warnings (to prevent broken references from piling up in the future).
- Various Travis CI fixes that didn’t affect published code.
Release 0.17 (2018-06-27)¶
Added with-gui-environment
program (a generalization of notify-send-headless
).
Release 0.16 (2018-06-21)¶
Expose the value of /proc/[pid]/cwd
on Process
objects as the new
Process.cwd
property.
Release 0.15 (2018-06-21)¶
- Changes related to the
proc.gpg
module and thewith-gpg-agent
program:- Use existing
$GPG_AGENT_INFO
values when available and validated. - Let the operator know when starting a new GPG agent daemon (through logging).
- Check if gpg-agent` is installed before trying to run
gpg-agent --daemon
. - Added support for GPG agent sockets in
/run/user/$UID
(GnuPG >= 2.1.13).- This incompatibility came to light when I upgraded my laptop from Ubuntu 16.04 to 18.04.
- Fixed hanging Travis CI builds caused by
gpg-agent --daemon
not detaching properly when the standard error stream is redirected.- This incompatibility was exposed by Travis CI switching from Ubuntu 12.04 to 14.04.
- Fixed race condition in
find_gpg_agent_info()
raisingAttributeError
.
- Use existing
- Changes related to the documentation:
- Added this change log to the documentation (with a link in the readme).
- Integrated the
property_manager.sphinx
module (to generate boilerplate documentation). - Fixed intersphinx mapping in documentation configuration.
- Changed HTML theme from default to nature (a wide layout).
- Include documentation in source distributions (
MANIFEST.in
).
- And then some miscellaneous changes:
- Fixed Apache WSGI configuration on Travis CI.
- This test suite incompatibility was exposed by Travis CI switching from Ubuntu 12.04 to 14.04.
- Restored Python 2.6 compatibility in the test suite (concerning pytest version).
- Added license=MIT key to
setup.py
script. - Bumped the copyright to 2018.
- Fixed Apache WSGI configuration on Travis CI.
Release 0.14 (2017-06-24)¶
Swallow exceptions in the notify_desktop()
function.
This change is technically backwards incompatible but I consider it the more
sane behavior; I had just simply never seen notify-send
fail until the
failure which prompted this release 😇.
Release 0.13 (2017-06-24)¶
- Provide proper compatibility with GnuPG >= 2.1 which uses the fixed
location
~/.gnupg/S.gpg-agent
for the agent socket. - Bug fix for systemd incompatibility in test suite.
- Moved test helpers to the
humanfriendly.testing
module.
Release 0.12 (2017-02-14)¶
Improved robustness of Apache master process selection.
Release 0.11 (2017-01-24)¶
Added with-gpg-agent
program: A smart wrapper for the gpg-agent
--daemon
functionality that makes sure the environment variable
$GPG_AGENT_INFO
is always set correctly.
Release 0.10.1 (2016-11-13)¶
Fixed broken reStructuredText syntax in README (which breaks the rich text rendering on the Python Package Index).
Release 0.10 (2016-11-12)¶
- Several improvements to
cron-graceful
:- Improved cron daemon termination.
- Improved user friendliness of output.
- Avoid useless log output noise.
- Start publishing wheel distributions.
- Explicitly signal skipped tests (when possible).
- Refactored internal project infrastructure such as the makefile, setup script and Travis CI build configuration.
Release 0.9.1 (2016-06-13)¶
Silenced another race condition (ESRCH
instead of ENOENT
).
This is one of those things that you only observe after running a package like proc from a periodic task (cron job) that runs every minute on a dozen servers for a couple of weeks 🙂. The error condition was -correctly- being swallowed already, but it was more noisy than it needed to be.
Release 0.9 (2016-06-01)¶
Refactored the separation of concerns between the executor and proc packages.
Please refer to the commit message of the other side of this refactoring (executor#b484912bb33) for details about the how and why of this fairly involved refactoring 🙂.
Release 0.8.5 (2016-05-27)¶
Demote race condition log messages from WARNING to DEBUG level.
Reasoning: Race condition log messages are so frequent that they become noise, drowning out other more important log messages, so I decided to make them less noisy 🙂.
Fixed a confusing typo in the API docs, left over from a sentence that was (half) reformulated.
Noted a future improvement in the documentation: Generalized
notify-send-headless
functionality.
Release 0.8.4 (2016-04-22)¶
- Improved
notify-send-headless
documentation. - Improved test coverage by mocking external dependencies.
Release 0.8.3 (2016-04-21)¶
- Increase
cron-graceful[-additions]
test coverage. - Avoid duplicate builds on Travis CI.
- Test suite bug fix.
Release 0.8.2 (2016-04-21)¶
Increase test coverage (somewhat of a cop-out 🙂).
Release 0.8.1 (2016-04-21)¶
Now including an upstream bug fix to make the previous release work :-(.
Release 0.8 (2016-04-21)¶
- Try to make
notify-send-headless
foolproof. - Document supported Python implementations in
setup.py
. - Enabled Python 3.5 tests on Travis CI, documented Python 3.5 support.
Release 0.7 (2016-01-29)¶
Expose the real user/group names of processes.
Release 0.6 (2016-01-28)¶
- Expose
/proc/[pid]/status
(UID/GID information considered useful 🙂). - Changed
Process.from_pid()
to useProcess.from_path()
. - Re-ordered fields of
Process
class alphabetically. - Switched to flake8 for code style checks, fixed code style warnings pointed out by flake8.
- Updated
tox.ini
to includepy35
and options for flake8 and pytest. - Improved test coverage.
- Refactored the makefile.
Release 0.5.1 (2015-11-19)¶
Bug fix: Restored Python 2.6 compatibility (regarding the __exit__()
calling convention).
Release 0.5 (2015-11-19)¶
- Extracted
/proc/uptime
parsing to a separate function. - Generalized error handling (of permission errors and race conditions).
- Expose
/proc/[pid]/environ
(also:notify-send-headless
🙂).
Release 0.4.1 (2015-11-10)¶
Two minor bug fixes:
- Added a
Process.command_line
toProcess.cmdline
alias (to improve the compatibility with the process management code that’s shared between the executor and proc packages). - Improved the documentation after refactorings in the 0.4 release broke some references.
Release 0.4 (2015-11-10)¶
- Improved process management (shared between the executor and proc packages).
- Switched from cached-property to property-manager.
Release 0.3 (2015-09-25)¶
Make the cron-graceful
command “repeatable” (as in, running it twice will
not report a CronDaemonNotRunning
exception to the terminal but will
just mention that cron is not running and then exit gracefully).
Release 0.2.3 (2015-09-25)¶
- Bug fix: Make sure interactive spinners restore cursor visibility.
- Refactored
setup.py
script, improved trove classifiers. - Removed redundant
:py:
prefixes from reStructuredText fragments. - Bug fix for
make coverage
target inMakefile
.
Release 0.2.2 (2015-06-26)¶
Bug fix: Avoid KeyError
exception during tree construction.
Release 0.2.1 (2015-04-16)¶
- Fixed incompatibility with cached-property 1.1.0 (removed
__slots__
usage). - Fixed last remaining Python 2.6 incompatibility (in test suite).
Release 0.2 (2015-03-30)¶
- Added an example
proc.apache
module that monitors Apache worker memory usage. - Made the test suite more robust and increased test coverage.
Release 0.1.1 (2015-03-30)¶
- Enable callers to override object type for
proc.tree.get_process_tree()
. - Started documenting similar projects in the readme.
Release 0.1 (2015-03-29)¶
This was the initial commit and release. The “History” section of the readme provides a bit more context:
I’ve been writing shell and Python scripts that parse /proc
for years now
(it seems so temptingly easy when you get started 😉). Sometimes I resorted to
copy/pasting snippets of Python code between personal and work projects because
the code was basically done, just not available in an easy to share form.
Once I started fixing bugs in diverging copies of that code I decided it was time to combine all of the features I’d grown to appreciate into a single well tested and well documented Python package with an easy to use API and share it with the world.
This means that, although I made my first commit on the proc package in March 2015, much of its code has existed for years in various forms.