Source file uprof.icn
#####################################################################
#
# Unicon Profiler v0.7
# Author: Leah Wegner
# Maintainer: Clinton Jeffery, Gigi Young
# March 26, 2018
#
# Notes:
#  The granularity of the timing mechanism on classic Linux was 10 milliseconds.
#  Even after improvements to the code, &time uses 1ms units and can't do better.
#  This causes inaccurate timing results for procedures that typically complete
#  in 0ms, 1ms, or only a few milliseconds. For such procedures, it might
#  be more beneficial to look only at procedure counts instead of run-time.
#
# To Do:
#  Check and correct profiling for programs with co-expressions
#  Design and implement higher-resolution timing mechanisms.
#
####################################################################


link evinit
link evnames
link evsyms
link options
link printf
$include "evdefs.icn"

# Column widths for output formatting
$define PROC_WIDTH	12	# PROCEDURE
$define PTIME_WIDTH	5	# %TIME
$define TIME_WIDTH	7	# TIME
$define CTIME_WIDTH	10	# TIME/CALL
$define CALL_WIDTH	10	# CALLS	
$define RET_WIDTH	10	# RETURNS
$define SUSP_WIDTH	10	# SUSPENDS
$define RESUM_WIDTH	10	# RESUMES
$define FAIL_WIDTH	6	# FAILS

global Proc_stack
global Call_time_stack
global keys
global keys2
record lkey(keyval, timeval)

procedure main(args)
   #Declarations
   local opts
   local symmap
   local val
   local outfile, output

   local Proc_calls := table(0)
   local Proc_fails := table(0)
   local Proc_returns := table(0)
   local Proc_suspends := table(0)
   local Proc_resumes := table(0)

   local Proc_time := table(0)
   local Proc_alone_time := table(0)
   local proc_start_time := 0
   local proc_end_time := 0
   local mask := ProcMask
   local lastline, lines
   local line_time := table(0), line_count := table(0)
   local longest_filename_length := 0
   
   if (*args=0) | (args[1] == ("--help"|"-help")) then
       stop("Usage: uprof [-l] [-o outfile] prog args\n",
	    "\n",
	    "  -l \t\tEnables line-level time sampling\n",
	    "  -o outfile \tWwrites output to file 'outfile'\n")

   if args[1] == "-l" then { pop(args); lines := 1; mask ++:= E_Line }

   if args[1] == "-o" then { 
      pop(args)
      output := pop(args) | stop("no output file specified for -o option")
      if *args = 0 then
        stop("*** cannot open program to monitor")

      # create a new file in write mode
      outfile := open(output, "c") | stop("could not open ",outfile)
   }

   #initializations
   symmap := evsyms()
   Proc_stack := list()
   Call_time_stack := list()

   EvInit(args) | stop("*** cannot open program to monitor")

   while EvGet(mask) do {
      val := &eventvalue

   #Build the tables
      case &eventcode of {
	 E_Line: {
	     linetime := keyword("time", Monitored)
	     linefile := keyword("file", Monitored)
	     longest_filename_length <:= *linefile
	     line_count[linefile||":"||&eventvalue] +:= 1
	     if delta := (0 < linetime-\last_linetime) then {
		 line_time[linefile||":"||&eventvalue] +:= delta
	     }
	     last_linetime := linetime
	 }

         #CALL
         E_Pcall: {
            push(Proc_stack, val)
            push(Call_time_stack, (keyword("&time", Monitored)))
	    Proc_calls[val] +:= 1
         }

         #FAIL
         E_Pfail: {
            Current_proc := pop(Proc_stack)
            proc_start_time := pop(Call_time_stack)
            proc_end_time := keyword("&time", Monitored)

            Proc_fails[val] +:= 1

            curr_proc_time := proc_end_time - proc_start_time
            Proc_time[Current_proc] +:= curr_proc_time

            if *Proc_stack > 0 then {
                # subtract this proc's runtime from next proc on stack 
                Proc_time[Proc_stack[1]] -:= curr_proc_time 
            }
         }

         #RETURN
         E_Pret: {
            Current_proc := pop(Proc_stack)
            proc_start_time := pop(Call_time_stack)
            proc_end_time := keyword("&time", Monitored)

            Proc_returns[Current_proc] +:= 1

            curr_proc_time := proc_end_time - proc_start_time
            Proc_time[Current_proc] +:= curr_proc_time

            if *Proc_stack > 0 then {
                # subtract this proc's runtime from next proc on stack 
                Proc_time[Proc_stack[1]] -:= curr_proc_time
            }
         }

         #SUSPEND
         E_Psusp: {
            Current_proc := pop(Proc_stack)
            proc_start_time := pop(Call_time_stack)
            proc_end_time := keyword("&time", Monitored)

            Proc_suspends[Current_proc] +:= 1

            curr_proc_time := proc_end_time - proc_start_time
            Proc_time[Current_proc] +:= curr_proc_time

            if *Proc_stack > 0 then {
                # subtract this proc's runtime from next proc on stack 
                Proc_time[Proc_stack[1]] -:= curr_proc_time
            }

         }

         #RESUME
         E_Presum: {
            push(Proc_stack, val)
            Proc_resumes[val] +:= 1

            push(Call_time_stack, (keyword("&time", Monitored)))
         }
      }
   }

   if *Proc_stack > 0 then {
      write("Execution left ", *Proc_stack, " frames.")
      while *Proc_stack > 0 do {
         Current_proc := pop(Proc_stack)
         proc_start_time := pop(Call_time_stack)
         Proc_fails[Current_proc] +:= 1

         proc_end_time := keyword("&time", Monitored)

         curr_proc_time := proc_end_time - proc_start_time
         Proc_time[Current_proc] +:= curr_proc_time 
         if *Proc_stack > 0 then {
             # subtract this proc's runtime from next proc on stack 
             Proc_time[Proc_stack[1]] -:= curr_proc_time 
         }
      }
   }

   # calculate total time
   total := 0
   every y := key(Proc_time) do total +:= Proc_time[y]

   #sort the output
   keys := list()
   every y := key(Proc_time) do {
      x := lkey(y, divimage(Proc_time[y]*100,total))
      push(keys, x)
      }
   keys := reverse(sortf(keys, 2))

   #mxname := 15
   #every mxname <:= *image((!keys).keyval)[10:0]

   if \outfile then {
      write(outfile, left("PROCEDURE",PROC_WIDTH), 
            right("%TIME", PTIME_WIDTH), 
            right("TIME", TIME_WIDTH), 
            right("TIME/CALL", CTIME_WIDTH),
            right("CALLS", CALL_WIDTH), 
            right("RETURNS", RET_WIDTH),
            right("SUSPENDS", SUSP_WIDTH), 
            right("RESUMES",RESUM_WIDTH), 
            right("FAILS", FAIL_WIDTH) 
           )
      write(outfile, left("",PROC_WIDTH), 
            right("", PTIME_WIDTH), 
            right("(ms)", TIME_WIDTH), 
            right("(ms)", CTIME_WIDTH),
            right("", CALL_WIDTH), 
            right("", RET_WIDTH),
            right("", SUSP_WIDTH), 
            right("",RESUM_WIDTH), 
            right("", FAIL_WIDTH)
           )
       write(outfile)

       every y := (!keys).keyval do {
         write(outfile, left((image(y))[10:0], PROC_WIDTH),
               right(divimage(Proc_time[y]*100,total), PTIME_WIDTH),
               right(Proc_time[y], TIME_WIDTH),
               right(sprintf("%.3r",
                          divimage(Proc_time[y],real(
                          Proc_fails[y]+Proc_returns[y]+Proc_suspends[y]))
                         ),
                     CTIME_WIDTH),
               right(Proc_calls[y], CALL_WIDTH), 
               right(Proc_returns[y], RET_WIDTH),
               right(Proc_suspends[y], SUSP_WIDTH),
	       right(Proc_resumes[y], RESUM_WIDTH),
	       right(Proc_fails[y], FAIL_WIDTH)
              )
      }

      write(outfile, "\nTOTAL RUNTIME: ",sprintf("%.3r",real(total)*0.001),"s\n")

      if \lines then {
         write(outfile, sprintf("%"||(longest_filename_length+1)||"s %8s %8s %8s %8s",
	       "FILE", "LINE", "TIME", "LINE ", "TIME/LN"))
         write(outfile, sprintf("%"||(longest_filename_length+1)||"s %8s %8s %8s %8s",
	       "    ", "    ", "(ms)", "COUNT", "  (ms) "))
         L := reverse(sort(line_time,2))
         every le := !L do {
            write(outfile, sprintf("%"||(longest_filename_length+1)||"s %8d %8d %8d   %.3r",
	          le[1][1:find(":",le[1])], le[1][find(":",le[1])+1:0],
                  le[2], line_count[le[1]],
                  divimage(le[2], real(line_count[le[1]]))))
         }
      }
      close(outfile)
   }

   # print to stdout
   else {

      write(left("PROCEDURE",PROC_WIDTH), 
            right("%TIME", PTIME_WIDTH), 
            right("TIME", TIME_WIDTH), 
            right("TIME/CALL", CTIME_WIDTH),
            right("CALLS", CALL_WIDTH), 
            right("RETURNS", RET_WIDTH),
            right("SUSPENDS", SUSP_WIDTH), 
            right("RESUMES",RESUM_WIDTH), 
            right("FAILS", FAIL_WIDTH) 
           )
      write(left("",PROC_WIDTH), 
            right("", PTIME_WIDTH), 
            right("(ms)", TIME_WIDTH), 
            right("(ms)", CTIME_WIDTH),
            right("", CALL_WIDTH), 
            right("", RET_WIDTH),
            right("", SUSP_WIDTH), 
            right("",RESUM_WIDTH), 
            right("", FAIL_WIDTH)
           )
       write()

       every y := (!keys).keyval do {
         write(left((image(y))[10:0], PROC_WIDTH),
               right(divimage(Proc_time[y]*100,total), PTIME_WIDTH),
               right(Proc_time[y], TIME_WIDTH),
               right(sprintf("%.3r",
                          divimage(Proc_time[y],real(
                          Proc_fails[y]+Proc_returns[y]+Proc_suspends[y]))
                         ),
                     CTIME_WIDTH),
               right(Proc_calls[y], CALL_WIDTH), 
               right(Proc_returns[y], RET_WIDTH),
               right(Proc_suspends[y], SUSP_WIDTH),
	       right(Proc_resumes[y], RESUM_WIDTH),
	       right(Proc_fails[y], FAIL_WIDTH)
              )
      }

      write("\nTOTAL RUNTIME: ",sprintf("%.3r",real(total)*0.001),"s\n")

      if \lines then {
         write(sprintf("%"||(longest_filename_length+1)||"s %8s %8s %8s %8s",
	       "FILE", "LINE", "TIME", "LINE ", "TIME/LN"))
         write(sprintf("%"||(longest_filename_length+1)||"s %8s %8s %8s %8s",
	       "    ", "    ", "(ms)", "COUNT", "  (ms) "))
         L := reverse(sort(line_time,2))
         every le := !L do {
            write(sprintf("%"||(longest_filename_length+1)||"s %8d %8d %8d   %.3r",
	          le[1][1:find(":",le[1])], le[1][find(":",le[1])+1:0],
	          le[2], line_count[le[1]],
	          divimage(le[2], real(line_count[le[1]]))))
         }
      }
   }

end

#
# return an image of a division
#
procedure divimage(num,denom)
    if num = 0 then return 0
    else if denom = 0 then return 0
    else return num/denom
end

This page produced by UniDoc on 2021/04/15 @ 23:59:43.