[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