summaryrefslogtreecommitdiff
path: root/ipl/progs/iprofile.icn
diff options
context:
space:
mode:
Diffstat (limited to 'ipl/progs/iprofile.icn')
-rw-r--r--ipl/progs/iprofile.icn381
1 files changed, 381 insertions, 0 deletions
diff --git a/ipl/progs/iprofile.icn b/ipl/progs/iprofile.icn
new file mode 100644
index 0000000..98e0ded
--- /dev/null
+++ b/ipl/progs/iprofile.icn
@@ -0,0 +1,381 @@
+############################################################################
+#
+# 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