[Zodb-checkins] CVS: ZODB3/Tools - zeoqueue.py:1.4

Barry Warsaw barry@wooz.org
Wed, 5 Feb 2003 15:45:04 -0500


Update of /cvs-repository/ZODB3/Tools
In directory cvs.zope.org:/tmp/cvs-serv27226

Modified Files:
	zeoqueue.py 
Log Message:
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.


=== ZODB3/Tools/zeoqueue.py 1.3 => 1.4 ===
--- ZODB3/Tools/zeoqueue.py:1.3	Tue Feb  4 19:06:14 2003
+++ ZODB3/Tools/zeoqueue.py	Wed Feb  5 15:45:01 2003
@@ -1,22 +1,38 @@
 #! /usr/bin/env python
 """Report on the number of currently waiting clients in the ZEO queue.
 
-Usage: zeoqueue.py [options] logfile
+Usage: %(PROGRAM)s [options] logfile
 
 Options:
     -h / --help
         Print this help text and exit.
 
-    -v
+    -v / --verbose
         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 sys
 import time
+import errno
 import getopt
+import cPickle as pickle
 
 COMMASPACE = ', '
+STATEFILE = 'zeoqueue.pck'
+PROGRAM = sys.argv[0]
 
 try:
     True, False
@@ -52,7 +68,7 @@
      .*)         # rest of line
     """, re.VERBOSE)
 
-wcre = re.compile(r"""Clients waiting: (?P<num>\d+)""")
+wcre = re.compile(r'Clients waiting: (?P<num>\d+)')
 
 
 
@@ -122,6 +138,8 @@
     """
 
     def __init__(self):
+        self.lineno = 0
+        self.pos = 0
         self.reset()
 
     def reset(self):
@@ -143,6 +161,19 @@
         # transaction is good enough.
         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):
         if line.find("calling") != -1:
             self.process_call(line)
@@ -278,7 +309,7 @@
 
 
 def usage(code, msg=''):
-    print >> sys.stderr, __doc__
+    print >> sys.stderr, __doc__ % globals()
     if msg:
         print >> sys.stderr, msg
     sys.exit(code)
@@ -286,18 +317,40 @@
 
 def main():
     global VERBOSE
-    VERBOSE = 0
 
+    VERBOSE = 0
+    file = STATEFILE
+    reset = False
+    # -0 is a secret option used for testing purposes only
+    seek = True
     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:
         usage(1, msg)
 
     for opt, arg in opts:
         if opt in ('-h', '--help'):
             usage(0)
-        elif opt == '-v':
+        elif opt in ('-v', '--verbose'):
             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:
         usage(1, 'logfile is required')
@@ -305,14 +358,42 @@
         usage(1, 'too many arguments: %s' % COMMASPACE.join(args))
 
     path = args[0]
-    f = open(path, "rb")
-    s = Status()
-    while True:
-        line = f.readline()
-        if not line:
-            break
-        s.process(line)
-    s.report()
+
+    # Get the previous status object from the pickle file, if it is available
+    # and if the --reset flag wasn't given.
+    status = None
+    try:
+        statefp = open(file, 'rb')
+        try:
+            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__":