#!/usr/bin/env python
"""
  gunicorn_status - A munin plugin for Linux to monitor gunicorn processes

  Copyright (C) 2012 Azavea, Inc.
  Author: Andrew Jennings

  Like Munin, this plugin is licensed under the GNU GPL v2 license
  http://www.opensource.org/licenses/GPL-2.0

  If you've put your gunicorn pid somewhere other than the 
  default /var/run/gunicorn.pid, you can add a section like
  this to your munin-node's plugin configuration:
 
  [gunicorn_*]
  env.gunicorn_pid_path [path to your gunicorn pid]

  This plugin supports the following munin configuration parameters:
  #%# family=auto contrib
  #%# capabilities=autoconf

"""

import sys, os, re
from subprocess import check_output
from time import sleep

# set path to your gunicorn pid
try:
    GUNICORN_PID_PATH = os.environ['gunicorn_pid_path']
except:
    GUNICORN_PID_PATH = "/var/run/gunicorn.pid"

class GunicornStatus():
    master_pid = ''
    """
    The gunicorn master process pid, as a string
    """

    worker_pids = ''
    """
    The list of gunicorn processes as strings
    """

    def __init__(self):
        try:
            self._get_master_pid()
            self._get_worker_pids(self.master_pid)
        except:
            sys.exit("Couldn't read gunicorn pid")

    def print_total_workers(self):
        print ('total_workers.value %d' % self._worker_count())

    def print_idle_workers(self):
        print ('idle_workers.value %d' % self._idle_worker_count())


    def _get_master_pid(self):
        master_pid_file = open(GUNICORN_PID_PATH)
        self.master_pid = master_pid_file.read().rstrip()
        master_pid_file.close()    

    def _get_worker_pids(self, master_pid):
        children = check_output(
            ['ps', '--ppid', master_pid, '-o', 'pid', '--no-headers'])
        self.worker_pids = [pid.strip() for pid in children.splitlines()]

    def _worker_count(self):
        return len(self.worker_pids)

    def _idle_worker_count(self):
        idle_workers = 0
        for pid in self.worker_pids:
            before = self._cpu_time(pid)
            sleep(0.50)
            after = self._cpu_time(pid)
            if before == after:
                idle_workers += 1
        return idle_workers

    def _cpu_time(self, pid):
        proc_info = open('/proc/%s/stat' % pid).read()
        proc_info = [field.rstrip() for field in proc_info.split()]
        user_time = int(proc_info[13].rstrip())
        kernel_time = int(proc_info[14].rstrip())
        return user_time + kernel_time

def print_config():
    instance = None
    name = os.path.basename(sys.argv[0])
    if name != "gunicorn_status":
        for r in ("^gunicorn_(.*?)_status$", "^gunicorn_status_(.*?)$"):
            m = re.match(r, name, re.IGNORECASE)
            if m:
                instance = m.group(1)
                break
    graph_title = "graph_title Gunicorn - Status"
    if instance:
        graph_title = "%s - %s" % (graph_title, instance)
    print graph_title
    print "graph_args -l 0"
    print "graph_vlabel Number of workers"
    print "graph_category appserver"
    print "total_workers.label Total Workers"
    print "idle_workers.label Idle Workers"

if __name__ == "__main__":
    if len(sys.argv) == 2 and sys.argv[1] == 'config':
        print_config()
    elif len(sys.argv) == 2 and sys.argv[1] == 'autoconf':
        try:
            open(GUNICORN_PID_PATH).close()
            print "yes"
        except:
            print "no"
    # Some docs say it'll be called with fetch, some say no arg at all
    elif len(sys.argv) == 1 or (len(sys.argv) == 2 and sys.argv[1] == 'fetch'):
        status = GunicornStatus()
        try:
            status.print_total_workers()
            status.print_idle_workers()
        except:
            sys.exit("Couldn't retrieve gunicorn status")
