Commit d907de21 authored by Barry Warsaw's avatar Barry Warsaw

Attempt to reduce the disk I/O caused when running this script, by

saving state in a pck pickle file, and doing an incremental parse the
next time.  Specifically:

Added -f and -r flags.  The -f flag points to the pickle state file
(by default ./zeoqueue.pck).  The -r flag simply removes this file.
This is useful for log rotation in the cron job, and yes rm would have
been fine, but this option was requested by customers.

process_file(): Added to Status class so that the processing is done
here, where we can seek to a stored file position.

Also added the secret -0 option for testing, which suppresses the file
position seek (useful when the log file is split into chunks).

Return the number of blocked clients as the exit status code.
parent 564855d6
#! /usr/bin/env python #! /usr/bin/env python
"""Report on the number of currently waiting clients in the ZEO queue. """Report on the number of currently waiting clients in the ZEO queue.
Usage: zeoqueue.py [options] logfile Usage: %(PROGRAM)s [options] logfile
Options: Options:
-h / --help -h / --help
Print this help text and exit. Print this help text and exit.
-v -v / --verbose
Verbose output Verbose output
-f file
--file file
Use the specified file to store the incremental state as a pickle. If
not given, %(STATEFILE)s is used.
-r / --reset
Reset the state of the tool. This blows away any existing state
pickle file and then exits -- it does not parse the file. Use this
when you rotate log files so that the next run will parse from the
beginning of the file.
""" """
import os
import re import re
import sys import sys
import time import time
import errno
import getopt import getopt
import cPickle as pickle
COMMASPACE = ', ' COMMASPACE = ', '
STATEFILE = 'zeoqueue.pck'
PROGRAM = sys.argv[0]
try: try:
True, False True, False
...@@ -52,7 +68,7 @@ ccre = re.compile(r""" ...@@ -52,7 +68,7 @@ ccre = re.compile(r"""
.*) # rest of line .*) # rest of line
""", re.VERBOSE) """, re.VERBOSE)
wcre = re.compile(r"""Clients waiting: (?P<num>\d+)""") wcre = re.compile(r'Clients waiting: (?P<num>\d+)')
...@@ -122,6 +138,8 @@ class Status: ...@@ -122,6 +138,8 @@ class Status:
""" """
def __init__(self): def __init__(self):
self.lineno = 0
self.pos = 0
self.reset() self.reset()
def reset(self): def reset(self):
...@@ -143,6 +161,19 @@ class Status: ...@@ -143,6 +161,19 @@ class Status:
# transaction is good enough. # transaction is good enough.
return self.commit is not None return self.commit is not None
def process_file(self, fp):
if self.pos:
if VERBOSE:
print 'seeking to file position', self.pos
fp.seek(self.pos)
while True:
line = fp.readline()
if not line:
break
self.lineno += 1
self.process(line)
self.pos = fp.tell()
def process(self, line): def process(self, line):
if line.find("calling") != -1: if line.find("calling") != -1:
self.process_call(line) self.process_call(line)
...@@ -278,7 +309,7 @@ class Status: ...@@ -278,7 +309,7 @@ class Status:
def usage(code, msg=''): def usage(code, msg=''):
print >> sys.stderr, __doc__ print >> sys.stderr, __doc__ % globals()
if msg: if msg:
print >> sys.stderr, msg print >> sys.stderr, msg
sys.exit(code) sys.exit(code)
...@@ -286,18 +317,40 @@ def usage(code, msg=''): ...@@ -286,18 +317,40 @@ def usage(code, msg=''):
def main(): def main():
global VERBOSE global VERBOSE
VERBOSE = 0
VERBOSE = 0
file = STATEFILE
reset = False
# -0 is a secret option used for testing purposes only
seek = True
try: try:
opts, args = getopt.getopt(sys.argv[1:], 'vh', ['help']) opts, args = getopt.getopt(sys.argv[1:], 'vhf:r0',
['help', 'verbose', 'file=', 'reset'])
except getopt.error, msg: except getopt.error, msg:
usage(1, msg) usage(1, msg)
for opt, arg in opts: for opt, arg in opts:
if opt in ('-h', '--help'): if opt in ('-h', '--help'):
usage(0) usage(0)
elif opt == '-v': elif opt in ('-v', '--verbose'):
VERBOSE += 1 VERBOSE += 1
elif opt in ('-f', '--file'):
file = arg
elif opt in ('-r', '--reset'):
reset = True
elif opt == '-0':
seek = False
if reset:
# Blow away the existing state file and exit
try:
os.unlink(file)
if VERBOSE:
print 'removing pickle state file', file
except OSError, e:
if e.errno <> errno.ENOENT:
raise
return
if not args: if not args:
usage(1, 'logfile is required') usage(1, 'logfile is required')
...@@ -305,14 +358,42 @@ def main(): ...@@ -305,14 +358,42 @@ def main():
usage(1, 'too many arguments: %s' % COMMASPACE.join(args)) usage(1, 'too many arguments: %s' % COMMASPACE.join(args))
path = args[0] path = args[0]
f = open(path, "rb")
s = Status() # Get the previous status object from the pickle file, if it is available
while True: # and if the --reset flag wasn't given.
line = f.readline() status = None
if not line: try:
break statefp = open(file, 'rb')
s.process(line) try:
s.report() status = pickle.load(statefp)
if VERBOSE:
print 'reading status from file', file
finally:
statefp.close()
except IOError, e:
if e.errno <> errno.ENOENT:
raise
if status is None:
status = Status()
if VERBOSE:
print 'using new status'
if not seek:
status.pos = 0
fp = open(path, 'rb')
try:
status.process_file(fp)
finally:
fp.close()
# Save state
statefp = open(file, 'wb')
pickle.dump(status, statefp, 1)
statefp.close()
# Print the report and return the number of blocked clients in the exit
# status code.
status.report()
sys.exit(status.n_blocked)
if __name__ == "__main__": if __name__ == "__main__":
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment