forked from panda3d/panda3d
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTaskProfiler.py
More file actions
executable file
·163 lines (148 loc) · 6.43 KB
/
TaskProfiler.py
File metadata and controls
executable file
·163 lines (148 loc) · 6.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
from direct.directnotify.DirectNotifyGlobal import directNotify
from direct.fsm.StatePush import FunctionCall
from direct.showbase.PythonUtil import Averager
class TaskTracker:
# call it TaskProfiler to avoid confusion for the user
notify = directNotify.newCategory("TaskProfiler")
MinSamples = None
SpikeThreshold = None
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 = config.GetInt('profile-task-spike-min-samples', 30)
# defines spike as longer than this multiple of avg task duration
TaskTracker.SpikeThreshold = TaskProfiler.GetDefaultSpikeThreshold()
def destroy(self):
self.flush()
del self._namePrefix
del self._durationAverager
def flush(self):
self._durationAverager.reset()
if self._avgSession:
self._avgSession.release()
self._avgSession = None
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
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()
def getAvgDuration(self):
return self._durationAverager.getAverage()
def getNumDurationSamples(self):
return self._durationAverager.getCount()
def getAvgSession(self):
# returns profile session for closest-to-average sample
return self._avgSession
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)
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")
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
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
@staticmethod
def GetDefaultSpikeThreshold():
return config.GetFloat('profile-task-spike-threshold', 5.)
@staticmethod
def SetSpikeThreshold(spikeThreshold):
TaskTracker.SpikeThreshold = spikeThreshold
@staticmethod
def GetSpikeThreshold():
return TaskTracker.SpikeThreshold
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()
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