Create a gist now

Instantly share code, notes, and snippets.

Quick script for verifying that daemons have been restarted after a config change
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Check if all processes matching a name were started after a config
file's mtime.
--snip--
Tested under Python 2.7 and 3.4
"""
from __future__ import (absolute_import, division, print_function,
with_statement, unicode_literals)
__author__ = "Stephan Sokolow (deitarion/SSokolow)"
__appname__ = "restarted_since.py"
__version__ = "0.1"
__license__ = "MIT"
import logging, os, subprocess, sys
log = logging.getLogger(__name__)
# Ticks per second
tick_rate = os.sysconf(os.sysconf_names['SC_CLK_TCK'])
def getpids(procname):
"""Simple abstraction for a couple of ways to get PIDs from names"""
try:
pidstr = subprocess.check_output(['pidof', procname])
except subprocess.CalledProcessError:
pidstr = subprocess.check_output(['pgrep', procname])
return [int(x.strip()) for x in pidstr.strip().split()]
def get_start_time(pid):
"""Given a PID, look up the start time in seconds since the epoch
See `man 5 proc` for references used to implement.
Passes through IOError with errno=ENOENT if the process went away.
"""
# Read process start time as clock ticks since system boot
with open("/proc/{}/stat".format(pid), 'rb') as fobj:
proc_start_ticks_since_boot = int(fobj.read().strip().split()[21])
# Read system boot time as seconds since the epoch
sys_boot_time = None
with open("/proc/stat", 'rb') as fobj:
for line in fobj:
key, val = line.split(None, 1)
if key == b'btime':
sys_boot_time = int(val)
assert sys_boot_time
# Translate process start time from ticks to seconds
proc_start_secs_since_boot = proc_start_ticks_since_boot / tick_rate
# Translate process start time from since-boot to since-epoch
proc_start_secs_since_epoch = proc_start_secs_since_boot + sys_boot_time
return proc_start_secs_since_epoch
def get_mtime(path):
"""Thin wrapper for grabbing a file's mtime"""
assert os.path.exists(path), "Path does not exist: {}".format(path)
return os.stat(path).st_mtime
def main():
"""The main entry point, compatible with setuptools entry points."""
# If we're running on Python 2, take responsibility for preventing
# output from causing UnicodeEncodeErrors. (Done here so it should only
# happen when not being imported by some other program.)
if sys.version_info.major < 3:
reload(sys)
sys.setdefaultencoding('utf-8') # pylint: disable=no-member
from argparse import ArgumentParser
parser = ArgumentParser(
description=__doc__.replace('\r\n', '\n').split('\n--snip--\n')[0])
parser.add_argument('--version', action='version',
version="%%(prog)s v%s" % __version__)
parser.add_argument('-v', '--verbose', action="count",
default=2, help="Increase the verbosity. Use twice for extra effect")
parser.add_argument('-q', '--quiet', action="count",
default=0, help="Decrease the verbosity. Use twice for extra effect")
parser.add_argument('process_name', help="Process name to be checked")
parser.add_argument('config_file', help="UNIX timestamp to compare")
args = parser.parse_args()
# Set up clean logging to stderr
log_levels = [logging.CRITICAL, logging.ERROR, logging.WARNING,
logging.INFO, logging.DEBUG]
args.verbose = min(args.verbose - args.quiet, len(log_levels) - 1)
args.verbose = max(args.verbose, 0)
logging.basicConfig(level=log_levels[args.verbose],
format='%(levelname)s: %(message)s')
earliest_started = min(get_start_time(i)
for i in getpids(args.process_name))
if earliest_started > get_mtime(args.config_file):
sys.exit(0)
else:
sys.exit(1)
if __name__ == '__main__':
main()
# vim: set sw=4 sts=4 expandtab :
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment