groundwork for FrameProfiler

This commit is contained in:
Darren Ranalli 2008-10-22 00:20:09 +00:00
parent db4b14aaf7
commit 7bb3598712
5 changed files with 151 additions and 62 deletions

View File

@ -1,4 +1,5 @@
from pandac.libpandaexpressModules import TrueClock
from direct.directnotify.DirectNotifyGlobal import directNotify
from direct.showbase.PythonUtil import (
StdoutCapture, _installProfileCustomFuncs,_removeProfileCustomFuncs,
_profileWithoutGarbageLeak, _getProfileResultFileInfo, _setProfileResultsFileInfo,
@ -84,15 +85,14 @@ class ProfileSession:
#
# implementation sidesteps memory leak in Python profile module,
# and redirects file output to RAM file for efficiency
DefaultFilename = 'profilesession'
DefaultLines = 80
DefaultSorts = ['cumulative', 'time', 'calls']
TrueClock = TrueClock.getGlobalPtr()
def __init__(self, func, name):
notify = directNotify.newCategory("ProfileSession")
def __init__(self, name, func=None, logAfterProfile=False):
self._func = func
self._name = name
self._logAfterProfile = logAfterProfile
self._filenameBase = 'profileData-%s-%s' % (self._name, id(self))
self._refCount = 0
self._reset()
@ -126,17 +126,24 @@ class ProfileSession:
self._filename2ramFile = {}
self._stats = None
self._resultCache = {}
# if true, accumulate profile results every time we run
# if false, throw out old results every time we run
self._aggregate = False
self._lines = 200
self._sorts = ['cumulative', 'time', 'calls']
self._callInfo = False
self._totalTime = None
def _getNextFilename(self):
filename = '%s-%s' % (self._filenameBase, self._filenameCounter)
self._filenameCounter += 1
return filename
def run(self, aggregate=True):
def run(self):
# make sure this instance doesn't get destroyed inside self._func
self.acquire()
if not aggregate:
if not self._aggregate:
self._reset()
# if we're already profiling, just run the func and don't profile
@ -146,6 +153,7 @@ class ProfileSession:
self._duration = 0.
else:
# put the function in the global namespace so that profile can find it
assert callable(self._func)
__builtin__.globalProfileFunc = self._func
__builtin__.globalProfileResult = [None]
@ -194,6 +202,9 @@ class ProfileSession:
self._successfulProfiles += 1
if self._logAfterProfile:
self.notify.info(self.getResults())
self.release()
return result
@ -214,15 +225,66 @@ class ProfileSession:
_removeProfileCustomFuncs(filename)
# and discard it
del self._filename2ramFile[filename]
def setName(self, name):
self._name = name
def getName(self):
return self._name
def setFunc(self, func):
self._func = func
def getFunc(self):
return self._func
def setAggregate(self, aggregate):
self._aggregate = aggregate
def getAggregate(self):
return self._aggregate
def setLogAfterProfile(self, logAfterProfile):
self._logAfterProfile = logAfterProfile
def getLogAfterProfile(self):
return self._logAfterProfile
def setLines(self, lines):
self._lines = lines
def getLines(self):
return self._lines
def setSorts(self, sorts):
self._sorts = sorts
def getSorts(self):
return self._sorts
def setShowCallInfo(self, showCallInfo):
self._showCallInfo = showCallInfo
def getShowCallInfo(self):
return self._showCallInfo
def setTotalTime(self, totalTime=None):
self._totalTime = totalTime
def resetTotalTime(self):
self._totalTime = None
def getTotalTime(self):
return self._totalTime
def getResults(self,
lines=200,
sorts=['cumulative', 'time', 'calls'],
callInfo=False,
totalTime=None):
lines=Default,
sorts=Default,
callInfo=Default,
totalTime=Default):
if not self.profileSucceeded():
output = '%s: profiler already running, could not profile' % self._name
else:
if lines is Default:
lines = self._lines
if sorts is Default:
sorts = self._sorts
if callInfo is Default:
callInfo = self._callInfo
if totalTime is Default:
totalTime = self._totalTime
# make sure our stats object exists and is up-to-date
statsChanged = (self._statFileCounter < len(self._filenames))

View File

@ -3751,6 +3751,10 @@ def pandaBreak(dotpath, linenum, temporary = 0, cond = None):
filename="%s\\%s"%(filename,d)
globalPdb.set_break(filename+".py", linenum, temporary, cond)
class Default:
# represents 'use the default value'
# useful for keyword arguments to virtual methods
pass
import __builtin__
__builtin__.Functor = Functor
@ -3802,3 +3806,4 @@ __builtin__.logBlock = logBlock
__builtin__.HierarchyException = HierarchyException
__builtin__.pdir = pdir
__builtin__.deeptype = deeptype
__builtin__.Default = Default

View File

@ -111,12 +111,12 @@ class TaskManager:
self.fKeyboardInterrupt = False
self.interruptCount = 0
self._profileFrames = False
self._frameProfileQueue = Queue()
# this will be set when it's safe to import StateVar
# this will be set when it's safe to import StateVar
self._profileTasks = None
self._taskProfiler = None
self._profileInfo = ScratchPad(
self._taskProfileInfo = ScratchPad(
taskId = None,
profiled = False,
session = None,
@ -130,6 +130,7 @@ class TaskManager:
self.setProfileTasks(ConfigVariableBool('profile-task-spikes', 0).getValue())
def destroy(self):
self._frameProfileQueue.clear()
self.mgr.cleanup()
def setClock(self, clockObject):
@ -441,9 +442,14 @@ class TaskManager:
self.running = True
while self.running:
try:
if self._profileFrames:
self._profileFrames = False
self._doProfiledFrames()
if len(self._frameProfileQueue):
numFrames, session = self._frameProfileQueue.pop()
def _profileFunc(numFrames=numFrames):
self._doProfiledFrames(numFrames)
session.setFunc(_profileFunc)
print '** profiling %s frames' % numFrames
session.run()
_profileFunc = None
else:
self.step()
except KeyboardInterrupt:
@ -522,17 +528,22 @@ class TaskManager:
from direct.tkpanels import TaskManagerPanel
return TaskManagerPanel.TaskManagerPanel(self)
def profileFrames(self, num=None):
self._profileFrames = True
def getProfileSession(self, name=None):
# call to get a profile session that you can modify before passing to profileFrames()
if name is None:
name = 'taskMgrFrameProfile'
return ProfileSession(name)
def profileFrames(self, num=None, session=None):
if num is None:
num = 1
self._profileFrameCount = num
if session is None:
session = self.getProfileSession()
self._frameProfileQueue.push((num, session))
@profiled()
def _doProfiledFrames(self, *args, **kArgs):
print '** profiling %s frames' % self._profileFrameCount
for i in xrange(self._profileFrameCount):
result = self.step(*args, **kArgs)
def _doProfiledFrames(self, numFrames):
for i in xrange(numFrames):
result = self.step()
return result
def getProfileTasks(self):
@ -557,10 +568,10 @@ class TaskManager:
self._taskProfiler.flush(name)
def _setProfileTask(self, task):
if self._profileInfo.session:
self._profileInfo.session.release()
self._profileInfo.session = None
self._profileInfo = ScratchPad(
if self._taskProfileInfo.session:
self._taskProfileInfo.session.release()
self._taskProfileInfo.session = None
self._taskProfileInfo = ScratchPad(
taskFunc = task.getFunction(),
taskArgs = task.getArgs(),
task = task,
@ -571,7 +582,7 @@ class TaskManager:
# Temporarily replace the task's own function with our
# _profileTask method.
task.setFunction(self._profileTask)
task.setArgs([self._profileInfo], True)
task.setArgs([self._taskProfileInfo], True)
def _profileTask(self, profileInfo, task):
# This is called instead of the task function when we have
@ -590,8 +601,8 @@ class TaskManager:
task.setArgs(taskArgs, appendTask)
task.setFunction(profileInfo.taskFunc)
profileSession = ProfileSession(Functor(profileInfo.taskFunc, *profileInfo.taskArgs),
'profiled-task-%s' % task.getName())
profileSession = ProfileSession('profiled-task-%s' % task.getName(),
Functor(profileInfo.taskFunc, *profileInfo.taskArgs))
ret = profileSession.run()
# set these values *after* profiling in case we're profiling the TaskProfiler
@ -602,10 +613,10 @@ class TaskManager:
def _hasProfiledDesignatedTask(self):
# have we run a profile of the designated task yet?
return self._profileInfo.profiled
return self._taskProfileInfo.profiled
def _getLastProfileSession(self):
return self._profileInfo.session
def _getLastTaskProfileSession(self):
return self._taskProfileInfo.session
def _getRandomTask(self):
# Figure out when the next frame is likely to expire, so we

View File

@ -409,13 +409,13 @@ class TaskManager:
# List of tasks scheduled to execute in the future
self.__doLaterList = []
self._profileFrames = False
self._frameProfileQueue = Queue()
self.MaxEpockSpeed = 1.0/30.0;
# this will be set when it's safe to import StateVar
self._profileTasks = None
self._taskProfiler = None
self._profileInfo = ScratchPad(
self._taskProfileInfo = ScratchPad(
taskId = None,
profiled = False,
session = None,
@ -458,6 +458,7 @@ class TaskManager:
pass
def destroy(self):
self._frameProfileQueue.clear()
if self._doLaterTask:
self._doLaterTask.remove()
if self._taskProfiler:
@ -799,9 +800,9 @@ class TaskManager:
def __executeTask(self, task):
task.setCurrentTimeFrame(self.currentTime, self.currentFrame)
# cache reference to profile info here, self._profileInfo might get swapped out
# cache reference to profile info here, self._taskProfileInfo might get swapped out
# by the task when it runs
profileInfo = self._profileInfo
profileInfo = self._taskProfileInfo
doProfile = (task.id == profileInfo.taskId)
# don't profile the same task twice in a row
doProfile = doProfile and (not profileInfo.profiled)
@ -811,8 +812,8 @@ class TaskManager:
# don't record timing info
if doProfile:
profileSession = ProfileSession(Functor(task, *task.extraArgs),
'TASK_PROFILE:%s' % task.name)
profileSession = ProfileSession('TASK_PROFILE:%s' % task.name,
Functor(task, *task.extraArgs))
ret = profileSession.run()
# set these values *after* profiling in case we're profiling the TaskProfiler
profileInfo.session = profileSession
@ -834,8 +835,8 @@ class TaskManager:
task.pstats.start()
startTime = self.trueClock.getShortTime()
if doProfile:
profileSession = ProfileSession(Functor(task, *task.extraArgs),
'profiled-task-%s' % task.name)
profileSession = ProfileSession('profiled-task-%s' % task.name,
Functor(task, *task.extraArgs))
ret = profileSession.run()
# set these values *after* profiling in case we're profiling the TaskProfiler
profileInfo.session = profileSession
@ -963,11 +964,18 @@ class TaskManager:
self.__addNewTask(task)
self.pendingTaskDict.clear()
def profileFrames(self, num=None):
self._profileFrames = True
def getProfileSession(self, name=None):
# call to get a profile session that you can modify before passing to profileFrames()
if name is None:
name = 'taskMgrFrameProfile'
return ProfileSession(name)
def profileFrames(self, num=None, session=None):
if num is None:
num = 1
self._profileFrameCount = num
if session is None:
session = self.getProfileSession()
self._frameProfileQueue.push((num, session))
# in the event we want to do frame time managment.. this is the function to
@ -987,11 +995,9 @@ class TaskManager:
delta = minFinTime - self.globalClock.getRealTime();
@profiled()
def _doProfiledFrames(self, *args, **kArgs):
print '** profiling %s frames' % self._profileFrameCount
for i in xrange(self._profileFrameCount):
result = self.step(*args, **kArgs)
def _doProfiledFrames(self, numFrames):
for i in xrange(numFrames):
result = self.step()
return result
def getProfileTasks(self):
@ -1016,10 +1022,10 @@ class TaskManager:
self._taskProfiler.flush(name)
def _setProfileTask(self, task):
if self._profileInfo.session:
self._profileInfo.session.release()
self._profileInfo.session = None
self._profileInfo = ScratchPad(
if self._taskProfileInfo.session:
self._taskProfileInfo.session.release()
self._taskProfileInfo.session = None
self._taskProfileInfo = ScratchPad(
taskId = task.id,
profiled = False,
session = None,
@ -1027,10 +1033,10 @@ class TaskManager:
def _hasProfiledDesignatedTask(self):
# have we run a profile of the designated task yet?
return self._profileInfo.profiled
return self._taskProfileInfo.profiled
def _getLastProfileSession(self):
return self._profileInfo.session
def _getLastTaskProfileSession(self):
return self._taskProfileInfo.session
def _getRandomTask(self):
numTasks = 0
@ -1159,9 +1165,14 @@ class TaskManager:
self.running = 1
while self.running:
try:
if self._profileFrames:
self._profileFrames = False
self._doProfiledFrames()
if len(self._frameProfileQueue):
numFrames, session = self._frameProfileQueue.pop()
def _profileFunc(numFrames=numFrames):
self._doProfiledFrames(numFrames)
session.setFunc(_profileFunc)
print '** profiling %s frames' % numFrames
session.run()
_profileFunc = None
else:
self.step()
except KeyboardInterrupt:

View File

@ -134,7 +134,7 @@ class TaskProfiler:
# gather data from the previous frame
# set up for the next frame
if (self._task is not None) and taskMgr._hasProfiledDesignatedTask():
session = taskMgr._getLastProfileSession()
session = taskMgr._getLastTaskProfileSession()
# if we couldn't profile, throw this result out
if session.profileSucceeded():
namePrefix = self._task.getNamePrefix()