Difference between revisions of "Job queue idle.py"

From MythTV Official Wiki
Jump to: navigation, search
m (Add missing colon (syntax error))
 
(4 intermediate revisions by 2 users not shown)
Line 1: Line 1:
 
{{Wrongtitle|job_queue_idle.py}}
 
{{Wrongtitle|job_queue_idle.py}}
 
{{Script info
 
{{Script info
|author=Mike Dean
+
|author=various
 
|short=Tool to check job queue status, to allow mythjobqueue host and backend shutdown
 
|short=Tool to check job queue status, to allow mythjobqueue host and backend shutdown
 
|long=A python script that checks the job queue status. Its exit status indicates the number of jobs available for processing on this host. A zero exit status (success) means the job queue is idle and may be terminated.
 
|long=A python script that checks the job queue status. Its exit status indicates the number of jobs available for processing on this host. A zero exit status (success) means the job queue is idle and may be terminated.
Line 9: Line 9:
 
This python script checks to see if a job queue server is idle. Its exit status indicates the number of jobs available for processing on this host. A zero exit status (success) means the job queue is idle and may be terminated.
 
This python script checks to see if a job queue server is idle. Its exit status indicates the number of jobs available for processing on this host. A zero exit status (success) means the job queue is idle and may be terminated.
  
It can be used in a cron job or in a loop in a script that starts and backgrounds mythjobqueue to determine if there are any jobs currently being processed by this host's mythjobqueue or available for this host's mythjobqueue to process. If no jobs are in progress or available for processing, mythjobqueue may be shut down so that the master backend may automatically shut itself down when idle.
+
It can be used in a cron job or in a loop in a script that starts and backgrounds mythjobqueue to determine if there are any jobs currently being processed by this host's mythjobqueue or available for this host's mythjobqueue to process. If no jobs are in progress or available for processing, mythjobqueue, or even the computer running mythjobqueue, may be shut down.
  
 
{{Python|job_queue_idle.py|
 
{{Python|job_queue_idle.py|
Line 19: Line 19:
 
# Purpose
 
# Purpose
 
#  This python script checks to see if a job queue server is idle.
 
#  This python script checks to see if a job queue server is idle.
It can be used in a cron job or in a loop in a script that starts
+
If no jobs are listed for, or can be run by, the current host, the
and backgrounds mythjobqueue to determine if there are any jobs
+
script will return 0, indicating mythjobqueue can be safely
currently being processed by this host's mythjobqueue or available
+
terminated, allowing the master backend to shut itself down.
#   for this host's mythjobqueue to process. If no jobs are in
+
#  Otherwise, the script returns the total number of jobs capable of
progress or available for processing, mythjobqueue may be shut down
+
#  being run by the current host.
so that the master backend may automatically shut itself down when
+
#
idle.
+
It can also be run in daemon mode, in which case it backgrounds
 +
itself, and proceeds to automatically manage the start and stop of
 +
mythjobqueue as needed.
 
#---------------------------
 
#---------------------------
  
from MythTV import MythDB, Job
+
from MythTV import MythDB, Job, MythDBError
 
from optparse import OptionParser
 
from optparse import OptionParser
 +
import subprocess
 +
import time
 
import sys
 
import sys
 +
import os
 +
 +
DB = None
 +
LEVEL = None
  
 
class VERBOSE_LEVEL:
 
class VERBOSE_LEVEL:
Line 38: Line 46:
  
 
def verbose(level, *messages):
 
def verbose(level, *messages):
     if (verbose_level >= level):
+
     if (LEVEL >= level):
 
         print ''.join(map(str,messages))
 
         print ''.join(map(str,messages))
  
def main():
+
def get_pid():
     parser = OptionParser(usage="usage: %prog [options]")
+
     pid = None
     parser.set_defaults(verbose_level=VERBOSE_LEVEL.DEFAULT)
+
    ps = subprocess.Popen(['ps', 'ax'], stdout=-1)
     parser.add_option('-v', '--verbose', action='store', type='int',
+
     ps.wait()
                      dest='verbose_level', help='Verbosity level')
+
     for line in ps.stdout.read().split('\n'):
    opts, args = parser.parse_args();
+
        if 'mythjobqueue' in line:
 +
            pid = int(line.strip().split(' ')[0])
 +
            break
 +
    return pid
 +
 
 +
def run_jobqueue():
 +
    if subprocess.call(['mythjobqueue', '--daemon']):
 +
        verbose(VERBOSE_LEVEL.DEFAULT, "Daemon failed to run mythjobqueue")
 +
        sys.exit(1)
 +
    return get_pid()
  
    global verbose_level;
+
def term_jobqueue(pid):
     verbose_level = opts.verbose_level
+
     subprocess.call(['kill', str(pid)])
  
    DB = MythDB()
+
def get_job_count():
 
     currenthost = DB.gethostname()
 
     currenthost = DB.gethostname()
 
     verbose(VERBOSE_LEVEL.DEBUG, 'Checking job queue status for host ',
 
     verbose(VERBOSE_LEVEL.DEBUG, 'Checking job queue status for host ',
Line 83: Line 100:
 
         verbose(VERBOSE_LEVEL.DEFAULT, "mythjobqueue is idle.")
 
         verbose(VERBOSE_LEVEL.DEFAULT, "mythjobqueue is idle.")
  
     sys.exit(jobCount)
+
     return jobCount
 +
 
 +
def run_daemon():
 +
    # fork once
 +
    try:
 +
        pid = os.fork()
 +
        if pid:
 +
            # parent, exit
 +
            sys.exit(0)
 +
    except OSError, e:
 +
        verbose(VERBOSE_LEVEL.DEFAULT, "Daemon failed fork to background.")
 +
        sys.exit(1)
  
 +
    os.chdir("/")
 +
    os.setsid()
 +
    os.umask(0)
 +
 +
    # fork twice
 +
    try:
 +
        pid = os.fork()
 +
        if pid:
 +
            # parent, exit
 +
            sys.exit(0)
 +
    except OSError, e:
 +
        verbose(VERBOSE_LEVEL.DEFAULT, "Daemon failed fork to background.")
 +
        sys.exit(1)
 +
 +
    pid = get_pid()
 +
    if pid is None:
 +
        pid = run_jobqueue()
 +
 +
    running = True
 +
    while True:
 +
        try:
 +
            count = get_job_count()
 +
            if running and count==0:
 +
                term_jobqueue(pid)
 +
            elif not running and count:
 +
                pid = run_jobqueue()
 +
        except MythDBError:
 +
            # database is inaccessible
 +
            # wait for bindings to automatically reconnect
 +
            pass
 +
        time.sleep(60)
 +
 +
       
 
if __name__ == '__main__':
 
if __name__ == '__main__':
     main()
+
     parser = OptionParser(usage="usage: %prog [options]")
</pre>}}
 
  
Since the above is enough to query the job queue, but not actually do anything about it, I wrote the following wrapper which uses the above and then starts and stops mythjobqueue (mythjobqueue prevents the backend from going idle, so it has to actually stopped when the queue is empty if you want your backend to be able to idle and sleep) when there is something to be processed:
+
    parser.set_defaults(verbose_level=VERBOSE_LEVEL.DEFAULT)
 +
    parser.add_option('-v', '--verbose', action='store', type='int',
 +
                      dest='verbose_level', default=VERBOSE_LEVEL.DEFAULT,
 +
                      help='Verbosity level')
 +
    parser.add_option('-d', '--daemonize', action='store_true',
 +
                      dest='daemon', default=False,
 +
                      help='Daemonize and manage mythjobqueue')
  
<pre>
+
    opts, args = parser.parse_args()
#!/bin/bash -e
 
  
export PATH=$PATH:~/bin
+
    LEVEL = opts.verbose_level
  
jobqueue_pid=""
+
    try:
 +
        DB = MythDB()
 +
    except MythDBError:
 +
        verbose(VERBOSE_LEVEL.VERBOSE, 'Failed to connect to the database server.')
 +
        sys.exit(int(opts.daemon))
  
while true; do
+
     if opts.daemon:
    num_jobs=0
+
         run_daemon()
     if ! job_queue_idle.py; then
+
        sys.exit(1)
         num_jobs=${PIPESTATUS[0]}
 
    fi
 
  
    if [ $num_jobs -gt 0 ]; then
+
     else:
        if [ -z "$jobqueue_pid" ]; then
+
         sys.exit(get_job_count())
            mythjobqueue &
+
</pre>}}
    jobqueue_pid=$!
 
fi
 
     else
 
         if [ -n "$jobqueue_pid" ]; then
 
    kill $jobqueue_pid
 
            jobqueue_pid=""
 
fi
 
    fi
 
    sleep 60
 
done
 
</pre>
 
 
 
I also patched the above job_queue_idle.py with the following to deal with the backend sleeping and the database not being available.  Basically, when that happens job_queue_idle.py returns 0 (queue is empty) which seems appropriate since the backend is not running to have the queue processed anyway.  I think I recall that the backend won't even idle if the queue is not empty anyway, so if it is sleeping, that implies the queue is empty and therefore returning 0 if the backend is sleeping is appropriate.
 
  
<pre>
 
--- a/job_queue_idle.py 2011-05-29 13:52:40.000000000 -0400
 
+++ b/job_queue_idle.py 2011-05-26 12:33:08.209777516 -0400
 
@@ -36,7 +36,12 @@
 
    global verbose_level;
 
    verbose_level = opts.verbose_level
 
 
-    DB = MythDB()
 
+    try:
 
+        DB = MythDB()
 
+    except:
 
+        verbose(VERBOSE_LEVEL.DEBUG, 'Failed to connect to the database server')
 
+        sys.exit(0)
 
+
 
    currenthost = DB.gethostname()
 
    verbose(VERBOSE_LEVEL.DEBUG, 'Checking job queue status for host ',
 
            currenthost)
 
</pre>
 
 
[[Category:Python Scripts]]
 
[[Category:Python Scripts]]
 +
[[Category:Management Scripts]]

Latest revision as of 03:59, 28 April 2015

Important.png Note: The correct title of this article is job_queue_idle.py. It appears incorrectly here due to technical restrictions.


Author various
Description A python script that checks the job queue status. Its exit status indicates the number of jobs available for processing on this host. A zero exit status (success) means the job queue is idle and may be terminated.
Supports Version24.png  


This python script checks to see if a job queue server is idle. Its exit status indicates the number of jobs available for processing on this host. A zero exit status (success) means the job queue is idle and may be terminated.

It can be used in a cron job or in a loop in a script that starts and backgrounds mythjobqueue to determine if there are any jobs currently being processed by this host's mythjobqueue or available for this host's mythjobqueue to process. If no jobs are in progress or available for processing, mythjobqueue, or even the computer running mythjobqueue, may be shut down.


PythonIcon.png job_queue_idle.py

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
#---------------------------
# Name: job_queue_idle.py
# Purpose
#   This python script checks to see if a job queue server is idle.
#   If no jobs are listed for, or can be run by, the current host, the
#   script will return 0, indicating mythjobqueue can be safely
#   terminated, allowing the master backend to shut itself down.
#   Otherwise, the script returns the total number of jobs capable of
#   being run by the current host.
#
#   It can also be run in daemon mode, in which case it backgrounds
#   itself, and proceeds to automatically manage the start and stop of
#   mythjobqueue as needed.
#---------------------------

from MythTV import MythDB, Job, MythDBError
from optparse import OptionParser
import subprocess
import time
import sys
import os

DB = None
LEVEL = None

class VERBOSE_LEVEL:
    DEFAULT = 0
    VERBOSE = 1
    DEBUG   = 2

def verbose(level, *messages):
    if (LEVEL >= level):
        print ''.join(map(str,messages))

def get_pid():
    pid = None
    ps = subprocess.Popen(['ps', 'ax'], stdout=-1)
    ps.wait()
    for line in ps.stdout.read().split('\n'):
        if 'mythjobqueue' in line:
            pid = int(line.strip().split(' ')[0])
            break
    return pid

def run_jobqueue():
    if subprocess.call(['mythjobqueue', '--daemon']):
        verbose(VERBOSE_LEVEL.DEFAULT, "Daemon failed to run mythjobqueue")
        sys.exit(1)
    return get_pid()

def term_jobqueue(pid):
    subprocess.call(['kill', str(pid)])

def get_job_count():
    currenthost = DB.gethostname()
    verbose(VERBOSE_LEVEL.DEBUG, 'Checking job queue status for host ',
            currenthost)
    jobs = list(Job.getAllEntries(db=DB))
    jobCount=0
    if len(jobs) == 0:
        verbose(VERBOSE_LEVEL.VERBOSE, 'No jobs found')
    else:
        for job in jobs:
            if (len(job.hostname) == 0):
                verbose(VERBOSE_LEVEL.VERBOSE, 'Job ',job.id,
                        'available for processing on any host (',
                        job.comment,')')
                jobCount+=1
            elif ((job.hostname != currenthost)):
                verbose(VERBOSE_LEVEL.DEBUG, 'Job ',job.id,' claimed by ',
                        job.hostname,' (',job.comment,')')
            elif (not(job.status & job.DONE)):
                verbose(VERBOSE_LEVEL.VERBOSE, 'Currently processing job ',
                        job.id,' (',job.comment,')')
                jobCount+=1
            else:
                verbose(VERBOSE_LEVEL.DEBUG, 'Finished processing job ',
                        job.id,' (',job.comment,')')

    verbose(VERBOSE_LEVEL.VERBOSE, 'Found ',jobCount,' jobs.')
    if (jobCount > 0):
        verbose(VERBOSE_LEVEL.DEFAULT, "mythjobqueue is not idle.")
    else:
        verbose(VERBOSE_LEVEL.DEFAULT, "mythjobqueue is idle.")

    return jobCount

def run_daemon():
    # fork once
    try:
        pid = os.fork()
        if pid:
            # parent, exit
            sys.exit(0)
    except OSError, e:
        verbose(VERBOSE_LEVEL.DEFAULT, "Daemon failed fork to background.")
        sys.exit(1)

    os.chdir("/")
    os.setsid()
    os.umask(0)

    # fork twice
    try:
        pid = os.fork()
        if pid:
            # parent, exit
            sys.exit(0)
    except OSError, e:
        verbose(VERBOSE_LEVEL.DEFAULT, "Daemon failed fork to background.")
        sys.exit(1)

    pid = get_pid()
    if pid is None:
        pid = run_jobqueue()

    running = True
    while True:
        try:
            count = get_job_count()
            if running and count==0:
                term_jobqueue(pid)
            elif not running and count:
                pid = run_jobqueue()
        except MythDBError:
            # database is inaccessible
            # wait for bindings to automatically reconnect
            pass
        time.sleep(60)

        
if __name__ == '__main__':
    parser = OptionParser(usage="usage: %prog [options]")

    parser.set_defaults(verbose_level=VERBOSE_LEVEL.DEFAULT)
    parser.add_option('-v', '--verbose', action='store', type='int',
                      dest='verbose_level', default=VERBOSE_LEVEL.DEFAULT,
                      help='Verbosity level')
    parser.add_option('-d', '--daemonize', action='store_true',
                      dest='daemon', default=False,
                      help='Daemonize and manage mythjobqueue')

    opts, args = parser.parse_args()

    LEVEL = opts.verbose_level

    try:
        DB = MythDB()
    except MythDBError:
        verbose(VERBOSE_LEVEL.VERBOSE, 'Failed to connect to the database server.')
        sys.exit(int(opts.daemon))

    if opts.daemon:
        run_daemon()
        sys.exit(1)

    else:
        sys.exit(get_job_count())