show timings as percentages, don't display max-non-spike profiles, output easier to scan

This commit is contained in:
Darren Ranalli 2008-10-14 21:07:07 +00:00
parent f7fc1ca70b
commit 232583858c
2 changed files with 46 additions and 54 deletions

View File

@ -12,6 +12,11 @@ import marshal
class PercentStats(pstats.Stats): class PercentStats(pstats.Stats):
# prints more useful output when sampled durations are shorter than a millisecond # prints more useful output when sampled durations are shorter than a millisecond
# lots of this is copied from Python's pstats.py # lots of this is copied from Python's pstats.py
def setTotalTime(self, tt):
# use this to set 'total time' to base time percentages on
# allows profiles to show timing based on percentages of duration of another profile
self._totalTime = tt
def print_stats(self, *amount): def print_stats(self, *amount):
for filename in self.files: for filename in self.files:
print filename print filename
@ -26,6 +31,8 @@ class PercentStats(pstats.Stats):
# DCR # DCR
#print "in %.3f CPU seconds" % self.total_tt #print "in %.3f CPU seconds" % self.total_tt
print "in %s CPU milliseconds" % (self.total_tt * 1000.) print "in %s CPU milliseconds" % (self.total_tt * 1000.)
if self._totalTime != self.total_tt:
print indent, 'percentages are of %s CPU milliseconds' % (self._totalTime * 1000)
print print
width, list = self.get_print_list(amount) width, list = self.get_print_list(amount)
if list: if list:
@ -33,12 +40,15 @@ class PercentStats(pstats.Stats):
for func in list: for func in list:
self.print_line(func) self.print_line(func)
print print
print # DCR
#print
return self return self
@staticmethod def f8(self, x):
def f8(x): if self._totalTime == 0.:
return "%7.2f%%" % (x*100.) # profiling was too quick for clock resolution...
return ' Inf%'
return "%7.2f%%" % ((x*100.) / self._totalTime)
@staticmethod @staticmethod
def func_std_string(func_name): # match what old profile produced def func_std_string(func_name): # match what old profile produced
@ -47,21 +57,23 @@ class PercentStats(pstats.Stats):
def print_line(self, func): def print_line(self, func):
cc, nc, tt, ct, callers = self.stats[func] cc, nc, tt, ct, callers = self.stats[func]
c = str(nc) c = str(nc)
d = self.total_tt # DCR
f8 = PercentStats.f8 f8 = self.f8
if nc != cc: if nc != cc:
c = c + '/' + str(cc) c = c + '/' + str(cc)
print c.rjust(9), print c.rjust(9),
print f8(tt/d), print f8(tt),
if nc == 0: if nc == 0:
print ' '*8, print ' '*8,
else: else:
print f8((tt/nc)/d), print f8(tt/nc),
print f8(ct/d), print f8(ct),
if cc == 0: if cc == 0:
print ' '*8, print ' '*8,
else: else:
print f8((ct/cc)/d), print f8(ct/cc),
# DCR
#print func_std_string(func)
print PercentStats.func_std_string(func) print PercentStats.func_std_string(func)
class ProfileSession: class ProfileSession:
@ -204,9 +216,10 @@ class ProfileSession:
del self._filename2ramFile[filename] del self._filename2ramFile[filename]
def getResults(self, def getResults(self,
lines=80, lines=200,
sorts=['cumulative', 'time', 'calls'], sorts=['cumulative', 'time', 'calls'],
callInfo=False): callInfo=False,
totalTime=None):
if not self.profileSucceeded(): if not self.profileSucceeded():
output = '%s: profiler already running, could not profile' % self._name output = '%s: profiler already running, could not profile' % self._name
else: else:
@ -232,11 +245,15 @@ class ProfileSession:
# throw out any cached result strings # throw out any cached result strings
self._resultCache = {} self._resultCache = {}
if totalTime is None:
totalTime = self._stats.total_tt
# make sure the arguments will hash efficiently if callers provide different types # make sure the arguments will hash efficiently if callers provide different types
lines = int(lines) lines = int(lines)
sorts = list(sorts) sorts = list(sorts)
callInfo = bool(callInfo) callInfo = bool(callInfo)
k = str((lines, sorts, callInfo)) totalTime = float(totalTime)
k = str((lines, sorts, callInfo, totalTime))
if k in self._resultCache: if k in self._resultCache:
# we've already created this output string, get it from the cache # we've already created this output string, get it from the cache
output = self._resultCache[k] output = self._resultCache[k]
@ -248,6 +265,8 @@ class ProfileSession:
# print the info to stdout # print the info to stdout
s = self._stats s = self._stats
# make sure our percentages are relative to the correct total time
s.setTotalTime(totalTime)
for sort in sorts: for sort in sorts:
s.sort_stats(sort) s.sort_stats(sort)
s.print_stats(lines) s.print_stats(lines)

View File

@ -11,7 +11,6 @@ class TaskTracker:
self._namePrefix = namePrefix self._namePrefix = namePrefix
self._durationAverager = Averager('%s-durationAverager' % namePrefix) self._durationAverager = Averager('%s-durationAverager' % namePrefix)
self._avgSession = None self._avgSession = None
self._maxNonSpikeSession = None
if TaskTracker.MinSamples is None: if TaskTracker.MinSamples is None:
# number of samples required before spikes start getting identified # number of samples required before spikes start getting identified
TaskTracker.MinSamples = config.GetInt('profile-task-spike-min-samples', 30) TaskTracker.MinSamples = config.GetInt('profile-task-spike-min-samples', 30)
@ -25,10 +24,7 @@ class TaskTracker:
self._durationAverager.reset() self._durationAverager.reset()
if self._avgSession: if self._avgSession:
self._avgSession.release() self._avgSession.release()
if self._maxNonSpikeSession:
self._maxNonSpikeSession.release()
self._avgSession = None self._avgSession = None
self._maxNonSpikeSession = None
def getNamePrefix(self, namePrefix): def getNamePrefix(self, namePrefix):
return self._namePrefix return self._namePrefix
def _checkSpike(self, session): def _checkSpike(self, session):
@ -40,51 +36,33 @@ class TaskTracker:
if duration > (self.getAvgDuration() * self.SpikeThreshold): if duration > (self.getAvgDuration() * self.SpikeThreshold):
isSpike = True isSpike = True
avgSession = self.getAvgSession() avgSession = self.getAvgSession()
maxNSSession = self.getMaxNonSpikeSession() s = '\n%s task CPU spike profile (%s) %s\n' % ('=' * 30, self._namePrefix, '=' * 30)
self.notify.info('task CPU spike profile (%s):\n' s += ('~' * 60) + '\n'
'== AVERAGE\n%s\n' for sorts in (['cumulative'], ['time'], ['calls']):
'== LONGEST NON-SPIKE\n%s\n' s += ('-- AVERAGE --\n%s'
'== SPIKE\n%s' % ( '-- SPIKE --\n%s' % (
self._namePrefix, avgSession.getResults(sorts=sorts, totalTime=duration),
avgSession.getResults(), session.getResults(sorts=sorts)))
maxNSSession.getResults(), self.notify.info(s)
session.getResults()))
return isSpike return isSpike
def addProfileSession(self, session): def addProfileSession(self, session):
isSpike = self._checkSpike(session) isSpike = self._checkSpike(session)
duration = session.getDuration() duration = session.getDuration()
self._durationAverager.addValue(duration) self._durationAverager.addValue(duration)
if self.getNumDurationSamples() == self.MinSamples:
# if we just collected enough samples to check for spikes, see if
# our max 'non-spike' session collected so far counts as a spike
# and if so, log it
if self._checkSpike(self._maxNonSpikeSession):
self._maxNonSpikeSession = self._avgSession
storeAvg = True storeAvg = True
storeMaxNS = True
if self._avgSession is not None: if self._avgSession is not None:
avgDur = self.getAvgDuration() avgDur = self.getAvgDuration()
if abs(self._avgSession.getDuration() - avgDur) < abs(duration - avgDur): if abs(self._avgSession.getDuration() - avgDur) < abs(duration - avgDur):
# current avg data is more average than this new sample, keep the data we've # current avg data is more average than this new sample, keep the data we've
# already got stored # already got stored
storeAvg = False storeAvg = False
if isSpike:
storeMaxNS = False
else:
if (self._maxNonSpikeSession is not None and
self._maxNonSpikeSession.getDuration() > duration):
storeMaxNS = False
if storeAvg: if storeAvg:
if self._avgSession: if self._avgSession:
self._avgSession.release() self._avgSession.release()
session.acquire() session.acquire()
self._avgSession = session self._avgSession = session
if storeMaxNS:
if self._maxNonSpikeSession:
self._maxNonSpikeSession.release()
session.acquire()
self._maxNonSpikeSession = session
def getAvgDuration(self): def getAvgDuration(self):
return self._durationAverager.getAverage() return self._durationAverager.getAverage()
def getNumDurationSamples(self): def getNumDurationSamples(self):
@ -92,18 +70,13 @@ class TaskTracker:
def getAvgSession(self): def getAvgSession(self):
# returns profile session for closest-to-average sample # returns profile session for closest-to-average sample
return self._avgSession return self._avgSession
def getMaxNonSpikeSession(self):
# returns profile session for closest-to-average sample
return self._maxNonSpikeSession
def log(self): def log(self):
if self._avgSession: if self._avgSession:
self.notify.info('task CPU profile (%s):\n' s = 'task CPU profile (%s):\n' % self._namePrefix
'== AVERAGE\n%s\n' s += ('~' * 60) + '\n'
'== LONGEST NON-SPIKE\n%s' % ( for sorts in (['cumulative'], ['time'], ['calls']):
self._namePrefix, s += self._avgSession.getResults(sorts=sorts)
self._avgSession.getResults(), self.notify.info(s)
self._maxNonSpikeSession.getResults(),
))
else: else:
self.notify.info('task CPU profile (%s): no data collected' % self._namePrefix) self.notify.info('task CPU profile (%s): no data collected' % self._namePrefix)