[Dev] event profiling and profile tool
Alec Flett
alecf at osafoundation.org
Thu Feb 10 16:21:00 PST 2005
Phillip J. Eby wrote:
> At 10:32 AM 2/10/05 -0800, Ted Leung wrote:
>
>> It would also be good to coordinate your CLI with Phillip's code for
>> profiling the unit tests...
>
>
> Perhaps my tool should use 'events.prof' instead of 'profile.dat', or
> vice versa. OTOH, if Alec's profile analysis tool accepts the
> filename on the command line, then it's of no import. One could
> simply run the unit tests and then run the analysis tool on
> profile.dat to analyze the test results.
>
Ted and I were talking about this - one thing that would probably be
really good to work out is a method of generating events to chandler
itself - orthogonal to the unit tests, we probably need a way to
simulate at least a few user actions to the full chandler app. Really,
this could be as easy as:
- having a bunch of event names with possible parameters (a few events
take parameters) in a text file
- a command line option for chandler to "execute" this script by just
firing off the events listed in the file once the UI has appeared, wait
a bit, then quit the app
I think an important thing to know about the event profiling things are
that they are based on CPIA events, which are application events - i.e.
when someone selects a menu item, and so forth. We can simulate those
pretty easily by just dispatching them to the toplevel view.
> I'm certainly curious to see the profile navigation. I seem to recall
> that somebody has written a nice GUI for analyzing Python profiling
> results but I don't remember where I saw it. Personally I haven't had
> reason to do much with profiler data besides sort the data a couple
> different ways and display the top 10 or 20 routines by time,
> cumulative time, or number of calls.
>
its pretty darn rudamentary (but it does take the filename on the
command line) - it runs on the console and its basically stuff like
"show callers of <functionName>" or "show stats sorted by <field>" - we
probably want a GUI for doing more elaborate analisys, and I would
gladly throw it away if you know of a better system out there for
analyzing these files.
I've attached the python file here and will try to get it checked in
later (so you guys can hack on it :)
Alec
-------------- next part --------------
#!/usr/bin/env python
import getopt, sys, os
import hotshot, hotshot.stats
import pstats
import cStringIO
options = { 'filename': None,
'order': 'time',
'limit': 20,
'output': None,
'pager': True }
#
# process_options - processes the options from the command line
#
def process_options():
optlist, args = getopt.getopt(sys.argv[1:], "p:o:l:c:e:",
["profile=",
"order=",
"limit=",
"callers-of=",
"callees-of="])
for opt, arg in optlist:
if (opt == '-p'):
options['filename'] = arg
# need to check valid orders:
# calls, cumulative, file, module, pcalls, line, name, nfl, stdname, time
if (opt == '-o'):
options['order'] = arg
if (opt == '-l'):
options['limit'] = int(arg)
if (opt == '-c'):
options['output'] = 'callers'
options['data'] = arg
if (opt == '-e'):
options['output'] = 'callees'
options['data'] = arg
if args and not options['filename']:
options['filename'] = args[0]
if not options['order']:
options['order'] = 'time'
if not options['filename']:
print "Filename required..."
exit
#
# prompt_user - prompts the user for an action, and then sets the option
# structure to match what they entered
#
def prompt_user():
while True:
try:
print "c = callers | e = callees | s = stats | o = order | l = limit";
line = sys.stdin.readline().strip()
commandline = line.split(None)
command = commandline[0]
if len(commandline) > 1:
data = commandline[1]
else:
data = None
if command == 'q':
return False
if command == 'c':
options['output'] = 'callers'
options['data'] = data
if command == 'e':
options['output'] = 'callees'
options['data'] = data
if command == 's':
options['output'] = 'stats'
options['data'] = data
if command == 'o':
options['order'] = data
break
except:
print "Unrecognized command"
return True
#
# output_with_pager - borrowed from http://www.andreasen.org/misc/util.py
#
def output_with_pager(string):
print string
# this seems to be broken right now?
less = os.popen('less -', 'w')
#try:
less.write(string)
less.close()
#except:
pass
#
# show_profile - displays the profile to the user given the current options
#
def show_profile(stats):
# stats.strip_dirs()
stats.sort_stats(options['order'])
# now capture the output
out = cStringIO.StringIO()
old_stdout = sys.stdout
sys.stdout = out
# Figure out the correct part of stats to call
try:
if options['output'] == 'callers':
print " Callers of '" + options['data'] + "':"
stats.print_callers(options['data'], options['limit'])
elif options['output'] == 'callees':
print " Functions that '" + options['data'] + "' call:"
stats.print_callees(options['data'], options['limit'])
else:
# show stats
print "Statistics: "
stats.print_stats(options['limit'])
except:
print "Couldn't generate output. Possibly bad caller/callee pattern"
# reset to defaults
sys.stdout = old_stdout
out.seek(0)
parse_state = None;
# keep track of where the 2nd column of functions start
# we'll find this out from the header
col2starts = 0
result = "";
for line in out:
# funclist1: the first line of the function list
if parse_state == 'funclist':
function = line[0:col2starts].strip()
subfunc = line[col2starts:].strip()
if function:
result += "\n" + function + "\n"
result += " " + subfunc + "\n"
# default parse_state, look for Function header
elif line.startswith('Function'):
if options['output'] == 'callers':
col2starts = line.find('was called by')
elif options['output'] == 'callees':
col2starts = line.find('called')
parse_state = 'funclist'
else:
result += line + "\n"
# now spit out to less
output_with_pager(result)
#
# main
#
def main():
process_options()
stats = hotshot.stats.load(options['filename'])
while prompt_user():
show_profile(stats)
main()
More information about the Dev
mailing list