[Zope-Checkins] CVS: Zope/lib/python/Products/CallProfiler - .cvsignore:1.1.2.1 CallProfiler.py:1.1.2.1 README.txt:1.1.2.1 TODO.txt:1.1.2.1 __init__.py:1.1.2.1 make_dist.sh:1.1.2.1 profiler.py:1.1.2.1 refresh.txt:1.1.2.1 run_tests:1.1.2.1 version.txt:1.1.2.1

Anthony Baxter anthony@interlink.com.au
Fri, 17 May 2002 01:26:55 -0400


Update of /cvs-repository/Zope/lib/python/Products/CallProfiler
In directory cvs.zope.org:/tmp/cvs-serv26935/CallProfiler

Added Files:
      Tag: anthony-CallProfiler-branch
	.cvsignore CallProfiler.py README.txt TODO.txt __init__.py 
	make_dist.sh profiler.py refresh.txt run_tests version.txt 
Log Message:
Initial checkin of CallProfiler branch. This is the internal ekit version.

Added since the 1.4 release:

  profiler_cache_hook - hooks into the ZCache stuff for hit/miss counts
  sorting on the summary page
  Add / replace a profileable module "on the fly"



=== Added File Zope/lib/python/Products/CallProfiler/.cvsignore ===
*.pyc
*.pyo
*.gz


=== Added File Zope/lib/python/Products/CallProfiler/CallProfiler.py === (467/567 lines abridged)
# Copyright (c) 2002 ekit.com Inc (http://www.ekit-inc.com/)
# 
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# 
#   The above copyright notice and this permission notice shall be included in
#   all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

# $Id: CallProfiler.py,v 1.1.2.1 2002/05/17 05:26:53 anthony Exp $

from zLOG import LOG, ERROR, INFO
from Globals import InitializeClass, HTMLFile
from OFS.SimpleItem import Item
from Acquisition import Implicit
from Persistence import Persistent
from AccessControl import ClassSecurityInfo
from AccessControl import ModuleSecurityInfo
modulesecurity = ModuleSecurityInfo()

import cStringIO
import time, operator
from thread import get_ident

# get the profiler store
from profiler import profiler

def profiler_call_hook(self, *args, **kw):
    '''A call hook
    '''
    mt = self.meta_type
    sid = self.getId()
    profiler.startCall(mt, sid)
    try:
        return self.profiler_call_original(*args, **kw)
    finally:
        profiler.endCall()

def profiler_publish_hook(request, *args, **kw):

[-=- -=- -=- 467 lines omitted -=- -=- -=-]

# Revision 1.10  2002/02/07 23:12:48  rjones
# Fixes to the data gathering and display
#
# Revision 1.9  2002/02/07 05:09:11  rjones
# Better call stack handling
#
# Revision 1.8  2002/02/06 00:33:55  rjones
# Lots of data handling improvements:
#   . moved the data handling part off into a separate module
#   . that module has some basic unit tests
#
# Revision 1.7  2002/02/05 22:11:02  rjones
# Fixes
#
# Revision 1.6  2002/02/05 04:50:13  rjones
# Fixes, aggregation, oh my! :)
#
# Revision 1.5  2002/02/01 05:42:17  rjones
# fixes
#
# Revision 1.4  2002/01/31 23:11:36  rjones
# copyright and CVS comment cleanups
#
# Revision 1.3  2002/01/31 06:19:17  rjones
# Now adds itself to the Control Panel, and isn't available for adding elsewhere.
#
# Revision 1.2  2002/01/31 05:03:27  rjones
# adding CallProfiler to HEAD
#
# Revision 1.1.2.6  2002/01/31 04:16:33  rjones
# Some more cleanups
#
# Revision 1.1.2.5  2002/01/31 04:09:42  rjones
# More cleanups in the profiler code so we can get at the data more easily
#
# Revision 1.1.2.4  2002/01/31 00:50:08  rjones
# Profiler now patches the Zope modules as required:
#  . ZPublisher/Publish.py publish function
#  . others as defined in the profileable_modules dict in CallProfiler.py
#
# Revision 1.1.2.3  2002/01/30 07:36:00  rjones
# split off the processing code from the display code - should be easy enough
# to do the render in ZPT or *shudder* DTML.
#
# Revision 1.1.2.2  2002/01/30 05:41:33  rjones
# cosmetic changes
#
# Revision 1.1.2.1  2002/01/30 04:48:38  rjones
# CallProfiler initial version
#


=== Added File Zope/lib/python/Products/CallProfiler/README.txt ===
Call Profiler Documentation (the minimalist approach)
===========================

Purpose:
   To monitor the chain of DTML, ZSQL, ZPT, PythonMethod, PythonScript, ...
calls in a Zope request and gather timing information, for the purpose of
identifying hot-spots for potential optimisation.


Usage:
   Once the product is installed in your Products directory and Zope has
been restarted, visit the Call Profiler link in your Control Panel.

   In the configuration tab, check the types of documents you wish to
obtain the timing information for. You may also clear any previously
gathered timing information using the "clear" buttons.

   Once you have selected the documents to monitor and have clicked
"Monitor selected calls", load up the pages you wish to profile. Once
they're loaded (or even as they're loading :) you should visit the results
tab. There you will find a list of the requests made by browsers with some
timing information:
   . clicking the url will load the page requested
   . clicking on the time of the request will bring up a blow-by-blow
     breakdown of the documents called to form the request.

   You may also see the requests aggregated by URL on the "Requests By URL"
tab - giving the minimum, average and maximum times for the responses. You
may:
   . click the url to load the page requested
   . click the session times to see the detailed breakdown of the request

   The detailed view is set to highlight calls that broach 3% (yellow), 5%
(orange) and 10% (red) of the total request time.

   If a given document call has sub-calls:
   . the '...' times indicate the time spent in the call (between sub-calls)
   . the (end) time gives two timings:
     1. the first is the time spent in the call not including sub-calls
     2. the second time is the total time of the call including sub-calls


Note:
   All profiling information is lost when either:
    1. Zope is restarted, or
    2. the Call Profiler product's code is re-loaded.

   Also, don't leave the profiler on for too long - it uses a boundless
memory-based store for the timing values. It will use up all your memory
eventually (though we haven't done any testing to determine how long that
might be).


License
=======
Copyright (c) ekit.com Inc (http://www.ekit-inc.com/)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

This product includes software developed by Digital Creations for use in
the Z Object Publishing Environment (http://www.zope.org/).
(specifically, we use the control panel installation code from LeakFinder in
 the __init__.py module)



=== Added File Zope/lib/python/Products/CallProfiler/TODO.txt ===
TODO

. determine the pecentages for highlighting dynamically
  - if there's < 5 calls, red is >50%
  - if there's < 10 calls, red is >20%?
  - else red is >20%?

. stuff the results away in the ODB when a request is finished

. allow auto-culling or auto-aggregation of results when it's left on for a
  long time

. include a stringification of PARENTS and getPhysicalPath to indicate where
  particular methods are


DONE

. sorting of columns in the results

. clean up the data storage so I can reduce the computation time

. better call graph display

. url lopping when > 100 chars

. detect caching hit/miss



=== Added File Zope/lib/python/Products/CallProfiler/__init__.py ===
# Copyright (c) ekit.com Inc (http://www.ekit-inc.com/)
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# 
#   The above copyright notice and this permission notice shall be included in
#   all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

# This product includes software developed by Digital Creations for use in
# the Z Object Publishing Environment (http://www.zope.org/).
# (specifically, we use the control panel installation code from LeakFinder)

__version__ = '1.6'

def installControlPanel(context, panelClass):
    from App.ApplicationManager import ApplicationManager
    from Acquisition import aq_base
    app = context._ProductContext__app
    cp = app.Control_Panel
    id = panelClass.id
    if 0: # Enable to clean up the control panel.
        try: del cp._objects
        except: pass
    cp.id # Unghostify.
    if hasattr(cp, id):
        return cp._getOb(id)
    if cp.__dict__.has_key('_objects'):
        # _objects has been overridden.  We have to persist.
        existing = getattr(aq_base(cp), id, None)
        if existing is None or existing.__class__ != panelClass:
            cp._setObject(id, panelClass())
    else:
        # Don't persist what we don't have to.
        objects = ApplicationManager._objects
        objects = filter(lambda o, id=id: o['id'] != id, objects)
        ApplicationManager._objects = objects + (
            {'id':id, 'meta_type':panelClass.meta_type},)
        try: delattr(cp, id)
        except: pass
        setattr(ApplicationManager, id, panelClass())
    return cp._getOb(id)

def initialize(context):
    import CallProfiler

    installControlPanel(context, CallProfiler.CallProfiler)

    import Globals, OFS
    CallProfiler.CallProfiler.icon = 'misc_/CallProfiler/profiler.gif'
    icon = Globals.ImageFile('www/profiler.gif', globals())
    icon.__roles__=None
    if not hasattr(OFS.misc_.misc_, 'CallProfiler'):
        setattr(OFS.misc_.misc_, 'CallProfiler',
	    OFS.misc_.Misc_('CallProfiler', {}))
    getattr(OFS.misc_.misc_, 'CallProfiler')['profiler.gif']=icon

    context.registerClass(
	CallProfiler, meta_type = 'Call Profiler',
	constructors = (CallProfiler.manage_addForm, CallProfiler.manage_add)
    )



=== Added File Zope/lib/python/Products/CallProfiler/make_dist.sh ===
#!/bin/sh

# figure the dist name
name=`cat version.txt`
echo "Building $name.tar.gz"

# make the dist dir
rm -rf dist
mkdir dist
mkdir dist/CallProfiler
mkdir dist/CallProfiler/dtml
mkdir dist/CallProfiler/www
mkdir dist/CallProfiler/test

# copy in the dist files
cp *.py *.txt run_tests dist/CallProfiler
cp dtml/*.dtml dist/CallProfiler/dtml/
cp www/*.* dist/CallProfiler/www/
cp test/*.py dist/CallProfiler/test/

# package
(cd dist; tar cf ${name}.tar CallProfiler; gzip ${name}.tar)
mv dist/${name}.tar.gz .

# cleanup
rm -rf dist



=== Added File Zope/lib/python/Products/CallProfiler/profiler.py === (472/572 lines abridged)
# Copyright (c) 2002 ekit.com Inc (http://www.ekit-inc.com/)
# 
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# 
#   The above copyright notice and this permission notice shall be included in
#   all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

# $Id: profiler.py,v 1.1.2.1 2002/05/17 05:26:53 anthony Exp $

import time, operator
from thread import get_ident

class Profiler:
    def __init__(self):
        self.reset()

    def reset(self):
        '''Reset the dicts
        '''
        # these vars hold the timing values
        self.thread = {}        # maps thread IDs to TID
        self.transaction = {}   # holds the full transaction info by TID

    def hasTID(self, tid):
        return self.transaction.has_key(tid)

    def startRequest(self, request):
        '''Register the start of a request (called by the Publisher)

           The StartDict entry will be transferred to the TimingDict once
           we have a valid timing mark (so it's a request we care about).
        '''
        tid = '%s:%s'%(time.time(), id(request))
        self.thread[get_ident()] = tid
        self.transaction[tid] = Transaction(tid, request.URL+request.PATH_INFO)

    def endRequest(self):

[-=- -=- -=- 472 lines omitted -=- -=- -=-]

                l.append('%-4s %-2s %-12s %-3s %-5s %s'%(
                    '.'*depth, event['ave_time_elapsed'], event['object'],
                    event['ave_time_total'], perc, proc))
            else:
                l.append('%-4s     %-12s %-3s %-5s'%(
                    '.'*depth, '(processing)', event['ave_time_total'],
                    perc))

        return '\n'.join(l)

    def __getitem__(self, name):
        '''Make this object look like one of the event info dicts
        '''
        if name == 'events': return self.events
        raise KeyError, name
#
# $Log: profiler.py,v $
# Revision 1.1.2.1  2002/05/17 05:26:53  anthony
# Initial checkin of CallProfiler branch. This is the internal ekit version.
#
# Added since the 1.4 release:
#
#   profiler_cache_hook - hooks into the ZCache stuff for hit/miss counts
#   sorting on the summary page
#   Add / replace a profileable module "on the fly"
#
# Revision 1.7  2002/02/14 05:54:21  rjones
# Handles caching, has sortable display columns
#
# Revision 1.6  2002/02/13 00:04:30  rjones
# *** empty log message ***
#
# Revision 1.5  2002/02/12 23:40:42  rjones
# added cache hit/miss detection
#
# Revision 1.4  2002/02/08 00:06:33  rjones
# added call counter
#
# Revision 1.3  2002/02/07 23:12:49  rjones
# Fixes to the data gathering and display
#
# Revision 1.2  2002/02/07 05:09:11  rjones
# Better call stack handling
#
# Revision 1.1  2002/02/06 00:33:55  rjones
# Lots of data handling improvements:
#   . moved the data handling part off into a separate module
#   . that module has some basic unit tests
#
#


=== Added File Zope/lib/python/Products/CallProfiler/refresh.txt ===


=== Added File Zope/lib/python/Products/CallProfiler/run_tests ===
#! /usr/bin/env python2

import test
test.go()


=== Added File Zope/lib/python/Products/CallProfiler/version.txt ===
CallProfiler-1.6