! $Id: profile.sqh,v 1.7 2003/12/20 04:49:50 rayo Exp $ ! ! sqrsh (sqr shell) ! Copyright (C) 1997 Ray Ontko & Company ! ! This program is free software; you can redistribute it and/or modify ! it under the terms of the GNU General Public License as published by ! the Free Software Foundation; either version 2 of the License, or ! (at your option) any later version. ! ! This program is distributed in the hope that it will be useful, ! but WITHOUT ANY WARRANTY; without even the implied warranty of ! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ! GNU General Public License for more details. ! ! You should have received a copy of the GNU General Public License ! along with this program; if not, write to the Free Software ! Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ! !++ ! ! profile ! ! profile inserts #debugp lines into a program module ! causing counters to be incremented each time that part ! of the code is reached. It also inserts code that causes the ! results to be displayed at the end of the program run. ! ! At present, this provides a procedural profile and ! limited code section profiling. There should be an ! option to allow the level of profiling. ! ! At present, this only works on complete programs, not ! on included files. That is, profiling will only be done ! on the present file, which should contain a begin-program ! and end-program (or begin-report end-report) paragraph. ! Ideally, the program would either: 1) insert into the ! included files as well, or 2) resolve all includes. ! Either way, this can be fairly dangerous and complex. ! I think our best solution here is to create include files ! of our own names that replace the user's include files to ! the specified depth. ! ! Note that this profiler can also serve as a source code coverage analyzer. ! In other words, you can use this to determine which code sections ! are used by a test program. By merging the results of profiling ! several test programs, you can determine which code sections are ! covered. We may wish to provide some output option that ! allows the user to specify a file name or type of output. ! !-- ! ! Modification History ! ! $Log: profile.sqh,v $ ! Revision 1.7 2003/12/20 04:49:50 rayo ! which -> that ! ! Revision 1.6 2003/11/30 23:55:05 rayo ! added support for begin-program/end-program ! added logic to try and determine whether a procedure is local/global ! ! ! Ray Ontko 1996.10.27 Created ! !%% - extracted BEGIN-SETUP section begins ! ! profile begin-setup ! #define PROFILE_ARRAY_SIZE 1000 ! create-array ! name=profile_array ! size={PROFILE_ARRAY_SIZE} ! field=name:char ! field=line:number ! ! profile end-setup !%% - extracted BEGIN-SETUP section ends ! !---------------------------------------------------------------- profile begin-procedure profile local move 'profile.tmp' to $tname move 'profile.bak' to $bname if isnull( $_p1 ) input $iname 'Program file' else move $_p1 to $iname end-if if isnull( $_p2 ) input $oname 'Generated file' else move $_p2 to $oname end-if do profile_file( $iname , $tname ) do fixup_files( $tname, $oname, $bname ) end-procedure ! profile !----------------------------------------------------------- profile_file begin-procedure profile_file( $sqr_fn , $gen_fn ) ! we should decide whether we want to use arrays or #variables ! I think that #variables would be faster, but we don't have a ! convenient way to sort them when we're done. We could do both, ! by using variables during the run, and then copy the data into ! an array for sorting. ! ! One problem with using variables, however, is that we need to ! know whether a given line being profiled is in a local or global ! procedure. This means that we have trouble deciding whether to ! put an extra underscore in the variable name. This can be easily ! solved by setting a local/global flag while we're scanning the ! file (the second time). But, we do have to be good at figuring ! out whether the file is local or global. ! ! some options to think about for the future: ! ! have the program track if, else, while, when, when-other, ! begin-select statements, and goto labels. This would be ! accomplished by identifying these as profile points and ! adding the appropriate code for each. I've more or less ! added the code for this; we should give an option for ! procedure-level profiling vs full. ! ! have the program profile included files. Each included ! file could also have a flag that prohibits profiling to ! that level. In this case, we would probably create local ! copies of the profiled include files, and the normal ifdef ! logic would determine whether these were compiled into the ! profiler driver program. This would involve re-naming the ! include statements in the driver, and in any recursively ! included modules. Alternately, the driver generator could ! include the modules, but we may run the risk of an infinite ! series of includes. ! ! have the program generate a listing by going back through the ! original input file and print the number of executions for each ! statement in the left margin. This, in turn, could be followed ! by the top-ten program sections. ! ! listing options: full (whole program, counts on every line) ! short (program counts by sections only) ! summary (top n program sections) ! ! scan the module to identify the names, locations, and number of profiles. ! scan the the module a second time to generate the file and insert: ! #debugp {code to initialize the #profile_n variables after begin-program} ! #debugp {code to display the output before end-program} ! #debugp {add 1 to #profile_n for each profiled line} ! open $sqr_fn as 1 for-reading record={SQRSH_LINE_MAX}:vary status=#status if #status = 0 move 0 to #profile_count read 1 into $line:{SQRSH_LINE_MAX} move 1 to #line while not #_end-file let $l = ltrim($line,' ') unstring $l by ' ' into $p1 $p2 lowercase $p1 evaluate $p1 when = 'begin-procedure' when = 'begin-select' when = 'if' when = 'else' when = 'when' when = 'when-other' when = 'while' put $line #line into profile_array(#profile_count) name line add 1 to #profile_count break end-evaluate read 1 into $line:{SQRSH_LINE_MAX} add 1 to #line end-while close 1 open $sqr_fn as 1 for-reading record={SQRSH_LINE_MAX}:vary status=#status if #status = 0 open $gen_fn as 2 for-writing record={SQRSH_LINE_MAX}:vary status=#status if #status = 0 move 0 to #count read 1 into $line:{SQRSH_LINE_MAX} move 1 to #line while not #_end-file let $l = ltrim($line,' ') unstring $l by ' ' into $p1 $p2 $p3 lowercase $p1 evaluate $p1 when = 'begin-procedure' lowercase $p3 if $p3 = 'local' or instr($p2,'(',1) > 0 move 1 to #local else move 0 to #local end-if when = 'begin-procedure' when = 'begin-select' when = 'if' when = 'else' when = 'when' when = 'when-other' when = 'while' write 2 from $line move #count to $count 88888 ! need to distinguish between local and global ! also need to set flag to print the following line, ! just in case the current command continues onto ! multiple lines. if #local write 2 from '#debugp add 1 to #_profile_' $count else write 2 from '#debugp add 1 to #profile_' $count end-if add 1 to #count break when = 'begin-program' when = 'begin-report' write 2 from $line break when = 'end-program' when = 'end-report' move 0 to #i while #i < #profile_count get $name #line from profile_array(#i) name line let $name = replace($name,'''','''''') let $name = replace($name,'!!','!!!!') move #i to $i 88888 move #line to $j 99999 write 2 from '#debugp show #profile_' $i ' edit ''999999999999'' ' '''' $j ' ' $name '''' add 1 to #i end-while write 2 from $line break ! remove any existing #debugp lines when = '#debugp' break when-other write 2 from $line break end-evaluate read 1 into $line:{SQRSH_LINE_MAX} add 1 to #line end-while close 2 end-if close 1 end-if end-if end-procedure ! profile_file