[Zodb-checkins] CVS: ZODB3/Tools - zodbload.py:1.1.4.1 zeoserverlog.py:1.1.4.1 timeout.py:1.1.4.1 README.txt:1.1.4.1 zeoup.py:1.13.14.1 zeopack.py:1.8.12.1 netspace.py:1.1.40.1 fstest.py:1.9.4.1 checkbtrees.py:1.1.28.1 analyze.py:1.1.28.1 space.py:NONE

Jeremy Hylton jeremy at zope.com
Thu Sep 4 13:47:25 EDT 2003


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

Modified Files:
      Tag: ZODB3-3_2-branch
	zeoup.py zeopack.py netspace.py fstest.py checkbtrees.py 
	analyze.py 
Added Files:
      Tag: ZODB3-3_2-branch
	zodbload.py zeoserverlog.py timeout.py README.txt 
Removed Files:
      Tag: ZODB3-3_2-branch
	space.py 
Log Message:
Sync with changes on the ZODB 3.1 branch.


=== Added File ZODB3/Tools/zodbload.py ===
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Test script for testing ZODB under a heavy zope-like load.

Note that, to be as realistic as possible with ZEO, you should run this
script multiple times, to simulate multiple clients.

Here's how this works.

The script starts some number of threads.  Each thread, sequentially
executes jobs.  There is a job producer that produces jobs.

Input data are provided by a mail producer that hands out message from
a mailbox.

Execution continues until there is an error, which will normally occur
when the mailbox is exhausted.

Command-line options are used to provide job definitions. Job
definitions have perameters of the form name=value.  Jobs have 2
standard parameters:

  frequency=integer

     The frequency of the job. The default is 1.

  sleep=float

     The number os seconds to sleep before performing the job. The
     default is 0.

Usage: loadmail2 [options]

  Options:

    -edit [frequency=integer] [sleep=float]

       Define an edit job. An edit job edits a random already-saved
       email message, deleting and inserting a random number of words.

       After editing the message, the message is (re)cataloged.

    -insert [number=int] [frequency=integer] [sleep=float]

       Insert some number of email messages.

    -index [number=int] [frequency=integer] [sleep=float]

       Insert and index (catalog) some number of email messages.

    -search [terms='word1 word2 ...'] [frequency=integer] [sleep=float]

       Search the catalog. A query is givem with one or more terms as
       would be entered into a typical seach box.  If no query is
       given, then queries will be randomly selected based on a set of
       built-in word list.

    -setup

       Set up the database. This will delete any existing Data.fs
       file.  (Of course, this may have no effect, if there is a
       custom_zodb that defined a different storage.) It also adds a
       mail folder and a catalog.

    -options file

       Read options from the given file. Th efile should be a python
       source file that defines a sequence of options named 'options'.

    -threads n

       Specify the number of threads to execute. If not specified (< 2),
       then jobs are run in a single (main) thread.

    -mbox filename

       Specify the mailbox for getting input data.

$Id: zodbload.py,v 1.1.4.1 2003/09/04 16:47:23 jeremy Exp $
"""

import mailbox
import math
import os
import random
import re
import sys
import threading
import time

class JobProducer:

    def __init__(self):
        self.jobs = []

    def add(self, callable, frequency, sleep, repeatp=0):
        self.jobs.extend([(callable, sleep, repeatp)] * int(frequency))
        random.shuffle(self.jobs)

    def next(self):
        factory, sleep, repeatp = random.choice(self.jobs)
        time.sleep(sleep)
        callable, args = factory.create()
        return factory, callable, args, repeatp

    def __nonzero__(self):
        return not not self.jobs



class MBox:

    def __init__(self, filename):
        if ' ' in filename:
            filename, min, max = filename.split()
            min = int(min)
            max = int(max)
        else:
            min = max = 0

        if filename.endswith('.bz2'):
            f = os.popen("bunzip2 <"+filename, 'r')
            filename = filename[-4:]
        else:
            f = open(filename)

        self._mbox = mb = mailbox.UnixMailbox(f)

        self.number = min
        while min:
            mb.next()
            min -= 1

        self._lock = threading.Lock()
        self.__name__ = os.path.splitext(os.path.split(filename)[1])[0]
        self._max = max

    def next(self):
        self._lock.acquire()
        try:
            if self._max > 0 and self.number >= self._max:
                raise IndexError(self.number + 1)
            message = self._mbox.next()
            message.body = message.fp.read()
            message.headers = list(message.headers)
            self.number += 1
            message.number = self.number
            message.mbox = self.__name__
            return message
        finally:
            self._lock.release()

bins = 9973
#bins = 11
def mailfolder(app, mboxname, number):
    mail = getattr(app, mboxname, None)
    if mail is None:
        app.manage_addFolder(mboxname)
        mail = getattr(app, mboxname)
        from BTrees.Length import Length
        mail.length = Length()
        for i in range(bins):
            mail.manage_addFolder('b'+str(i))
    bin = hash(str(number))%bins
    return getattr(mail, 'b'+str(bin))


def VmSize():

    try:
        f = open('/proc/%s/status' % os.getpid())
    except:
        return 0
    else:
        l = filter(lambda l: l[:7] == 'VmSize:', f.readlines())
        if l:
            l = l[0][7:].strip().split()[0]
            return int(l)
    return 0

def setup(lib_python):
    try:
        os.remove(os.path.join(lib_python, '..', '..', 'var', 'Data.fs'))
    except:
        pass
    import Zope
    import Products
    import AccessControl.SecurityManagement
    app=Zope.app()

    Products.ZCatalog.ZCatalog.manage_addZCatalog(app, 'cat', '')

    from Products.ZCTextIndex.ZCTextIndex import PLexicon
    from Products.ZCTextIndex.Lexicon import Splitter, CaseNormalizer

    app.cat._setObject('lex',
                       PLexicon('lex', '', Splitter(), CaseNormalizer())
                       )

    class extra:
        doc_attr = 'PrincipiaSearchSource'
        lexicon_id = 'lex'
        index_type = 'Okapi BM25 Rank'

    app.cat.addIndex('PrincipiaSearchSource', 'ZCTextIndex', extra)

    get_transaction().commit()

    system = AccessControl.SpecialUsers.system
    AccessControl.SecurityManagement.newSecurityManager(None, system)

    app._p_jar.close()

def do(db, f, args):
    """Do something in a transaction, retrying of necessary

    Measure the speed of both the compurartion and the commit
    """
    from ZODB.POSException import ConflictError
    wcomp = ccomp = wcommit = ccommit = 0.0
    rconflicts = wconflicts = 0
    start = time.time()

    while 1:
        connection = db.open()
        try:
            get_transaction().begin()
            t=time.time()
            c=time.clock()
            try:
                try:
                    r = f(connection, *args)
                except ConflictError:
                    rconflicts += 1
                    get_transaction().abort()
                    continue
            finally:
                wcomp += time.time() - t
                ccomp += time.clock() - c

            t=time.time()
            c=time.clock()
            try:
                try:
                    get_transaction().commit()
                    break
                except ConflictError:
                    wconflicts += 1
                    get_transaction().abort()
                    continue
            finally:
                wcommit += time.time() - t
                ccommit += time.clock() - c
        finally:
            connection.close()

    return start, wcomp, ccomp, rconflicts, wconflicts, wcommit, ccommit, r

def run1(tid, db, factory, job, args):
    (start, wcomp, ccomp, rconflicts, wconflicts, wcommit, ccommit, r
     ) = do(db, job, args)
    start = "%.4d-%.2d-%.2d %.2d:%.2d:%.2d" % time.localtime(start)[:6]
    print "%s %s %8.3g %8.3g %s %s\t%8.3g %8.3g %s %r" % (
        start, tid, wcomp, ccomp, rconflicts, wconflicts, wcommit, ccommit,
        factory.__name__, r)

def run(jobs, tid=''):
    import Zope
    while 1:
        factory, job, args, repeatp = jobs.next()
        run1(tid, Zope.DB, factory, job, args)
        if repeatp:
            while 1:
                i = random.randint(0,100)
                if i > repeatp:
                    break
                run1(tid, Zope.DB, factory, job, args)


def index(connection, messages, catalog):
    app = connection.root()['Application']
    for message in messages:
        mail = mailfolder(app, message.mbox, message.number)
        docid = 'm'+str(message.number)
        mail.manage_addDTMLDocument(docid, file=message.body)

        # increment counted
        getattr(app, message.mbox).length.change(1)

        doc = mail[docid]
        for h in message.headers:
            h = h.strip()
            l = h.find(':')
            if l <= 0:
                continue
            name = h[:l].lower()
            if name=='subject':
                name='title'
            v = h[l+1:].strip()
            type='string'

            if name=='title':
                doc.manage_changeProperties(title=h)
            else:
                try:
                    doc.manage_addProperty(name, v, type)
                except:
                    pass
        if catalog:
            app.cat.catalog_object(doc)

    return message.number

class IndexJob:
    needs_mbox = 1
    catalog = 1
    prefix = 'index'

    def __init__(self, mbox, number=1):
        self.__name__ = "%s%s_%s" % (self.prefix, number, mbox.__name__)
        self.mbox, self.number = mbox, int(number)

    def create(self):
        messages = [self.mbox.next() for i in range(self.number)]
        return index, (messages, self.catalog)


class InsertJob(IndexJob):
    catalog = 0
    prefix = 'insert'

wordre = re.compile(r'(\w{3,20})')
stop = 'and', 'not'
def edit(connection, mbox, catalog=1):
    app = connection.root()['Application']
    mail = getattr(app, mbox.__name__, None)
    if mail is None:
        time.sleep(1)
        return "No mailbox %s" % mbox.__name__

    nmessages = mail.length()
    if nmessages < 2:
        time.sleep(1)
        return "No messages to edit in %s" % mbox.__name__

    # find a message to edit:
    while 1:
        number = random.randint(1, nmessages-1)
        did = 'm' + str(number)

        mail = mailfolder(app, mbox.__name__, number)
        doc = getattr(mail, did, None)
        if doc is not None:
            break

    text = doc.raw.split()
    norig = len(text)
    if norig > 10:
        ndel = int(math.exp(random.randint(0, int(math.log(norig)))))
        nins = int(math.exp(random.randint(0, int(math.log(norig)))))
    else:
        ndel = 0
        nins = 10

    for j in range(ndel):
        j = random.randint(0,len(text)-1)
        word = text[j]
        m = wordre.search(word)
        if m:
            word = m.group(1).lower()
            if (not wordsd.has_key(word)) and word not in stop:
                words.append(word)
                wordsd[word] = 1
        del text[j]

    for j in range(nins):
        word = random.choice(words)
        text.append(word)

    doc.raw = ' '.join(text)

    if catalog:
        app.cat.catalog_object(doc)

    return norig, ndel, nins

class EditJob:
    needs_mbox = 1
    prefix = 'edit'
    catalog = 1

    def __init__(self, mbox):
        self.__name__ = "%s_%s" % (self.prefix, mbox.__name__)
        self.mbox = mbox

    def create(self):
        return edit, (self.mbox, self.catalog)

class ModifyJob(EditJob):
    prefix = 'modify'
    catalog = 0


def search(connection, terms, number):
    app = connection.root()['Application']
    cat = app.cat
    n = 0

    for i in number:
        term = random.choice(terms)

        results = cat(PrincipiaSearchSource=term)
        n += len(results)
        for result in results:
            did = result.getObject().getId()

    return n

class SearchJob:

    def __init__(self, terms='', number=10):

        if terms:
            terms = terms.split()
            self.__name__ = "search_" + '_'.join(terms)
            self.terms = terms
        else:
            self.__name__ = 'search'
            self.terms = words

        number = min(int(number), len(self.terms))
        self.number = range(number)

    def create(self):
        return search, (self.terms, self.number)


words=['banishment', 'indirectly', 'imprecise', 'peeks',
'opportunely', 'bribe', 'sufficiently', 'Occidentalized', 'elapsing',
'fermenting', 'listen', 'orphanage', 'younger', 'draperies', 'Ida',
'cuttlefish', 'mastermind', 'Michaels', 'populations', 'lent',
'cater', 'attentional', 'hastiness', 'dragnet', 'mangling',
'scabbards', 'princely', 'star', 'repeat', 'deviation', 'agers',
'fix', 'digital', 'ambitious', 'transit', 'jeeps', 'lighted',
'Prussianizations', 'Kickapoo', 'virtual', 'Andrew', 'generally',
'boatsman', 'amounts', 'promulgation', 'Malay', 'savaging',
'courtesan', 'nursed', 'hungered', 'shiningly', 'ship', 'presides',
'Parke', 'moderns', 'Jonas', 'unenlightening', 'dearth', 'deer',
'domesticates', 'recognize', 'gong', 'penetrating', 'dependents',
'unusually', 'complications', 'Dennis', 'imbalances', 'nightgown',
'attached', 'testaments', 'congresswoman', 'circuits', 'bumpers',
'braver', 'Boreas', 'hauled', 'Howe', 'seethed', 'cult', 'numismatic',
'vitality', 'differences', 'collapsed', 'Sandburg', 'inches', 'head',
'rhythmic', 'opponent', 'blanketer', 'attorneys', 'hen', 'spies',
'indispensably', 'clinical', 'redirection', 'submit', 'catalysts',
'councilwoman', 'kills', 'topologies', 'noxious', 'exactions',
'dashers', 'balanced', 'slider', 'cancerous', 'bathtubs', 'legged',
'respectably', 'crochets', 'absenteeism', 'arcsine', 'facility',
'cleaners', 'bobwhite', 'Hawkins', 'stockade', 'provisional',
'tenants', 'forearms', 'Knowlton', 'commit', 'scornful',
'pediatrician', 'greets', 'clenches', 'trowels', 'accepts',
'Carboloy', 'Glenn', 'Leigh', 'enroll', 'Madison', 'Macon', 'oiling',
'entertainingly', 'super', 'propositional', 'pliers', 'beneficiary',
'hospitable', 'emigration', 'sift', 'sensor', 'reserved',
'colonization', 'shrilled', 'momentously', 'stevedore', 'Shanghaiing',
'schoolmasters', 'shaken', 'biology', 'inclination', 'immoderate',
'stem', 'allegory', 'economical', 'daytime', 'Newell', 'Moscow',
'archeology', 'ported', 'scandals', 'Blackfoot', 'leery', 'kilobit',
'empire', 'obliviousness', 'productions', 'sacrificed', 'ideals',
'enrolling', 'certainties', 'Capsicum', 'Brookdale', 'Markism',
'unkind', 'dyers', 'legislates', 'grotesquely', 'megawords',
'arbitrary', 'laughing', 'wildcats', 'thrower', 'sex', 'devils',
'Wehr', 'ablates', 'consume', 'gossips', 'doorways', 'Shari',
'advanced', 'enumerable', 'existentially', 'stunt', 'auctioneers',
'scheduler', 'blanching', 'petulance', 'perceptibly', 'vapors',
'progressed', 'rains', 'intercom', 'emergency', 'increased',
'fluctuating', 'Krishna', 'silken', 'reformed', 'transformation',
'easter', 'fares', 'comprehensible', 'trespasses', 'hallmark',
'tormenter', 'breastworks', 'brassiere', 'bladders', 'civet', 'death',
'transformer', 'tolerably', 'bugle', 'clergy', 'mantels', 'satin',
'Boswellizes', 'Bloomington', 'notifier', 'Filippo', 'circling',
'unassigned', 'dumbness', 'sentries', 'representativeness', 'souped',
'Klux', 'Kingstown', 'gerund', 'Russell', 'splices', 'bellow',
'bandies', 'beefers', 'cameramen', 'appalled', 'Ionian', 'butterball',
'Portland', 'pleaded', 'admiringly', 'pricks', 'hearty', 'corer',
'deliverable', 'accountably', 'mentors', 'accorded',
'acknowledgement', 'Lawrenceville', 'morphology', 'eucalyptus',
'Rena', 'enchanting', 'tighter', 'scholars', 'graduations', 'edges',
'Latinization', 'proficiency', 'monolithic', 'parenthesizing', 'defy',
'shames', 'enjoyment', 'Purdue', 'disagrees', 'barefoot', 'maims',
'flabbergast', 'dishonorable', 'interpolation', 'fanatics', 'dickens',
'abysses', 'adverse', 'components', 'bowl', 'belong', 'Pipestone',
'trainees', 'paw', 'pigtail', 'feed', 'whore', 'conditioner',
'Volstead', 'voices', 'strain', 'inhabits', 'Edwin', 'discourses',
'deigns', 'cruiser', 'biconvex', 'biking', 'depreciation', 'Harrison',
'Persian', 'stunning', 'agar', 'rope', 'wagoner', 'elections',
'reticulately', 'Cruz', 'pulpits', 'wilt', 'peels', 'plants',
'administerings', 'deepen', 'rubs', 'hence', 'dissension', 'implored',
'bereavement', 'abyss', 'Pennsylvania', 'benevolent', 'corresponding',
'Poseidon', 'inactive', 'butchers', 'Mach', 'woke', 'loading',
'utilizing', 'Hoosier', 'undo', 'Semitization', 'trigger', 'Mouthe',
'mark', 'disgracefully', 'copier', 'futility', 'gondola', 'algebraic',
'lecturers', 'sponged', 'instigators', 'looted', 'ether', 'trust',
'feeblest', 'sequencer', 'disjointness', 'congresses', 'Vicksburg',
'incompatibilities', 'commend', 'Luxembourg', 'reticulation',
'instructively', 'reconstructs', 'bricks', 'attache', 'Englishman',
'provocation', 'roughen', 'cynic', 'plugged', 'scrawls', 'antipode',
'injected', 'Daedalus', 'Burnsides', 'asker', 'confronter',
'merriment', 'disdain', 'thicket', 'stinker', 'great', 'tiers',
'oust', 'antipodes', 'Macintosh', 'tented', 'packages',
'Mediterraneanize', 'hurts', 'orthodontist', 'seeder', 'readying',
'babying', 'Florida', 'Sri', 'buckets', 'complementary',
'cartographer', 'chateaus', 'shaves', 'thinkable', 'Tehran',
'Gordian', 'Angles', 'arguable', 'bureau', 'smallest', 'fans',
'navigated', 'dipole', 'bootleg', 'distinctive', 'minimization',
'absorbed', 'surmised', 'Malawi', 'absorbent', 'close', 'conciseness',
'hopefully', 'declares', 'descent', 'trick', 'portend', 'unable',
'mildly', 'Morse', 'reference', 'scours', 'Caribbean', 'battlers',
'astringency', 'likelier', 'Byronizes', 'econometric', 'grad',
'steak', 'Austrian', 'ban', 'voting', 'Darlington', 'bison', 'Cetus',
'proclaim', 'Gilbertson', 'evictions', 'submittal', 'bearings',
'Gothicizer', 'settings', 'McMahon', 'densities', 'determinants',
'period', 'DeKastere', 'swindle', 'promptness', 'enablers', 'wordy',
'during', 'tables', 'responder', 'baffle', 'phosgene', 'muttering',
'limiters', 'custodian', 'prevented', 'Stouffer', 'waltz', 'Videotex',
'brainstorms', 'alcoholism', 'jab', 'shouldering', 'screening',
'explicitly', 'earner', 'commandment', 'French', 'scrutinizing',
'Gemma', 'capacitive', 'sheriff', 'herbivore', 'Betsey', 'Formosa',
'scorcher', 'font', 'damming', 'soldiers', 'flack', 'Marks',
'unlinking', 'serenely', 'rotating', 'converge', 'celebrities',
'unassailable', 'bawling', 'wording', 'silencing', 'scotch',
'coincided', 'masochists', 'graphs', 'pernicious', 'disease',
'depreciates', 'later', 'torus', 'interject', 'mutated', 'causer',
'messy', 'Bechtel', 'redundantly', 'profoundest', 'autopsy',
'philosophic', 'iterate', 'Poisson', 'horridly', 'silversmith',
'millennium', 'plunder', 'salmon', 'missioner', 'advances', 'provers',
'earthliness', 'manor', 'resurrectors', 'Dahl', 'canto', 'gangrene',
'gabler', 'ashore', 'frictionless', 'expansionism', 'emphasis',
'preservations', 'Duane', 'descend', 'isolated', 'firmware',
'dynamites', 'scrawled', 'cavemen', 'ponder', 'prosperity', 'squaw',
'vulnerable', 'opthalmic', 'Simms', 'unite', 'totallers', 'Waring',
'enforced', 'bridge', 'collecting', 'sublime', 'Moore', 'gobble',
'criticizes', 'daydreams', 'sedate', 'apples', 'Concordia',
'subsequence', 'distill', 'Allan', 'seizure', 'Isadore', 'Lancashire',
'spacings', 'corresponded', 'hobble', 'Boonton', 'genuineness',
'artifact', 'gratuities', 'interviewee', 'Vladimir', 'mailable',
'Bini', 'Kowalewski', 'interprets', 'bereave', 'evacuated', 'friend',
'tourists', 'crunched', 'soothsayer', 'fleetly', 'Romanizations',
'Medicaid', 'persevering', 'flimsy', 'doomsday', 'trillion',
'carcasses', 'guess', 'seersucker', 'ripping', 'affliction',
'wildest', 'spokes', 'sheaths', 'procreate', 'rusticates', 'Schapiro',
'thereafter', 'mistakenly', 'shelf', 'ruination', 'bushel',
'assuredly', 'corrupting', 'federation', 'portmanteau', 'wading',
'incendiary', 'thing', 'wanderers', 'messages', 'Paso', 'reexamined',
'freeings', 'denture', 'potting', 'disturber', 'laborer', 'comrade',
'intercommunicating', 'Pelham', 'reproach', 'Fenton', 'Alva', 'oasis',
'attending', 'cockpit', 'scout', 'Jude', 'gagging', 'jailed',
'crustaceans', 'dirt', 'exquisitely', 'Internet', 'blocker', 'smock',
'Troutman', 'neighboring', 'surprise', 'midscale', 'impart',
'badgering', 'fountain', 'Essen', 'societies', 'redresses',
'afterwards', 'puckering', 'silks', 'Blakey', 'sequel', 'greet',
'basements', 'Aubrey', 'helmsman', 'album', 'wheelers', 'easternmost',
'flock', 'ambassadors', 'astatine', 'supplant', 'gird', 'clockwork',
'foxes', 'rerouting', 'divisional', 'bends', 'spacer',
'physiologically', 'exquisite', 'concerts', 'unbridled', 'crossing',
'rock', 'leatherneck', 'Fortescue', 'reloading', 'Laramie', 'Tim',
'forlorn', 'revert', 'scarcer', 'spigot', 'equality', 'paranormal',
'aggrieves', 'pegs', 'committeewomen', 'documented', 'interrupt',
'emerald', 'Battelle', 'reconverted', 'anticipated', 'prejudices',
'drowsiness', 'trivialities', 'food', 'blackberries', 'Cyclades',
'tourist', 'branching', 'nugget', 'Asilomar', 'repairmen', 'Cowan',
'receptacles', 'nobler', 'Nebraskan', 'territorial', 'chickadee',
'bedbug', 'darted', 'vigilance', 'Octavia', 'summands', 'policemen',
'twirls', 'style', 'outlawing', 'specifiable', 'pang', 'Orpheus',
'epigram', 'Babel', 'butyrate', 'wishing', 'fiendish', 'accentuate',
'much', 'pulsed', 'adorned', 'arbiters', 'counted', 'Afrikaner',
'parameterizes', 'agenda', 'Americanism', 'referenda', 'derived',
'liquidity', 'trembling', 'lordly', 'Agway', 'Dillon', 'propellers',
'statement', 'stickiest', 'thankfully', 'autograph', 'parallel',
'impulse', 'Hamey', 'stylistic', 'disproved', 'inquirer', 'hoisting',
'residues', 'variant', 'colonials', 'dequeued', 'especial', 'Samoa',
'Polaris', 'dismisses', 'surpasses', 'prognosis', 'urinates',
'leaguers', 'ostriches', 'calculative', 'digested', 'divided',
'reconfigurer', 'Lakewood', 'illegalities', 'redundancy',
'approachability', 'masterly', 'cookery', 'crystallized', 'Dunham',
'exclaims', 'mainline', 'Australianizes', 'nationhood', 'pusher',
'ushers', 'paranoia', 'workstations', 'radiance', 'impedes',
'Minotaur', 'cataloging', 'bites', 'fashioning', 'Alsop', 'servants',
'Onondaga', 'paragraph', 'leadings', 'clients', 'Latrobe',
'Cornwallis', 'excitingly', 'calorimetric', 'savior', 'tandem',
'antibiotics', 'excuse', 'brushy', 'selfish', 'naive', 'becomes',
'towers', 'popularizes', 'engender', 'introducing', 'possession',
'slaughtered', 'marginally', 'Packards', 'parabola', 'utopia',
'automata', 'deterrent', 'chocolates', 'objectives', 'clannish',
'aspirin', 'ferociousness', 'primarily', 'armpit', 'handfuls',
'dangle', 'Manila', 'enlivened', 'decrease', 'phylum', 'hardy',
'objectively', 'baskets', 'chaired', 'Sepoy', 'deputy', 'blizzard',
'shootings', 'breathtaking', 'sticking', 'initials', 'epitomized',
'Forrest', 'cellular', 'amatory', 'radioed', 'horrified', 'Neva',
'simultaneous', 'delimiter', 'expulsion', 'Himmler', 'contradiction',
'Remus', 'Franklinizations', 'luggage', 'moisture', 'Jews',
'comptroller', 'brevity', 'contradictions', 'Ohio', 'active',
'babysit', 'China', 'youngest', 'superstition', 'clawing', 'raccoons',
'chose', 'shoreline', 'helmets', 'Jeffersonian', 'papered',
'kindergarten', 'reply', 'succinct', 'split', 'wriggle', 'suitcases',
'nonce', 'grinders', 'anthem', 'showcase', 'maimed', 'blue', 'obeys',
'unreported', 'perusing', 'recalculate', 'rancher', 'demonic',
'Lilliputianize', 'approximation', 'repents', 'yellowness',
'irritates', 'Ferber', 'flashlights', 'booty', 'Neanderthal',
'someday', 'foregoes', 'lingering', 'cloudiness', 'guy', 'consumer',
'Berkowitz', 'relics', 'interpolating', 'reappearing', 'advisements',
'Nolan', 'turrets', 'skeletal', 'skills', 'mammas', 'Winsett',
'wheelings', 'stiffen', 'monkeys', 'plainness', 'braziers', 'Leary',
'advisee', 'jack', 'verb', 'reinterpret', 'geometrical', 'trolleys',
'arboreal', 'overpowered', 'Cuzco', 'poetical', 'admirations',
'Hobbes', 'phonemes', 'Newsweek', 'agitator', 'finally', 'prophets',
'environment', 'easterners', 'precomputed', 'faults', 'rankly',
'swallowing', 'crawl', 'trolley', 'spreading', 'resourceful', 'go',
'demandingly', 'broader', 'spiders', 'Marsha', 'debris', 'operates',
'Dundee', 'alleles', 'crunchier', 'quizzical', 'hanging', 'Fisk']

wordsd = {}
for word in words:
    wordsd[word] = 1


def collect_options(args, jobs, options):

    while args:
        arg = args.pop(0)
        if arg.startswith('-'):
            name = arg[1:]
            if name == 'options':
                fname = args.pop(0)
                d = {}
                execfile(fname, d)
                collect_options(list(d['options']), jobs, options)
            elif options.has_key(name):
                v = args.pop(0)
                if options[name] != None:
                    raise ValueError(
                        "Duplicate values for %s, %s and %s"
                        % (name, v, options[name])
                        )
                options[name] = v
            elif name == 'setup':
                options['setup'] = 1
            elif globals().has_key(name.capitalize()+'Job'):
                job = name
                kw = {}
                while args and args[0].find("=") > 0:
                    arg = args.pop(0).split('=')
                    name, v = arg[0], '='.join(arg[1:])
                    if kw.has_key(name):
                        raise ValueError(
                            "Duplicate parameter %s for job %s"
                            % (name, job)
                            )
                    kw[name]=v
                if kw.has_key('frequency'):
                    frequency = kw['frequency']
                    del kw['frequency']
                else:
                    frequency = 1

                if kw.has_key('sleep'):
                    sleep = float(kw['sleep'])
                    del kw['sleep']
                else:
                    sleep = 0.0001

                if kw.has_key('repeat'):
                    repeatp = float(kw['repeat'])
                    del kw['repeat']
                else:
                    repeatp = 0

                jobs.append((job, kw, frequency, sleep, repeatp))
            else:
                raise ValueError("not an option or job", name)
        else:
            raise ValueError("Expected an option", arg)


def find_lib_python():
    for b in os.getcwd(), os.path.split(sys.argv[0])[0]:
        for i in range(6):
            d = ['..']*i + ['lib', 'python']
            p = os.path.join(b, *d)
            if os.path.isdir(p):
                return p
    raise ValueError("Couldn't find lib/python")

def main(args=None):
    lib_python = find_lib_python()
    sys.path.insert(0, lib_python)

    if args is None:
        args = sys.argv[1:]
    if not args:
        print __doc__
        sys.exit(0)

    print args
    random.seed(hash(tuple(args))) # always use the same for the given args

    options = {"mbox": None, "threads": None}
    jobdefs = []
    collect_options(args, jobdefs, options)

    mboxes = {}
    if options["mbox"]:
        mboxes[options["mbox"]] = MBox(options["mbox"])

    if options.has_key('setup'):
        setup(lib_python)
    else:
        import Zope
        Zope.startup()

    #from ThreadedAsync.LoopCallback import loop
    #threading.Thread(target=loop, args=(), name='asyncore').start()

    jobs = JobProducer()
    for job, kw, frequency, sleep, repeatp in jobdefs:
        Job = globals()[job.capitalize()+'Job']
        if getattr(Job, 'needs_mbox', 0):
            if not kw.has_key("mbox"):
                if not options["mbox"]:
                    raise ValueError(
                        "no mailbox (mbox option) file  specified")
                kw['mbox'] = mboxes[options["mbox"]]
            else:
                if not mboxes.has_key[kw["mbox"]]:
                    mboxes[kw['mbox']] = MBox[kw['mbox']]
                kw["mbox"] = mboxes[kw['mbox']]
        jobs.add(Job(**kw), frequency, sleep, repeatp)

    if not jobs:
        print "No jobs to execute"
        return

    threads = int(options['threads'] or '0')
    if threads > 1:
        threads = [threading.Thread(target=run, args=(jobs, i), name=str(i))
                   for i in range(threads)]
        for thread in threads:
            thread.start()
        for thread in threads:
            thread.join()
    else:
        run(jobs)


if __name__ == '__main__':
    main()


=== Added File ZODB3/Tools/zeoserverlog.py ===
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Tools for analyzing ZEO Server logs.

This script contains a number of commands, implemented by command
functions. To run a command, give the command name and it's arguments
as arguments to this script.

Commands:

  blocked_times file threshold

     Output a summary of episodes where thransactions were blocked
     when the episode lasted at least threshold seconds.

     The file may be a file name or - to read from standard input.
     The file may also be a command:

       script blocked_times 'bunzip2 <foo.log.bz2' 60

     If the file is a command, it must contain at least a single
     space.

     The columns of output are:

     - The time the episode started

     - The seconds from the start of the episode until the blocking
       transaction finished.

     - The client id (host and port) of the blocking transaction.

     - The seconds from the start of the episode until the end of the
       episode.

  time_calls file threshold

     Time how long calls took. Note that this is normally combined
     with grep to time just a particulat kind of call:

       script time_calls 'bunzip2 <foo.log.bz2 | grep tpc_finish' 10

       time_trans threshold

     The columns of output are:

     - The time of the call invocation

     - The seconds from the call to the return

     - The client that made the call.

  time_trans file threshold

    Output a summary of transactions that held the global transaction
    lock for at least threshold seconds. (This is the time from when
    voting starts until the transaction is completed by the server.)

    The columns of output are:

    - time that the vote started.

    - client id

    - number of objects written / number of objects updated

    - seconds from tpc_begin to vote start

    - seconds spent voting

    - vote status: n=normal, d=delayed, e=error

    - seconds wating between vote return and finish call

    - time spent finishing or 'abort' if the transaction aborted

  minute file

    Compute production statistics by minute

    The columns of output are:

    - date/time

    - Number of active clients

    - number of reads

    - number of stores

    - number of commits (finish)

    - number of aborts

    - number of transactions (commits + aborts)

    Summary statistics are printed at the end

  minutes file

    Show just the summary statistics for production by minute.

  hour file

    Compute production statistics by hour

  hours file

    Show just the summary statistics for production by hour.

  day file

    Compute production statistics by day

  days file

    Show just the summary statistics for production by day.

  verify file

    Compute verification statistics

    The columns of output are:

    - client id
    - verification start time
    - number of object's verified
    - wall time to verify
    - average miliseconds to verify per object.

$Id: zeoserverlog.py,v 1.1.4.1 2003/09/04 16:47:23 jeremy Exp $
"""

import datetime, sys, re, os


def time(line):
    d = line[:10]
    t = line[11:19]
    y, mo, d = map(int, d.split('-'))
    h, mi, s = map(int, t.split(':'))
    return datetime.datetime(y, mo, d, h, mi, s)


def sub(t1, t2):
    delta = t2 - t1
    return delta.days*86400.0+delta.seconds+delta.microseconds/1000000.0



waitre = re.compile(r'Clients waiting: (\d+)')
idre = re.compile(r' ZSS:\d+/(\d+.\d+.\d+.\d+:\d+) ')
def blocked_times(args):
    f, thresh = args

    t1 = t2 = cid = blocking = waiting = 0
    last_blocking = False

    thresh = int(thresh)

    for line in xopen(f):
        line = line.strip()

        if line.endswith('Blocked transaction restarted.'):
            blocking = False
            waiting = 0
        else:
            s = waitre.search(line)
            if not s:
                continue
            waiting = int(s.group(1))
            blocking = line.find(
                'Transaction blocked waiting for storage') >= 0

        if blocking and waiting == 1:
            t1 = time(line)
            t2 = t1

        if not blocking and last_blocking:
            last_wait = 0
            t2 = time(line)
            cid = idre.search(line).group(1)

        if waiting == 0:
            d = sub(t1, time(line))
            if d >= thresh:
                print t1, sub(t1, t2), cid, d
            t1 = t2 = cid = blocking = waiting = last_wait = max_wait = 0

        last_blocking = blocking

connidre = re.compile(r' zrpc-conn:(\d+.\d+.\d+.\d+:\d+) ')
def time_calls(f):
    f, thresh = f
    if f == '-':
        f = sys.stdin
    else:
        f = xopen(f)

    thresh = float(thresh)
    t1 = None
    maxd = 0

    for line in f:
        line = line.strip()

        if ' calling ' in line:
            t1 = time(line)
        elif ' returns ' in line and t1 is not None:
            d = sub(t1, time(line))
            if d >= thresh:
                print t1, d, connidre.search(line).group(1)
            maxd = max(maxd, d)
            t1 = None

    print maxd

def xopen(f):
    if f == '-':
        return sys.stdin
    if ' ' in f:
        return os.popen(f, 'r')
    return open(f)

def time_tpc(f):
    f, thresh = f
    if f == '-':
        f = sys.stdin
    else:
        f = xopen(f)

    thresh = float(thresh)
    transactions = {}

    for line in f:
        line = line.strip()

        if ' calling vote(' in line:
            cid = connidre.search(line).group(1)
            transactions[cid] = time(line),
        elif ' vote returns None' in line:
            cid = connidre.search(line).group(1)
            transactions[cid] += time(line), 'n'
        elif ' vote() raised' in line:
            cid = connidre.search(line).group(1)
            transactions[cid] += time(line), 'e'
        elif ' vote returns ' in line:
            # delayed, skip
            cid = connidre.search(line).group(1)
            transactions[cid] += time(line), 'd'
        elif ' calling tpc_abort(' in line:
            cid = connidre.search(line).group(1)
            if cid in transactions:
                t1, t2, vs = transactions[cid]
                t = time(line)
                d = sub(t1, t)
                if d >= thresh:
                    print 'a', t1, cid, sub(t1, t2), vs, sub(t2, t)
                del transactions[cid]
        elif ' calling tpc_finish(' in line:
            if cid in transactions:
                cid = connidre.search(line).group(1)
                transactions[cid] += time(line),
        elif ' tpc_finish returns ' in line:
            if cid in transactions:
                t1, t2, vs, t3 = transactions[cid]
                t = time(line)
                d = sub(t1, t)
                if d >= thresh:
                    print 'c', t1, cid, sub(t1, t2), vs, sub(t2, t3), sub(t3, t)
                del transactions[cid]


newobre = re.compile(r"storea\(.*, '\\x00\\x00\\x00\\x00\\x00")
def time_trans(f):
    f, thresh = f
    if f == '-':
        f = sys.stdin
    else:
        f = xopen(f)

    thresh = float(thresh)
    transactions = {}

    for line in f:
        line = line.strip()

        if ' calling tpc_begin(' in line:
            cid = connidre.search(line).group(1)
            transactions[cid] = time(line), [0, 0]
        if ' calling storea(' in line:
            cid = connidre.search(line).group(1)
            if cid in transactions:
                transactions[cid][1][0] += 1
                if not newobre.search(line):
                    transactions[cid][1][1] += 1

        elif ' calling vote(' in line:
            cid = connidre.search(line).group(1)
            if cid in transactions:
                transactions[cid] += time(line),
        elif ' vote returns None' in line:
            cid = connidre.search(line).group(1)
            if cid in transactions:
                transactions[cid] += time(line), 'n'
        elif ' vote() raised' in line:
            cid = connidre.search(line).group(1)
            if cid in transactions:
                transactions[cid] += time(line), 'e'
        elif ' vote returns ' in line:
            # delayed, skip
            cid = connidre.search(line).group(1)
            if cid in transactions:
                transactions[cid] += time(line), 'd'
        elif ' calling tpc_abort(' in line:
            cid = connidre.search(line).group(1)
            if cid in transactions:
                try:
                    t0, (stores, old), t1, t2, vs = transactions[cid]
                except ValueError:
                    pass
                else:
                    t = time(line)
                    d = sub(t1, t)
                    if d >= thresh:
                        print t1, cid, "%s/%s" % (stores, old), \
                              sub(t0, t1), sub(t1, t2), vs, \
                              sub(t2, t), 'abort'
                del transactions[cid]
        elif ' calling tpc_finish(' in line:
            if cid in transactions:
                cid = connidre.search(line).group(1)
                transactions[cid] += time(line),
        elif ' tpc_finish returns ' in line:
            if cid in transactions:
                t0, (stores, old), t1, t2, vs, t3 = transactions[cid]
                t = time(line)
                d = sub(t1, t)
                if d >= thresh:
                    print t1, cid, "%s/%s" % (stores, old), \
                          sub(t0, t1), sub(t1, t2), vs, \
                          sub(t2, t3), sub(t3, t)
                del transactions[cid]

def minute(f, slice=16, detail=1, summary=1):
    f, = f

    if f == '-':
        f = sys.stdin
    else:
        f = xopen(f)

    mlast = r = s = c = a = cl = None
    rs = []
    ss = []
    cs = []
    as = []
    ts = []
    cls = []

    for line in f:
        line = line.strip()
        if (line.find('returns') > 0
            or line.find('storea') > 0
            or line.find('tpc_abort') > 0
            ):
            client = connidre.search(line).group(1)
            m = line[:slice]
            if m != mlast:
                if mlast:
                    if detail:
                        print mlast, len(cl), r, s, c, a, a+c
                    cls.append(len(cl))
                    rs.append(r)
                    ss.append(s)
                    cs.append(c)
                    as.append(a)
                    ts.append(c+a)
                mlast = m
                r = s = c = a = 0
                cl = {}
            if line.find('zeoLoad') > 0:
                r += 1
                cl[client] = 1
            elif line.find('storea') > 0:
                s += 1
                cl[client] = 1
            elif line.find('tpc_finish') > 0:
                c += 1
                cl[client] = 1
            elif line.find('tpc_abort') > 0:
                a += 1
                cl[client] = 1

    if mlast:
        if detail:
            print mlast, len(cl), r, s, c, a, a+c
        cls.append(len(cl))
        rs.append(r)
        ss.append(s)
        cs.append(c)
        as.append(a)
        ts.append(c+a)

    if summary:
        print
        print 'Summary:     \t', '\t'.join(('min', '10%', '25%', 'med',
                                            '75%', '90%', 'max', 'mean'))
        print "n=%6d\t" % len(cls), '-'*62
        print 'Clients: \t', '\t'.join(map(str,stats(cls)))
        print 'Reads:   \t', '\t'.join(map(str,stats( rs)))
        print 'Stores:  \t', '\t'.join(map(str,stats( ss)))
        print 'Commits: \t', '\t'.join(map(str,stats( cs)))
        print 'Aborts:  \t', '\t'.join(map(str,stats( as)))
        print 'Trans:   \t', '\t'.join(map(str,stats( ts)))

def stats(s):
    s.sort()
    min = s[0]
    max = s[-1]
    n = len(s)
    out = [min]
    ni = n + 1
    for p in .1, .25, .5, .75, .90:
        lp = ni*p
        l = int(lp)
        if lp < 1 or lp > n:
            out.append('-')
        elif abs(lp-l) < .00001:
            out.append(s[l-1])
        else:
            out.append(int(s[l-1] + (lp - l) * (s[l] - s[l-1])))

    mean = 0.0
    for v in s:
        mean += v

    out.extend([max, int(mean/n)])

    return out

def minutes(f):
    minute(f, 16, detail=0)

def hour(f):
    minute(f, 13)

def day(f):
    minute(f, 10)

def hours(f):
    minute(f, 13, detail=0)

def days(f):
    minute(f, 10, detail=0)


new_connection_idre = re.compile(r"new connection \('(\d+.\d+.\d+.\d+)', (\d+)\):")
def verify(f):
    f, = f

    if f == '-':
        f = sys.stdin
    else:
        f = xopen(f)

    t1 = None
    nv = {}
    for line in f:
        if line.find('new connection') > 0:
            m = new_connection_idre.search(line)
            cid = "%s:%s" % (m.group(1), m.group(2))
            nv[cid] = [time(line), 0]
        elif line.find('calling zeoVerify(') > 0:
            cid = connidre.search(line).group(1)
            nv[cid][1] += 1
        elif line.find('calling endZeoVerify()') > 0:
            cid = connidre.search(line).group(1)
            t1, n = nv[cid]
            if n:
                d = sub(t1, time(line))
                print cid, t1, n, d, n and (d*1000.0/n) or '-'

def recovery(f):
    f, = f

    if f == '-':
        f = sys.stdin
    else:
        f = xopen(f)

    last = ''
    trans = []
    n = 0
    for line in f:
        n += 1
        if line.find('RecoveryServer') < 0:
            continue
        l = line.find('sending transaction ')
        if l > 0 and last.find('sending transaction ') > 0:
            trans.append(line[l+20:].strip())
        else:
            if trans:
                if len(trans) > 1:
                    print "  ... %s similar records skipped ..." % (
                        len(trans) - 1)
                    print n, last.strip()
                trans=[]
            print n, line.strip()
        last = line

    if len(trans) > 1:
        print "  ... %s similar records skipped ..." % (
            len(trans) - 1)
        print n, last.strip()



if __name__ == '__main__':
    globals()[sys.argv[1]](sys.argv[2:])


=== Added File ZODB3/Tools/timeout.py ===
#! /usr/bin/env python

"""Transaction timeout test script.

This script connects to a storage, begins a transaction, calls store()
and tpc_vote(), and then sleeps forever.  This should trigger the
transaction timeout feature of the server.

usage: timeout.py address delay [storage-name]

"""

import sys
import time

from ZODB.Transaction import Transaction
from ZODB.tests.MinPO import MinPO
from ZODB.tests.StorageTestBase import zodb_pickle
from ZEO.ClientStorage import ClientStorage

ZERO = '\0'*8

def main():
    if len(sys.argv) not in (3, 4):
        sys.stderr.write("Usage: timeout.py address delay [storage-name]\n" %
                         sys.argv[0])
        sys.exit(2)

    hostport = sys.argv[1]
    delay = float(sys.argv[2])
    if sys.argv[3:]:
        name = sys.argv[3]
    else:
        name = "1"

    if "/" in hostport:
        address = hostport
    else:
        if ":" in hostport:
            i = hostport.index(":")
            host, port = hostport[:i], hostport[i+1:]
        else:
            host, port = "", hostport
        port = int(port)
        address = (host, port)

    print "Connecting to %s..." % repr(address)
    storage = ClientStorage(address, name)
    print "Connected.  Now starting a transaction..."

    oid = storage.new_oid()
    version = ""
    revid = ZERO
    data = MinPO("timeout.py")
    pickled_data = zodb_pickle(data)
    t = Transaction()
    t.user = "timeout.py"
    storage.tpc_begin(t)
    storage.store(oid, revid, pickled_data, version, t)
    print "Stored.  Now voting..."
    storage.tpc_vote(t)

    print "Voted; now sleeping %s..." % delay
    time.sleep(delay)
    print "Done."

if __name__ == "__main__":
    main()


=== Added File ZODB3/Tools/README.txt ===
This directory contains a collect of utilities for managing ZODB
databases.  Some are more useful than others.  If you install ZODB
using distutils ("python setup.py install"), fsdump.py, fstest.py,
repozo.py, and zeopack.py will be installed in /usr/local/bin.

Unless otherwise noted, these scripts are invoked with the name of the
Data.fs file as their only argument.  Example: checkbtrees.py data.fs.


analyze.py -- A transaction analyzer for FileStorage

Reports on the data in a FileStorage.  The report is organized by
class.  It shows total data, as well as separate reports for current
and historical revisions of objects.


checkbtrees.py -- Checks BTrees in a FileStorage for corruption.

Attempts to find all the BTrees contained in a Data.fs and calls their
_check() methods.


fsdump.py -- Summarize FileStorage contents, one line per revision.

Prints a report of FileStorage contents, with one line for each
transaction and one line for each data record in that transaction.
Includes time stamps, file positions, and class names.


fstest.py -- Simple consistency checker for FileStorage

usage: fstest.py [-v] data.fs

The fstest tool will scan all the data in a FileStorage and report an
error if it finds any corrupt transaction data.  The tool will print a
message when the first error is detected an exit.

The tool accepts one or more -v arguments.  If a single -v is used, it
will print a line of text for each transaction record it encounters.
If two -v arguments are used, it will also print a line of text for
each object.  The objects for a transaction will be printed before the
transaction itself.

Note: It does not check the consistency of the object pickles.  It is
possible for the damage to occur only in the part of the file that
stores object pickles.  Those errors will go undetected.


netspace.py -- Hackish attempt to report on size of objects

usage: netspace.py [-P | -v] data.fs

-P: do a pack first
-v: print info for all objects, even if a traversal path isn't found

Traverses objects from the database root and attempts to calculate
size of object, including all reachable subobjects.


parsezeolog.py -- Parse BLATHER logs from ZEO server.

This script may be obsolete.  It has not been tested against the
current log output of the ZEO server.

Reports on the time and size of transactions committed by a ZEO
server, by inspecting log messages at BLATHER level.


repozo.py -- Incremental backup utility for FileStorage.

Run the script with the -h option to see usage details.


timeout.py -- Script to test transaction timeout

usage: timeout.py address delay [storage-name]

This script connects to a storage, begins a transaction, calls store()
and tpc_vote(), and then sleeps forever.  This should trigger the
transaction timeout feature of the server.


zeopack.py -- Script to pack a ZEO server.

The script connects to a server and calls pack() on a specific
storage.  See the script for usage details.


zeoreplay.py -- Experimental script to replay transactions from a ZEO log.

Like parsezeolog.py, this may be obsolete because it was written
against an earlier version of the ZEO server.  See the script for
usage details.


zeoup.py

Usage: zeoup.py [options]

The test will connect to a ZEO server, load the root object, and
attempt to update the zeoup counter in the root.  It will report
success if it updates to counter or if it gets a ConflictError.  A
ConflictError is considered a success, because the client was able to
start a transaction.

See the script for details about the options.


zodbload.py - exercise ZODB under a heavy synthesized Zope-like load

See the module docstring for details.  Note that this script requires
Zope.  New in ZODB3 3.1.4.


zeoserverlog.py - analyze ZEO server log for performance statistics

See the module docstring for details; there are a large number of
options.  New in ZODB3 3.1.4.

=== ZODB3/Tools/zeoup.py 1.13 => 1.13.14.1 ===
--- ZODB3/Tools/zeoup.py:1.13	Tue Dec 10 13:44:41 2002
+++ ZODB3/Tools/zeoup.py	Thu Sep  4 12:47:23 2003
@@ -1,7 +1,7 @@
 #! /usr/bin/env python
 """Make sure a ZEO server is running.
 
-Usage: zeoup.py [options]
+usage: zeoup.py [options]
 
 The test will connect to a ZEO server, load the root object, and attempt to
 update the zeoup counter in the root.  It will report success if it updates
@@ -11,7 +11,7 @@
 Options:
 
     -p port -- port to connect to
-    
+
     -h host -- host to connect to (default is current host)
 
     -S storage -- storage name (default '1')


=== ZODB3/Tools/zeopack.py 1.8 => 1.8.12.1 ===
--- ZODB3/Tools/zeopack.py:1.8	Tue Jan 28 16:20:40 2003
+++ ZODB3/Tools/zeopack.py	Thu Sep  4 12:47:23 2003
@@ -6,11 +6,11 @@
 Options:
 
     -p port -- port to connect to
-    
+
     -h host -- host to connect to (default is current host)
-    
+
     -U path -- Unix-domain socket to connect to
-    
+
     -S name -- storage name (default is '1')
 
     -d days -- pack objects more than days old


=== ZODB3/Tools/netspace.py 1.1 => 1.1.40.1 ===
--- ZODB3/Tools/netspace.py:1.1	Fri May  3 16:33:22 2002
+++ ZODB3/Tools/netspace.py	Thu Sep  4 12:47:23 2003
@@ -89,7 +89,7 @@
         keys = filter(paths.has_key, keys)
 
     fmt = "%8s %5d %8d %s %s.%s"
-    
+
     for oid in keys:
         data, serialno = fs.load(oid, '')
         mod, klass = get_pickle_metadata(data)


=== ZODB3/Tools/fstest.py 1.9 => 1.9.4.1 ===
--- ZODB3/Tools/fstest.py:1.9	Tue Apr 22 13:58:24 2003
+++ ZODB3/Tools/fstest.py	Thu Sep  4 12:47:23 2003
@@ -4,14 +4,14 @@
 #
 # Copyright (c) 2001, 2002 Zope Corporation and Contributors.
 # All Rights Reserved.
-# 
+#
 # This software is subject to the provisions of the Zope Public License,
 # Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
 # FOR A PARTICULAR PURPOSE
-# 
+#
 ##############################################################################
 
 """Simple consistency checker for FileStorage.
@@ -109,7 +109,7 @@
     It also leaves the file pointer set to pos.  The path argument is
     used for generating error messages.
     """
-    
+
     h = file.read(TREC_HDR_LEN)
     if not h:
         return None, None
@@ -131,7 +131,7 @@
         raise FormatError("%s truncated possibly because of"
                           " damaged records at %s" % (path, pos))
     if status == Status.checkpoint:
-        raise FormatError("%s checkpoint flag was not cleared at %s" 
+        raise FormatError("%s checkpoint flag was not cleared at %s"
                           % (path, pos))
     if status not in ' up':
         raise FormatError("%s has invalid status '%s' at %s" %


=== ZODB3/Tools/checkbtrees.py 1.1 => 1.1.28.1 ===
--- ZODB3/Tools/checkbtrees.py:1.1	Thu Jun 20 18:49:50 2002
+++ ZODB3/Tools/checkbtrees.py	Thu Sep  4 12:47:23 2003
@@ -3,18 +3,33 @@
 
 usage: checkbtrees.py data.fs
 
-Try to find all the BTrees in a Data.fs and call their _check() methods.
+Try to find all the BTrees in a Data.fs, call their _check() methods,
+and run them through BTrees.check.check().
 """
 
 from types import IntType
 
 import ZODB
 from ZODB.FileStorage import FileStorage
+from BTrees.check import check
+
+# Set of oids we've already visited.  Since the object structure is
+# a general graph, this is needed to prevent unbounded paths in the
+# presence of cycles.  It's also helpful in eliminating redundant
+# checking when a BTree is pointed to by many objects.
+oids_seen = {}
+
+# Append (obj, path) to L if and only if obj is a persistent object
+# and we haven't seen it before.
+def add_if_new_persistent(L, obj, path):
+    global oids_seen
 
-def add_if_persistent(L, obj, path):
     getattr(obj, '_', None) # unghostify
     if hasattr(obj, '_p_oid'):
-        L.append((obj, path))
+        oid = obj._p_oid
+        if not oids_seen.has_key(oid):
+            L.append((obj, path))
+            oids_seen[oid] = 1
 
 def get_subobjects(obj):
     getattr(obj, '_', None) # unghostify
@@ -25,7 +40,7 @@
         attrs = ()
     for pair in attrs:
         sub.append(pair)
-        
+
     # what if it is a mapping?
     try:
         items = obj.items()
@@ -54,7 +69,7 @@
     cn = ZODB.DB(fs).open()
     rt = cn.root()
     todo = []
-    add_if_persistent(todo, rt, '')
+    add_if_new_persistent(todo, rt, '')
 
     found = 0
     while todo:
@@ -75,6 +90,13 @@
                     print msg
                     print "*" * 60
 
+                try:
+                    check(obj)
+                except AssertionError, msg:
+                    print "*" * 60
+                    print msg
+                    print "*" * 60
+
         if found % 100 == 0:
             cn.cacheMinimize()
 
@@ -84,7 +106,7 @@
                 newpath = "%s%s" % (path, k)
             else:
                 newpath = "%s.%s" % (path, k)
-            add_if_persistent(todo, v, newpath)
+            add_if_new_persistent(todo, v, newpath)
 
     print "total", len(fs._index), "found", found
 


=== ZODB3/Tools/analyze.py 1.1 => 1.1.28.1 ===
--- ZODB3/Tools/analyze.py:1.1	Mon Aug 26 14:29:58 2002
+++ ZODB3/Tools/analyze.py	Thu Sep  4 12:47:23 2003
@@ -137,4 +137,3 @@
 if __name__ == "__main__":
     path = sys.argv[1]
     report(analyze(path))
-    

=== Removed File ZODB3/Tools/space.py ===




More information about the Zodb-checkins mailing list