Measuring performance with pstats

Hi,

I am relatively new to panda and have been writing a small simulation using panda to get my hands wet. I was using the pstats tool to measure how much cpu time is being used in some of my game loops. One thing I ran into was the decorator pattern described here panda3d.org/manual/index.php/M … ith_PStats

I was using it to time some of my functions since for some reason the default collector don’t seem to show stats for my innermost functions it seems to stop at 2 levels deep. I was using text-stats to see since the pretty UI is not available on Mac:-.

The problem I ran into was multiple classes have similar named functions and the decorator in the twiki doesn’t seem to print the class name. As a result if two classes have a function named “move” it gets accumulated into the same collector.

So after reading up a bit about python decorators I came up with a slightly altered class based decorator that does the trick. Here’s the code for it, I altered the code a bit so as not to try and store the customCollectors array in the base object but instead made it a top level dictionary of its own.

#A simple decorator function to invoke custom collectors on individual
#functions that need to be profiled.

code form panda3d.org/manual/index.php/M … ith_PStats

from panda3d.core import *
import builtin

builtin.customCollectors = {}

class pstat_obj(object):
from pandac.PandaModules import PStatCollector
def init(self,name):
self.classname = name
self._wrapped = None

def __call__(self,wrapped):
    self._wrapped = wrapped
    collectorName = "Debug:%s:%s" % (self.classname,wrapped.__name__)
    if collectorName in customCollectors.keys():
        self.pstat = customCollectors[collectorName]
    else:
        customCollectors[collectorName] = PStatCollector(collectorName)
        self.pstat = customCollectors[collectorName]
    
    def callWrapped(instance,*args,**kwargs):
        self.pstat.start()
        returned = self._wrapped(instance,*args,**kwargs)
        self.pstat.stop()
        return returned 
    return callWrapped

The way one would use it is

class x:
@pstat_obj(“x”)
def a(self):

This would create a collector named “Debug:x:a” which gets nicely split up by the text-stats and allows me to collect more class level function stats.

Feel free to use it or update the twiki with this version if people feel it works. My Python is still a bit rusty so there may be a better way of doing this. If so please let me know:)

I did something similar using the module name.
Of course this only works if you have your code split up into modules, but for me almost every class has it’s own file.
My goal was to keep it fully automatic with no parameters like the original.

pstat_debug.py

from pandac.PandaModules import PStatCollector


def do_pstat(func, collectorName):
    if hasattr(base, 'custom_collectors'):
        if collectorName in base.custom_collectors.keys():
            pstat = base.custom_collectors[collectorName]
        else:
            base.custom_collectors[collectorName] = PStatCollector(collectorName)
            pstat = base.custom_collectors[collectorName]
    else:
        base.custom_collectors = {}
        base.custom_collectors[collectorName] = PStatCollector(collectorName)
        pstat = base.custom_collectors[collectorName]
    def doPstat(*args, **kargs):
        pstat.start()
        returned = func(*args, **kargs)
        pstat.stop()
        return returned
    doPstat.__name__ = func.__name__
    doPstat.__dict__ = func.__dict__
    doPstat.__doc__ = func.__doc__
    return doPstat


def pstat_debug(func):
    return do_pstat(func, '%s:%s' % (func.__module__, func.__name__))

your_class_module.py

from pstat_debug import *

class your_class(object):
    @pstat_debug
    def your_function(self):
        print 'No, I have no tomatoes.'
    @pstat_debug
    def another_function(self):
        print 'Yes, we have no bananas.'