############################################################################ # # File: iprofile.icn # # Subject: Program to profile Icon procedure usage # # Author: Richard L. Goerwitz # # Date: May 2, 2001 # ############################################################################ # # This file is in the public domain. # ############################################################################ # # Version: 1.5 # ############################################################################ # # This very simple profiler takes a single argument - an Icon program # compiled with the -t option. Displays stats on which procedures # were called the most often, and from what lines in what files they # were called. Use this program to figure out what procedures are # getting worked the hardest and why. Counts only invocations and # resumptions; not suspensions, returns, failures. # # If you are running a program that reads from a file, be sure to # protect the redirection symbol from the shell (i.e. "profile # 'myprog < input'" instead of "profile myprog < input"). If a given # program normally reads &input, please redirect stdin to read from # another tty than the one you are running profile from. If you # forget to do this, the results might be very interesting.... Also, # don't redirect stderr, as this contains the trace that profile will # be reading and using to obtain run-time statistics. Profile # automatically redirects stdout to /dev/null. # # Currently runs only under UNIX, but with some tweaking could be # made to run elsewhere as well. # # The display should be pretty much self-explanatory. Filenames and # procedures get truncated at nineteen characters (if the display # gets too wide, it can become hard to read). A star is prepended to # procedures whose statistics have changed since the last screen # update. # ############################################################################ # # Requires: co-expressions, keyboard functions, pipes, UNIX # ############################################################################ # # Links: itlib, iscreen # ############################################################################ link itlib link iscreen global CM, LI, CO, CE procedure main(a) local whitespace, firstidchars, idchars, usage, in_data, cmd, line, filename, linenum, procname, t, threshhold whitespace := '\t ' firstidchars := &letters ++ '_' idchars := &digits ++ &letters ++ '_' usage := "usage: profile filename _ (filename = Icon program compiled with -t option)" # # If called with a program name as the first argument, open it, # and pipe the trace output back to this program. Assume the # user knew enough to compile it with the "-t" option. # if *a > 0 then { if find("UNIX", &features) then { cmd := ""; every cmd ||:= !a || " " if find("2>", cmd) then stop("profile: Please don't redirect stderr!") in_data := open(cmd || " 2>&1 1> /dev/null", "pr") | stop("profile: Can't find or execute ", cmd, ".") } else stop("profile: Your OS is not (yet) supported.") } else stop(usage) # clear screen, set up global variables; initialize table setup_screen() t := table() threshhold := 0 while line := read(in_data) do { threshhold +:= 1 # # Break each line down into a file name, line number, and # procedure name. # line ? { tab(many(whitespace)) match(":") & next { filename := trim(tab(find(":"))) & tab(many(whitespace ++ ':')) & linenum := tab(many(&digits)) & tab(many(whitespace ++ '|')) & procname := tab(any(firstidchars)) || tab(many(idchars)) } | next tab(many(whitespace)) # Count only invocations and resumptions. match("suspended"|"failed"|"returned") & next } # # Enter statistics into table. # /t[procname] := table() /t[procname][filename] := table(0) t[procname][filename][linenum] +:= 1 # # Display stats interactively. # if threshhold > 90 then { threshhold := 0 display_stats(t) } } display_stats(t) # Write a nice exit message. goodbye() end # # display_stats: display the information in t interactively # procedure display_stats(t) local l, input, c static top, len, firstline # sets global variables CM, LI, CO, and CE initial { top := 1 # The first line we can write data to on the screen. firstline := 3 len := LI - 4 - firstline } # # Structure the information in t into a list. Note that to obtain # the number of procedures, one must divide l in half. # l := sort_table(t) # # Check for user input. # while kbhit() do { iputs(igoto(CM, 1, LI-1)) writes("Press j/k/^/$/p/q: ") iputs(CE) writes(input := map(getch())) case input of { # Increase or decrease top by 4; don't go beyond 0 or # *l; no even numbers for top (the 4 also must be even). "j" : top := (*l > (top+2) | *l-1) "\r" : top := (*l > (top+2) | *l-1) "\n" : top := (*l > (top+2) | *l-1) "k" : top := (0 < (top-2) | 1) "\x02" : top := (0 < (top-4) | 1) "\x15": top := (0 < (top-4) | 1) " " : top := (*l > (top+4) | *l-1) "\x06" : top := (*l > (top+4) | *l-1) "\x04" : top := (*l > (top+4) | *l-1) "^" : top := 1 "$" : top := *l-1 "p" : { iputs(igoto(CM, 1, LI-1)) writes("Press any key to continue: "); iputs(CE) until kbhit() & getch() do delay(500) } "q" : goodbye() "\x0C" : setup_screen() "\x012": setup_screen() default: { if any(&digits, input) then { while c := getche() do { if c == ("\n"|"\r") then { if not (input <:= 1) then input +:= input % 2 - 1 top := (0 < input | 1) top := (*l > input | *l-1) break } else { if any(&digits, c) then input ||:= c & next else break } } } } } iputs(igoto(CM, 1, LI-1)) writes("Press j/k/^/$/p/q: ") iputs(CE) } # # Display the information contained in table t via list l2. # write_list(l, top, len, firstline) return end # # sort_table: structure the info in t into a list # # What a mess. T is a table, keys = procedure names, values = # another table. These other tables are tables where keys = file # names and values = yet another table. These yet other tables # are structured as follows: keys = line numbers, values = number # of invocations. The idea is to collapse all of these tables # into sorted lists, and at the same time count up the total # number of invocations for a given procedure name (going through # all its invocations at every line in every file). A new table # is then created where keys = procedure names and values = total # number of invocations. Yet another sort is done on the basis of # total number of invocations. # procedure sort_table(t) local t2, total_t, k, total, i, l, l2 static old_totals initial old_totals := table() t2 := copy(t) total_t := table() every k := key(t2) do { t2[k] := sort(t2[k], 3) total := 0 every i := 2 to *t2[k] by 2 do { every total +:= !t2[k][i] t2[k][i] := sort(t2[k][i], 3) } insert(total_t, k, total) } l2 := list(); l := sort(total_t, 4) every i := 1 to *l-1 by 2 do { push(l2, t2[l[i]]) if not (total_t[l[i]] <= \old_totals[l[i]]) then l[i] := "*" || l[i] push(l2, l[i]) } old_totals := total_t return l2 end # # write_list: write statistics in the upper part of the screen # procedure write_list(l, top, len, firstline) local i, j, k, z, w static last_i #global CM, CE initial last_i := 2 # Arg1, l, is a sorted table of sorted tables of sorted tables! # Firstline is the first line on the screen we can write data to. # i := firstline iputs(igoto(CM, 1, i)); iputs(CE) every j := top to *l by 2 do { writes(left(l[j], 19, " ")) every k := 1 to *l[j+1]-1 by 2 do { iputs(igoto(CM, 20, i)) writes(left(l[j+1][k], 19, " ")) every z := 1 to *l[j+1][k+1]-1 by 2 do { iputs(igoto(CM, 40, i)) writes(left(l[j+1][k+1][z], 7, " ")) iputs(igoto(CM, 48, i)) writes(l[j+1][k+1][z+1]) if (i +:= 1) > (firstline + len) then break break break else iputs(igoto(CM, 1, i)) & iputs(CE) } } } # Clear the remaining lines down to the status line. # every w := i to last_i do { iputs(igoto(CM, 1, w)) iputs(CE) } last_i := i return end # # setup_screen: clear screen, set up status line. # procedure setup_screen() # global CM, LI, CO, CE initial { CM := getval("cm") | stop("setup_screen: No cm capability!") LI := getval("li") CO := getval("co") CE := getval("ce") # UNIX-specific command to disable character echo. system("stty -echo") } clear() iputs(igoto(CM, 1, 1)) emphasize() writes(left(left("procedure name", 19, " ") || left("source file", 20, " ") || left("line", 8, " ") || "number of invocations/resumptions", CO, " ")) normal() status_line("- \"Profile,\" by Richard Goerwitz -") iputs(igoto(CM, 1, LI-1)) writes("J or CR=down; k=up; ^=begin; $=end; p=pause; q=quit: ") iputs(CE) return end # # goodbye: exit, say something nice # procedure goodbye() # UNIX-specific command. system("stty echo") status_line("- \"Profile,\" by Richard Goerwitz -") every boldface() | emphasize() | normal() | boldface() | emphasize() | normal() do { delay(50) iputs(igoto(CM, 1, LI-1)) writes("Hope you enjoyed using profile! ") normal(); iputs(CE) } exit() end # # stop_profile: graceful exit after error # procedure stop_profile(s) # UNIX-specific command. system("stty echo") status_line("- \"Profile,\" by Richard Goerwitz -") iputs(igoto(CM, 1, LI-1)) writes(s); iputs(CE) iputs(igoto(CM, 1, LI)) stop() end