from panda3d.core import ConfigVariableInt, ConfigVariableDouble
from direct.directnotify.DirectNotifyGlobal import directNotify
from direct.fsm.StatePush import FunctionCall
from direct.showbase.PythonUtil import Averager
from .TaskManagerGlobal import taskMgr
[docs]class TaskTracker:
# call it TaskProfiler to avoid confusion for the user
notify = directNotify.newCategory("TaskProfiler")
MinSamples = None
SpikeThreshold = None
[docs] def __init__(self, namePrefix):
self._namePrefix = namePrefix
self._durationAverager = Averager('%s-durationAverager' % namePrefix)
self._avgSession = None
if TaskTracker.MinSamples is None:
# number of samples required before spikes start getting identified
TaskTracker.MinSamples = ConfigVariableInt('profile-task-spike-min-samples', 30).value
# defines spike as longer than this multiple of avg task duration
TaskTracker.SpikeThreshold = TaskProfiler.GetDefaultSpikeThreshold()
[docs] def destroy(self):
self.flush()
del self._namePrefix
del self._durationAverager
[docs] def flush(self):
self._durationAverager.reset()
if self._avgSession:
self._avgSession.release()
self._avgSession = None
[docs] def getNamePrefix(self, namePrefix):
return self._namePrefix
def _checkSpike(self, session):
duration = session.getDuration()
isSpike = False
# do we have enough samples?
if self.getNumDurationSamples() > self.MinSamples:
# was this a spike?
if duration > (self.getAvgDuration() * self.SpikeThreshold):
isSpike = True
avgSession = self.getAvgSession()
s = '\n%s task CPU spike profile (%s) %s\n' % ('=' * 30, self._namePrefix, '=' * 30)
s += ('|' * 80) + '\n'
for sorts in (['cumulative'], ['time'], ['calls']):
s += ('-- AVERAGE --\n%s'
'-- SPIKE --\n%s' % (
avgSession.getResults(sorts=sorts, totalTime=duration),
session.getResults(sorts=sorts)))
self.notify.info(s)
return isSpike
[docs] def addProfileSession(self, session):
duration = session.getDuration()
if duration == 0.:
# profiled code was too fast for the clock, throw this result out
# if we keep it we may get many false positive spike detects
return
isSpike = self._checkSpike(session)
self._durationAverager.addValue(duration)
storeAvg = True
if self._avgSession is not None:
avgDur = self.getAvgDuration()
if abs(self._avgSession.getDuration() - avgDur) < abs(duration - avgDur):
# current avg data is more average than this new sample, keep the data we've
# already got stored
storeAvg = False
if storeAvg:
if self._avgSession:
self._avgSession.release()
self._avgSession = session.getReference()
[docs] def getAvgDuration(self):
return self._durationAverager.getAverage()
[docs] def getNumDurationSamples(self):
return self._durationAverager.getCount()
[docs] def getAvgSession(self):
# returns profile session for closest-to-average sample
return self._avgSession
[docs] def log(self):
if self._avgSession:
s = 'task CPU profile (%s):\n' % self._namePrefix
s += ('|' * 80) + '\n'
for sorts in (['cumulative'], ['time'], ['calls']):
s += self._avgSession.getResults(sorts=sorts)
self.notify.info(s)
else:
self.notify.info('task CPU profile (%s): no data collected' % self._namePrefix)
[docs]class TaskProfiler:
# this does intermittent profiling of tasks running on the system
# if a task has a spike in execution time, the profile of the spike is logged
notify = directNotify.newCategory("TaskProfiler")
[docs] def __init__(self):
self._enableFC = FunctionCall(self._setEnabled, taskMgr.getProfileTasksSV())
self._enableFC.pushCurrentState()
# table of task name pattern to TaskTracker
self._namePrefix2tracker = {}
self._task = None
[docs] def destroy(self):
if taskMgr.getProfileTasks():
self._setEnabled(False)
self._enableFC.destroy()
for tracker in self._namePrefix2tracker.values():
tracker.destroy()
del self._namePrefix2tracker
del self._task
[docs] @staticmethod
def GetDefaultSpikeThreshold():
return ConfigVariableDouble('profile-task-spike-threshold', 5.).value
[docs] @staticmethod
def SetSpikeThreshold(spikeThreshold):
TaskTracker.SpikeThreshold = spikeThreshold
[docs] @staticmethod
def GetSpikeThreshold():
return TaskTracker.SpikeThreshold
[docs] def logProfiles(self, name=None):
if name:
name = name.lower()
for namePrefix, tracker in self._namePrefix2tracker.items():
if name and name not in namePrefix.lower():
continue
tracker.log()
[docs] def flush(self, name):
if name:
name = name.lower()
# flush stored task profiles
for namePrefix, tracker in self._namePrefix2tracker.items():
if name and name not in namePrefix.lower():
continue
tracker.flush()
def _setEnabled(self, enabled):
if enabled:
self.notify.info('task profiler started')
self._taskName = 'profile-tasks-%s' % id(self)
taskMgr.add(self._doProfileTasks, self._taskName, priority=-200)
else:
taskMgr.remove(self._taskName)
del self._taskName
self.notify.info('task profiler stopped')
def _doProfileTasks(self, task=None):
# gather data from the previous frame
# set up for the next frame
if self._task is not None and taskMgr._hasProfiledDesignatedTask():
session = taskMgr._getLastTaskProfileSession()
# if we couldn't profile, throw this result out
if session.profileSucceeded():
namePrefix = self._task.getNamePrefix()
if namePrefix not in self._namePrefix2tracker:
self._namePrefix2tracker[namePrefix] = TaskTracker(namePrefix)
tracker = self._namePrefix2tracker[namePrefix]
tracker.addProfileSession(session)
# set up the next task
self._task = taskMgr._getRandomTask()
taskMgr._setProfileTask(self._task)
return task.cont