from twisted.runner.procmon import ProcessMonitor from twisted.python import log, reflect import twisted.protocols.basic from twisted.internet import reactor, protocol from optparse import OptionParser, SUPPRESS_HELP import sys, os, time, subprocess import pickle import logging logging.basicConfig(level=logging.DEBUG, format='%(asctime)s [%(name)s] %(levelname)s: %(message)s') processes = {'lrsscanner': 'youtomb.scan.lrsscanner', 'deadscanner': 'youtomb.scan.deadscanner', 'toptodayexplorer': 'youtomb.explore.toptodayexplorer', 'mosttodayexplorer': 'youtomb.explore.mosttodayexplorer', 'diggexplorer': 'youtomb.explore.diggexplorer', 'technoratiexplorer': 'youtomb.explore.technoratiexplorer'} # From twisted.runner.procmon, modified to use Python's logging class LineLogger(twisted.protocols.basic.LineReceiver): tag = None delimiter = '\n' logger = None level = logging.INFO def lineReceived(self, line): if self.logger is None: self.logger = logging.getLogger(self.tag) self.logger.log(self.level, line) class LoggingProtocol(protocol.ProcessProtocol): service = None name = None empty = 1 errEmpty = 1 def connectionMade(self): self.output = LineLogger() self.output.tag = self.name self.output.makeConnection(twisted.runner.procmon.transport) self.errOutput = LineLogger() self.errOutput.level = logging.ERROR self.errOutput.tag = self.name self.errOutput.makeConnection(twisted.runner.procmon.transport) def outReceived(self, data): self.output.dataReceived(data) self.empty = data[-1] == '\n' def errReceived(self, data): self.errOutput.dataReceived(data) self.errEmpty = data[-1] == '\n' def processEnded(self, reason): if not self.empty: self.output.dataReceived('\n') if not self.errEmpty: self.errOutput.dataReceived('\n') self.service.connectionLost(self.name) twisted.runner.procmon.LineLogger = LineLogger twisted.runner.procmon.LoggingProtocol = LoggingProtocol def startProcess(self, name): if self.protocols.has_key(name): return p = self.protocols[name] = LoggingProtocol() p.service = self p.name = name args, uid, gid = self.processes[name] self.timeStarted[name] = time.time() reactor.spawnProcess(p, args[0], args, env=os.environ, uid=uid, gid=gid) twisted.runner.procmon.ProcessMonitor.startProcess = startProcess def textFromEventDict(eventDict): edm = eventDict['message'] if not edm: if eventDict['isError'] and 'failure' in eventDict: text = ((eventDict.get('why') or 'Unhandled Error') + '\n' + eventDict['failure'].getTraceback()) elif 'format' in eventDict: try: text = eventDict['format'] % eventDict except: text = 'UNFORMATTABLE OBJECT WRITTEN TO LOG with fmt %r, MESSAGE LOST' % (fmtString,) else: # we don't know how to log this return else: text = ' '.join(map(reflect.safe_str, edm)) return text def stdoutEmit(eventDict): logging.info(textFromEventDict(eventDict)) def kinit(keytab, principal): subprocess.check_call(("/usr/kerberos/bin/kinit","-V","-k","-t", keytab, principal)) def aklog(cells=("athena.mit.edu", "sipb.mit.edu")): subprocess.check_call(("/usr/bin/aklog",)+cells) def main(): log.addObserver(stdoutEmit) logging.info("Starting ProcMon") parser = OptionParser() parser.add_option("-t", "--threshold", dest="threshold", default=5, type="int", help="number of seconds process has to live before it is considered to be alive", metavar="SECONDS") parser.add_option("--with-python", dest="interpreter", default=sys.executable, help="python interpreter to use for launching processes", metavar="PATH") parser.add_option("-p", "--principal", dest="principal", default="daemon/youtomb.mit.edu@ATHENA.MIT.EDU", help="Kerberos principal to access files with", metavar="PRINC") parser.add_option("--new-pag", dest="new_pag", default=False, action="store_true", help="create a new PAG and kinit before executing") parser.add_option("-k", "--keytab", dest="keytab", default="/mit/freeculture/youtomb/youtomb.keytab", help="keytab to use for kinit", metavar="PATH") parser.add_option("--pickled-options", dest="pickled_options", help=SUPPRESS_HELP) (options, args) = parser.parse_args() if options.pickled_options: (options, args) = pickle.loads(options.pickled_options) if options.new_pag: aklog() def doKrenew(): kinit(options.keytab, options.principal) aklog() reactor.callLater(60*60, doKrenew) reactor.callLater(60*60, doKrenew) else: print "Uhh, pickled options but not new_pag?" print "Got options:", options elif options.new_pag: opts = pickle.dumps((options, args)) os.environ["KRB5CCNAME"] = os.tempnam(None, "yt.krb5cc") print "Using KRB5CCNAME=%s" % os.environ["KRB5CCNAME"] kinit(options.keytab, options.principal) # Need to aklog here so python can read the scripts command = ("pagsh", "pagsh", "-c", 'aklog athena sipb; "$@"', "python", options.interpreter, sys.argv[0], "--pickled-options", opts) print "Executing", command os.execlp(*command) mon = ProcessMonitor() mon.threshold = options.threshold for module in args: module = processes.get(module, module) mon.addProcess(module, [options.interpreter, "-m", module]) mon.startService() reactor.addSystemEventTrigger('before', 'shutdown', mon.stopService) reactor.run() if __name__ == "__main__": main()