Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
| Download
GAP 4.8.9 installation with standard packages -- copy to your CoCalc project to get it
Project: cocalc-sagemath-dev-slelievre
Views: 418346############################################################################# ## #W profile.g GAP 4 package `browse' Thomas Breuer ## #Y Copyright (C) 2010, Lehrstuhl D für Mathematik, RWTH Aachen, Germany ## ############################################################################# ## #F BrowseProfile( [<functions>][,][<mincount>, <mintime>] ) ## ## <#GAPDoc Label="BrowseProfile_section"> ## <Section Label="sec:profiledisp"> ## <Heading>Profiling &GAP; functions–a Variant</Heading> ## ## A &Browse; adapted way to evaluate profiling results is ## to show the overview that is printed by the &GAP; function ## <Ref Func="DisplayProfile" BookName="ref"/> in a &Browse; table, ## which allows one to sort the profiled functions according to ## the numbers of calls, the time spent, etc., ## and to search for certain functions one is interested in. ## ## <ManSection> ## <Func Name="BrowseProfile" Arg="[functions][,][mincount, mintime]"/> ## ## <Description> ## The arguments and their meaning are the same as for the function ## <Ref Func="DisplayProfile" BookName="ref"/>, ## in the sense that the lines printed by that function correspond to the ## rows of the list that is shown by <Ref Func="BrowseProfile"/>. ## Initially, the table is sorted in the same way as the list shown by ## <Ref Func="BrowseProfile"/>; sorting the table by any of the first five ## columns will yield a non-increasing order of the rows. ## <P/> ## The threshold values <A>mincount</A> and <A>mintime</A> can be changed ## in visual mode via the user input <B>e</B>. ## If mouse events are enabled (see <Ref Func="NCurses.UseMouse"/>) then ## one can also use a mouse click on the current parameter value shown ## in the table header ## in order to enter the mode for changing the parameters. ## <P/> ## When a row or an entry in a row is selected, ## <Q>click</Q> shows the code of the corresponding function in a pager ## (see <Ref Func="NCurses.Pager"/>) whenever this is possible, as follows. ## If the function was read from a file then this file is opened, ## if the function was entered interactively then the code of the function ## is shown in the format produced by <Ref Func="Print" BookName="ref"/>; ## other functions (for example &GAP; kernel functions) cannot be shown, ## one gets an alert message (see <Ref Func="NCurses.Alert"/>) in such a ## case. ## <P/> ## The full functionality of the function ## <Ref Func="NCurses.BrowseGeneric"/> is available. ## <P/> ## <Example><![CDATA[ ## gap> n:= [ 14, 14, 14, 14, 14 ];; # ``do nothing'' ## gap> ProfileOperationsAndMethods( true ); # collect some data ## gap> ConjugacyClasses( PrimitiveGroup( 24, 1 ) );; ## gap> ProfileOperationsAndMethods( false ); ## gap> BrowseData.SetReplay( Concatenation( ## > "scso", # sort by column 1, ## > n, ## > "rso", # sort by column 2, ## > n, ## > "rso", # sort by column 3, ## > n, ## > "q", # deselect the column, ## > "/Centralizer", [ NCurses.keys.ENTER ], # search for a function, ## > n, "Q" ) ); # and quit ## gap> BrowseProfile(); ## gap> BrowseData.SetReplay( false ); ## ]]></Example> ## <P/> ## <E>Implementation remarks</E>: ## The browse table has a dynamic header, ## which shows the current values of <A>mincount</A> and <A>mintime</A>, ## and a dynamic footer, which shows the sums of counts and timings for the ## rows in the table (label <C>TOTAL</C>) and if applicable the sums for the ## profiled functions not shown in the table (label <C>OTHER</C>). ## There are no row labels, and the obvious column labels. ## There is no return value. ## <P/> ## The standard modes in <Ref Var="BrowseData"/> (except the <C>help</C> ## mode) have been modified ## by adding a new action for changing the threshold parameters ## <A>mincount</A> and <A>mintime</A> (user input <B>e</B>). ## The way how this in implemented made it necessary to change the standard ## <Q>reset</Q> action (user input <B>!</B>) of the table; ## note that resetting (a sorting or filtering of) the table must not ## make those rows visible that shall be hidden because of the ## threshold parameters. ## <P/> ## The code can be found in the file <F>app/profile.g</F> of the package. ## </Description> ## </ManSection> ## </Section> ## <#/GAPDoc> ## BindGlobal( "BrowseProfile", function( arg ) local i, funcs, mincount, mintime, info, reject, table, denom, widths, otim, osto, entry, rej, row, collabels, packagetotals, sel_action, recompute_hide_conditions, modes, editfields, myDealWithMouseClick, replactions, newactions, mode; # Stop profiling, in order to keep the computations in this function # away from the profiling data. for i in PROFILED_FUNCTIONS do UNPROFILE_FUNC( i ); od; # Get the arguments. funcs:= "all"; mincount:= GAPInfo.ProfileThreshold[1]; mintime:= GAPInfo.ProfileThreshold[2]; if Length( arg ) = 0 then # Keep these defaults. elif Length( arg ) = 1 and IsList( arg[1] ) then funcs:= arg[1]; elif Length( arg ) = 2 and IsInt( arg[1] ) and IsInt( arg[2] ) then mincount:= arg[1]; mintime:= arg[2]; elif Length( arg ) = 3 and IsList( arg[1] ) and IsInt( arg[2] ) and IsInt( arg[3] ) then funcs:= arg[1]; mincount:= arg[2]; mintime:= arg[3]; elif ForAll( arg, IsFunction ) then funcs:= arg; else # Start profiling again. for i in PROFILED_FUNCTIONS do PROFILE_FUNC( i ); od; Error( "usage: BrowseProfile( ", "[<functions>][,][<mincount>, <mintime>] )" ); fi; # Compute the table contents. # We create a row for each profiled function, and hide those rows that # shall not be shown because of the threshold parameters. # Note that the parameters can be changed interactively, # and we want to keep the same table. # For efficiency reasons, functions with zero count and zero time # are omitted except if `mincount' or `mintime' are zero, # and we forbid setting the parameters to zero if a nonzero parameter # was given as an argument. if mincount < 0 then mincount:= 0; fi; if mintime < 0 then mintime:= 0; fi; if mincount = 0 or mintime = 0 then info:= ProfileInfo( funcs, 0, 0 ); else info:= ProfileInfo( funcs, 1, 1 ); fi; reject:= [ false ]; table:= []; denom:= info.denom; widths:= info.widths; otim:= 0; osto:= 0; for entry in info.prof do rej:= entry[1] < mincount and entry[2] + entry[3] < mintime; row:= []; for i in [ 1 .. Length( denom ) ] do if denom[i] = 1 then row[i]:= String( entry[i] ); else row[i]:= String( QuoInt( entry[i], denom[i] ) ); fi; if widths[i] < 0 then row[i]:= rec( rows:= [ row[i] ], align:= "l" ); fi; od; Add( table, row ); Append( reject, [ rej, rej ] ); if rej then otim:= otim + entry[2]; osto:= osto + entry[4]; fi; od; if ForAll( [ 2 .. Length( reject ) ], i -> reject[i] ) then NCurses.Alert( [ "There are currently no functions", "to be profiled" ], 2000 ); return; fi; # Get the column labels. # We use the widths only for the alignment information. collabels:= []; for i in [ 1 .. Length( info.labelsCol) ] do if info.widths[i] < 0 then Add( collabels, rec( rows:= [ info.labelsCol[i] ], align:= "l" ) ); else Add( collabels, rec( rows:= [ info.labelsCol[i] ], align:= "r" ) ); fi; od; # Collect the total time and storage for each package involved. # (This information is used when the table is categorized by packages.) packagetotals:= function( t, prof ) local mincount, mintime, n, pkgpos, cntpos, timepos, chldpos, storpos, pkgnames, sumtime, sumstor, i, pkg, pos; if not IsBound( t.dynamic.packagetotals ) then mincount:= t.dynamic.mincount; mintime:= t.dynamic.mintime; n:= prof.labelsCol; pkgpos:= Position( n, "package" ); cntpos:= Position( n, " count" ); timepos:= Position( n, "self/ms" ); chldpos:= Position( n, "chld/ms" ); storpos:= Position( n, "stor/kb" ); pkgnames:= [ "GAP" ]; sumtime:= [ 0 ]; sumstor:= [ 0 ]; for i in prof.prof do if mincount <= i[ cntpos ] or mintime <= i[ timepos ] + i[ chldpos ] then pkg:= i[ pkgpos ]; pos:= Position( pkgnames, pkg ); if pos = fail then Add( pkgnames, pkg ); pos:= Length( pkgnames ); sumtime[ pos ]:= 0; sumstor[ pos ]:= 0; fi; sumtime[ pos ]:= sumtime[ pos ] + i[ timepos ]; sumstor[ pos ]:= sumstor[ pos ] + i[ storpos ]; fi; od; pos:= Length( t.work.startCollapsedCategory[1][3] ); t.dynamic.packagetotals:= [ pkgnames, List( sumtime, x -> QuoInt( x, prof.denom[ timepos ] ) ), List( sumstor, x -> QuoInt( x, prof.denom[ storpos ] ) ), Sum( t.work.widthCol{ [ 1 .. 2 * timepos ] } ) - pos, Sum( t.work.widthCol{ [ 1 .. 2 * storpos ] } ) - pos ]; fi; return t.dynamic.packagetotals; end; # Implement the ``click'' action. # (Essentially the same function is contained in `app/methods.g' # and `app/pkgvar.g'.) sel_action:= rec( helplines:= [ "show the code of the chosen function" ], action:= function( t ) local pos, func, file, lines, stream; if t.dynamic.selectedEntry <> [ 0, 0 ] then pos:= t.dynamic.indexRow[ t.dynamic.selectedEntry[1] ] / 2; func:= info.funs[ pos ]; file:= FilenameFunc( func ); if file = fail or file = "*stdin*" or not IsReadableFile( file ) then # Show the code in a pager. lines:= ""; stream:= OutputTextString( lines, true ); PrintTo( stream, func ); CloseStream( stream ); else # Show the file in a pager. lines:= rec( lines:= StringFile( file ), start:= StartlineFunc( func ) ); fi; if BrowseData.IsDoneReplay( t.dynamic.replay ) then NCurses.Pager( lines ); NCurses.update_panels(); NCurses.doupdate(); NCurses.curs_set( 0 ); fi; fi; return t.dynamic.changed; end ); # Construct the extended modes if necessary. if not IsBound( BrowseData.defaults.work.customizedModes.profile ) then # Create a shallow copy of each default mode for `Browse', # and adjust the actions. modes:= List( BrowseData.defaults.work.availableModes, BrowseData.ShallowCopyMode ); # If the threshold is changed interactively, we have to adjust the table. recompute_hide_conditions:= function( t ) local reject, i, entry, rej, changed; reject:= [ false ]; otim:= 0; osto:= 0; for i in [ 2, 4 .. Length( t.dynamic.indexRow ) - 1 ] do entry:= t.work.prof[ t.dynamic.indexRow[i] / 2 ]; rej:= entry[1] < t.dynamic.mincount and entry[2] + entry[3] < t.dynamic.mintime; Append( reject, [ rej, rej ] ); if rej then otim:= otim + entry[2]; osto:= osto + entry[4]; fi; od; changed:= reject <> t.dynamic.isRejectedRow or t.dynamic.topleft <> [ 1, 1, 1, 1 ]; if changed then t.dynamic.isRejectedRow:= reject; t.dynamic.topleft:= [ 1, 1, 1, 1 ]; t.dynamic.otim:= otim; t.dynamic.osto:= osto; fi; return changed; end; editfields:= function( t, focus ) local val, arecs, pos; val:= String( t.dynamic.mincount, 0 ); arecs:= [ rec( prefix:= "count >= ", default:= val, suffix:= "", ncols:= "fit", begin:= [ 1, 20 ] ), rec( prefix:= "self + chld >= ", default:= String( t.dynamic.mintime, 0 ), suffix:= " ms", ncols:= "fit", begin:= [ 1, 33 + Length( val ) ] ) ]; if focus = "mincount" then arecs[1].focus:= true; else arecs[2].focus:= true; fi; NCurses.hide_panel( t.dynamic.statuspanel ); val:= NCurses.EditFields( t.dynamic.window, arecs ); NCurses.show_panel( t.dynamic.statuspanel ); if val = fail then return t.dynamic.changed; fi; Perform( val, NormalizeWhitespace ); val:= List( val, Int ); # Do not set negative values, and forbid zero except if # zero had been entered as an argument. if val[1] <> fail and t.dynamic.mincount <> val[1] and ( 0 < val[1] or ( 0 = val[1] and t.dynamic.mincount_orig = 0 ) ) then t.dynamic.changed:= true; t.dynamic.mincount:= val[1]; fi; if val[2] <> fail and t.dynamic.mintime <> val[2] and ( 0 <= val[2] or ( 0 = val[2] and t.dynamic.mintime_orig = 0 ) ) then t.dynamic.changed:= true; t.dynamic.mintime:= val[2]; fi; if t.dynamic.changed = true then recompute_hide_conditions( t ); Unbind( t.dynamic.packagetotals ); fi; return t.dynamic.changed; end; myDealWithMouseClick:= function( t, data, flag ) local pos, len; # If a parameter in the header is seleced then change it. pos:= BrowseData.PositionInBrowseTable( t, data ); if pos[1] = "header" and pos[2][1] = 3 then len:= Length( String( t.dynamic.mincount ) ); if pos[2][2] >= 22 and pos[2][2] < 31 + len then # focus on the first parameter return editfields( t, "mincount" ); elif pos[2][2] >= 35 + len and pos[2][2] < 50 + len + Length( String( t.dynamic.mintime ) ) then # focus on the second parameter return editfields( t, "mintime" ); fi; fi; # Otherwise do the same as the standard action. return BrowseData.DealWithMouseClick( t, data, flag ); end; # changed actions in any mode except help mode: # - reset the table # - mouse click on a parameter in the header replactions:= [ [ [ "!" ], rec( helplines := [ "unsort the table, recompute hide conditions", "according to the current thresholds,", "if necessary scroll up to the first row" ], action := function( t ) local changed; t.dynamic.changed:= BrowseData.actions.ResetTable.action( t ); changed:= recompute_hide_conditions( t ); t.dynamic.changed:= t.dynamic.changed or changed; return t.dynamic.changed; end ) ], [ [ [ [ NCurses.keys.MOUSE, "BUTTON1_PRESSED" ], "<Mouse1Down>" ], [ [ NCurses.keys.MOUSE, "BUTTON1_CLICKED" ], "<Mouse1Click>" ] ], rec( helplines:= Concatenation( [ "change the threshold parameter in the table header, or" ], BrowseData.actions.DealWithSingleMouseClick.helplines ), action := function( t, data ) return myDealWithMouseClick( t, data, false ); end ) ], [ [ [ [ NCurses.keys.MOUSE, "BUTTON1_DOUBLE_CLICKED" ], "<Mouse1DoubleClick>" ] ], rec( helplines:= Concatenation( [ "change the threshold parameter in the table header, or" ], BrowseData.actions.DealWithDoubleMouseClick.helplines ), action := function( t, data ) return myDealWithMouseClick( t, data, true ); end ) ], ]; # new actions in any mode except help mode: # - e: Change the value of the threshold parameters newactions:= [ [ [ "e" ], rec( helplines:= [ "change the threshold parameters" ], action:= t -> editfields( t, "mincount" ) ) ], ]; for mode in modes do if mode.name <> "help" then BrowseData.SetActions( mode, replactions, "replace" ); BrowseData.SetActions( mode, newactions ); fi; od; BrowseData.defaults.work.customizedModes.profile:= modes; fi; modes:= BrowseData.defaults.work.customizedModes.profile; # Construct and show the browse table. NCurses.BrowseGeneric( rec( work:= rec( align:= "tl", minyx:= [ 10, 1 ], availableModes:= modes, prof:= info.prof, # The header is dynamic because `mincount' and `mintime' can vary. header:= function( t ) return [ "", [ NCurses.attrs.UNDERLINE, true, "GAP Profiling", NCurses.attrs.NORMAL ], Concatenation( "(show functions with count >= ", String( t.dynamic.mincount ), " or self + chld >= ", String( t.dynamic.mintime ), " ms)" ), "" ]; end, # The footer is dynamic because we have to emulate scrolling # horizontally, and because the `OTHER' values can vary. footer:= function( t ) local start, w, lines, qotim, qosto, line; start:= Sum( List( [ 1 .. t.dynamic.topleft[2] - 1 ], j -> BrowseData.WidthCol( t, j ) ), t.dynamic.topleft[4] ); w:= List( [ [ 1 .. 4 ], [ 5 .. 8 ], [ 9 .. Length( collabels ) - 1 ] ], l -> Sum( List( l, j -> BrowseData.WidthCol( t, j ) ) ) ); lines:= []; qotim:= QuoInt( t.dynamic.otim, denom[2] ); qosto:= QuoInt( t.dynamic.osto, denom[4] ); if 0 < qotim or 0 < qosto then line:= Concatenation( String( qotim, w[1] ), String( qosto, w[2] ), String( " ", w[3] ), "OTHER" ); lines[1]:= line{ [ start .. Length( line ) ] }; else lines[1]:= ""; fi; line:= Concatenation( String( QuoInt( info.ttim, denom[2] ), w[1] ), String( QuoInt( info.tsto, denom[4] ), w[2] ), String( " ", w[3] ), "TOTAL" ); lines[2]:= line{ [ start .. Length( line ) ] }; return lines; end, main:= table, labelsCol:= [ collabels ], sepLabelsCol:= "=", sepCol:= Concatenation( [ "" ], ListWithIdenticalEntries( Length( collabels ) - 1, info.sepCol ), [ "" ] ), SpecialGrid:= BrowseData.SpecialGridLineDraw, Click:= rec( select_entry:= sel_action, select_row:= sel_action, ), # If the column shows the package assignment then show the # total time and storage used by this package in the category row. CategoryValues:= function( t, i, j ) local cats, totals, k, cat, pos, len; cats:= BrowseData.defaults.work.CategoryValues( t, i, j ); if info.labelsCol[ j/2 ] = "package" then totals:= packagetotals( t, info ); for k in [ 1 .. Length( cats ) ] do cat:= cats[k]; if cat = "(empty category)" then cat:= "(none)"; pos:= Position( totals[1], "" ); else pos:= Position( totals[1], cat ); fi; len:= totals[4] - Length( cat ) - 1; cat:= Concatenation( cat, " ", String( totals[2][ pos ], len ) ); Append( cat, " " ); len:= totals[5] - Length( cat ); Append( cat, String( totals[3][ pos ], len ) ); cats[k]:= cat; od; fi; return cats; end, ), dynamic:= rec( activeModes:= [ First( modes, x -> x.name = "browse" ) ], isRejectedRow:= reject, sortFunctionsForColumns:= ListWithIdenticalEntries( 5, BrowseData.CompareLenLexRev ), mincount:= mincount, mintime:= mintime, mincount_orig:= mincount, mintime_orig:= mintime, otim:= otim, osto:= osto, ), ) ); # Start profiling again. for i in PROFILED_FUNCTIONS do PROFILE_FUNC( i ); od; end ); ############################################################################# ## ## Add the Browse application to the list shown by `BrowseGapData'. ## BrowseGapDataAdd( "DisplayProfile as a Browse application", BrowseProfile, false, "\ the profiled operations, methods, and global functions, \ shown in a browse table; \ the columns of the table contain \ the names of the functions, the number of calls, \ and the time needed; \ ``click'' shows the code of the function if possible" ); ############################################################################# ## #E