diff --git a/M2/Macaulay2/bin/main.cpp b/M2/Macaulay2/bin/main.cpp index fa4c8540cf2..5e69d9636f4 100644 --- a/M2/Macaulay2/bin/main.cpp +++ b/M2/Macaulay2/bin/main.cpp @@ -122,15 +122,10 @@ int main(/* const */ int argc, /* const */ char *argv[], /* const */ char *env[] /* ######################################################################### */ -std::ofstream prof_log; -thread_local std::vector M2_stack; - -void stack_trace(std::ostream &stream, bool M2) { - if(M2) { - stream << "M2"; - for (char* M2_frame : M2_stack) - stream << ";" << M2_frame; - stream << std::endl; +void profiler_stacktrace(std::ostream &stream, int traceDepth) { + if(0 < traceDepth) { + // TODO: pipe output to the stream + profiler_stacktrace(traceDepth); } else { stream << "-* stack trace, pid: " << (long) getpid() << std::endl; stream << boost::stacktrace::stacktrace(); @@ -139,29 +134,19 @@ void stack_trace(std::ostream &stream, bool M2) { } void M2_flint_abort(void) { - stack_trace(std::cerr, false); + profiler_stacktrace(std::cerr, 0); abort(); } -extern "C" { - void M2_stack_trace() { stack_trace(std::cout, false); } -#if PROFILING - void M2_stack_push(char* M2_frame) { M2_stack.emplace_back(M2_frame); } - void M2_stack_pop() { M2_stack.pop_back(); } -#else - void M2_stack_push(char* M2_frame) {} - void M2_stack_pop() {} -#endif -} - void* profFunc(ArgCell* p) { using namespace std::chrono_literals; std::string filename("profile-" + std::to_string(getpid())+ ".raw"); - // std::cerr << "Saving profile data in " << filename << std::endl; - prof_log.open(filename, std::ios::out | std::ios::trunc ); + std::cerr << "-- Storing profiling data in " << filename << std::endl; + std::ofstream prof_log(filename, std::ios::out | std::ios::trunc ); while(true) { - std::this_thread::sleep_for(1000ms); + // use prime number to avoid oversampling scheduled tasks + std::this_thread::sleep_for(997ms); tryGlobalTrace(); } return NULL; @@ -235,7 +220,7 @@ extern "C" void oursignal(int sig, void (*handler)(int)) { void trace_handler(int sig) { if (tryGlobalTrace() == 0) - stack_trace(prof_log, true); + profiler_stacktrace(std::cerr, 1); oursignal(SIGUSR1,trace_handler); } @@ -253,7 +238,7 @@ void segv_handler(int sig) { fprintf(stderr,"-- SIGSEGV handler called a second time, aborting\n"); _exit(2); } - stack_trace(std::cerr, false); + profiler_stacktrace(std::cerr, 0); level --; _exit(1); } @@ -292,7 +277,7 @@ void interrupt_handler(int sig) { } } if (buf[0]=='b' || buf[0]=='B') { - stack_trace(std::cout, false); + profiler_stacktrace(std::cout, 0); fprintf(stderr,"exiting\n"); exit(12); } diff --git a/M2/Macaulay2/d/CMakeLists.txt b/M2/Macaulay2/d/CMakeLists.txt index 20fbb477c5a..9114704d792 100644 --- a/M2/Macaulay2/d/CMakeLists.txt +++ b/M2/Macaulay2/d/CMakeLists.txt @@ -84,6 +84,7 @@ set(DLIST xmlactors.d # removed unless WITH_XML actors5.d chrono.dd + profiler.dd threads.dd python.d # removed unless WITH_PYTHON interface.dd interface2.d diff --git a/M2/Macaulay2/d/Makefile.files.in b/M2/Macaulay2/d/Makefile.files.in index 7b8e4c617c3..7202f2a757e 100644 --- a/M2/Macaulay2/d/Makefile.files.in +++ b/M2/Macaulay2/d/Makefile.files.in @@ -88,6 +88,7 @@ M2_SRCFILES += xmlactors.d endif M2_DFILES += actors5.d M2_DFILES += chrono.dd +M2_DFILES += profiler.dd M2_DFILES += threads.dd ifeq (@PYTHON@,yes) diff --git a/M2/Macaulay2/d/actors4.d b/M2/Macaulay2/d/actors4.d index 9185872f813..012ceae8efc 100644 --- a/M2/Macaulay2/d/actors4.d +++ b/M2/Macaulay2/d/actors4.d @@ -1081,6 +1081,8 @@ setupfun("connectionCount", connectionCount); format(e:Expr):Expr := ( when e is s:stringCell do toExpr("\"" + present(s.v) + "\"") + is ZZcell do e + is QQcell do e is RRcell do format(Expr(Sequence(e))) is RRicell do format(Expr(Sequence(e))) is CCcell do format(Expr(Sequence(e))) @@ -1102,6 +1104,8 @@ format(e:Expr):Expr := ( is Nothing do nothing else return WrongArgZZ(4); if n > 5 then when args.4 is p:stringCell do sep = p.v else return WrongArgString(5); when args.(n-1) + is x:ZZcell do toExpr(concatenate(format(s,ac,l,t,sep,toRR(x.v,defaultPrecision)))) + is x:QQcell do toExpr(concatenate(format(s,ac,l,t,sep,toRR(x.v,defaultPrecision)))) is x:RRcell do toExpr(concatenate(format(s,ac,l,t,sep,x.v))) is z:CCcell do toExpr(format(s,ac,l,t,sep,false,false,z.v)) else WrongArgRR(n) diff --git a/M2/Macaulay2/d/actors5.d b/M2/Macaulay2/d/actors5.d index fdde6bcd9ae..788a2d550cf 100644 --- a/M2/Macaulay2/d/actors5.d +++ b/M2/Macaulay2/d/actors5.d @@ -1741,8 +1741,10 @@ storeInHashTable( Expr(newCompiledFunction(storeGlobalDictionaries))); getcwdfun(e:Expr):Expr := ( -- this has to be a function, because getcwd may fail - when e is s:Sequence do - if length(s) == 0 then cwd() else WrongNumArgs(0) + when e is s:Sequence do if length(s) != 0 then WrongNumArgs(0) else ( + dir := getcwd(); + if dir != "" then Expr(stringCell(dir)) + else buildErrorPacket("can't get current working directory: " + syserrmsg())) else WrongNumArgs(0)); setupfun("currentDirectory",getcwdfun); diff --git a/M2/Macaulay2/d/binding.d b/M2/Macaulay2/d/binding.d index 7276cf35803..2e0906aac23 100644 --- a/M2/Macaulay2/d/binding.d +++ b/M2/Macaulay2/d/binding.d @@ -306,24 +306,25 @@ bumpPrecedence(); export leftparen := parens("(",")",precSpace, precRightParen, precRightParen); export leftbrace := parens("{","}",precSpace, precRightParen, precRightParen); parseWORD.precedence = prec; parseWORD.binaryStrength = nopr; parseWORD.unaryStrength = nopr; - export timeS := special("time",unaryop,precSpace,wide); - export timingS := special("timing",unaryop,precSpace,wide); - export elapsedTimeS := special("elapsedTime",unaryop,precSpace,wide); - export elapsedTimingS := special("elapsedTiming",unaryop,precSpace,wide); - export shieldS := special("shield",unaryop,precSpace,wide); - export TestS := special("TEST",unaryop,precSpace,wide); - export throwS := special("throw",nunaryop,precSpace,wide); - export returnS := special("return",nunaryop,precSpace,wide); - export breakS := special("break",nunaryop,precSpace,wide); - export continueS := special("continue",nunaryop,precSpace,wide); - export stepS := special("step",nunaryop,precSpace,wide); - -- export codePositionS := special("codePosition",unaryop,precSpace,narrow); - special("new",unarynew,precSpace,narrow); - special("for",unaryfor,precSpace,narrow); - special("while",unarywhile,precSpace,wide); - special("if",unaryif,precSpace,wide); - special("try",unarytry,precSpace,wide); - special("catch",unarycatch,precSpace,wide); + export TestS := special("TEST", unaryop, precSpace, wide); + export timeS := special("time", unaryop, precSpace, wide); + export timingS := special("timing", unaryop, precSpace, wide); + export elapsedTimeS := special("elapsedTime", unaryop, precSpace, wide); + export elapsedTimingS := special("elapsedTiming", unaryop, precSpace, wide); + export breakpointS := special("breakpoint", unaryop, precSpace, wide); + export profileS := special("profile", unaryop, precSpace, wide); + export shieldS := special("shield", unaryop, precSpace, wide); + export throwS := special("throw", nunaryop, precSpace, wide); + export returnS := special("return", nunaryop, precSpace, wide); + export breakS := special("break", nunaryop, precSpace, wide); + export continueS := special("continue", nunaryop, precSpace, wide); + export stepS := special("step", nunaryop, precSpace, wide); + special("new", unarynew, precSpace, narrow); + special("for", unaryfor, precSpace, narrow); + special("while", unarywhile, precSpace, wide); + special("if", unaryif, precSpace, wide); + special("try", unarytry, precSpace, wide); + special("catch", unarycatch, precSpace, wide); bumpPrecedence(); export ParenStarParenS := makeKeyword(postfix("(*)")); bumpPrecedence(); @@ -360,10 +361,10 @@ bumpPrecedence(); --export UnderscoreSharpS := makeKeyword(postfix("_#")); bumpPrecedence(); --why are these using precSpace and not prec? - special("symbol",unarysymbol,precSpace,prec); - special("global",unaryglobal,precSpace,prec); - special("threadLocal",unarythread,precSpace,prec); - special("local",unarylocal,precSpace,prec); + special("symbol", unarysymbol, precSpace, prec); + special("global", unaryglobal, precSpace, prec); + special("threadLocal", unarythread, precSpace, prec); + special("local", unarylocal, precSpace, prec); ----------------------------------------------------------------------------- export GlobalAssignS := makeProtectedSymbolClosure("GlobalAssignHook"); export GlobalAssignE := Expr(GlobalAssignS); diff --git a/M2/Macaulay2/d/chrono.dd b/M2/Macaulay2/d/chrono.dd index 038f3aae0e2..5f2ba80297f 100644 --- a/M2/Macaulay2/d/chrono.dd +++ b/M2/Macaulay2/d/chrono.dd @@ -30,15 +30,17 @@ cpuTime(e:Expr):Expr := ( else WrongNumArgs(0)); setupfun("cpuTime", cpuTime); -cpuTimer(c:Code):Sequence := ( +export cpuTimer(c:Code):Sequence := ( T0 := cpuTime(); ret := eval(c); T1 := cpuTime(); Sequence(toExpr(T1 - T0), ret)); -wallTimer(c:Code):Sequence := ( +export wallTimer(c:Code):Sequence := ( T0 := Ccode(Chrono, "std::chrono::steady_clock::now()"); - ret := eval(c); + -- Note: here we intentionally call evalraw so + -- that evalprof can call this function safely. + ret := evalraw(c); T1 := Ccode(Chrono, "std::chrono::steady_clock::now()"); dT := double(Ccode(uint64_t, "(", T1, " - ", T0, ").count()")); dT = dT/1000000000.; diff --git a/M2/Macaulay2/d/debugging.dd b/M2/Macaulay2/d/debugging.dd index fc93b42b648..b138752b7fb 100644 --- a/M2/Macaulay2/d/debugging.dd +++ b/M2/Macaulay2/d/debugging.dd @@ -3,6 +3,15 @@ use common; use util; use hashtables; +-- debugger is not defined until interp.dd +-- so we use a pointer and populate it later. +dummydebugger(f:Frame,c:Code):Expr := nullE; +export debuggerpointer := dummydebugger; + +-- enters the debugger (defined in interp.dd) at that spot +breakpoint(c:Code):Expr := debuggerpointer(localFrame, c); +setupop(breakpointS, breakpoint); + export tostring(c:Code):string := ( when c is x:nullCode do "(null)" diff --git a/M2/Macaulay2/d/evaluate.d b/M2/Macaulay2/d/evaluate.d index 209b866ea7c..bc0724644c5 100644 --- a/M2/Macaulay2/d/evaluate.d +++ b/M2/Macaulay2/d/evaluate.d @@ -4,6 +4,11 @@ use hashtables; use convertr; use debugging; +-- evalprof is not defined until profiler.dd +-- so we use a pointer and populate it later. +dummyevalprof(c:Code):Expr := nullE; +export evalprofpointer := dummyevalprof; + export globalAssignmentHooks := newHashTableWithHash(mutableHashTableClass,nothingClass); setupconst("globalAssignmentHooks",Expr(globalAssignmentHooks)); export threadLocal evalSequenceHadError := false; @@ -14,6 +19,7 @@ threadLocal errorreturn := nullE; threadLocal recycleBin := new array(Frame) len 20 do provide dummyFrame; export trace := false; threadLocal export backtrace := true; +threadLocal export profiling := false; threadLocal lastCode := dummyCode; threadLocal lastCodePosition := Position("",ushort(0),ushort(0),ushort(0),ushort(0),ushort(0),ushort(0),ushort(0)); export chars := new array(Expr) len 256 do ( @@ -1340,9 +1346,7 @@ export handleError(c:Code,e:Expr):Expr := ( if !err.printed || backtrace && localFrame != oldReportFrame then ( if debuggingMode && !stopIfError && (! (p.filename === "stdio")) then ( if !err.printed then printError(err); - printErrorMessage(err.position,"--entering debugger (type help to see debugger commands)"); - z := debuggerFun(localFrame,c); - -- printErrorMessage(err.position,"--leaving debugger"); + z := debuggerpointer(localFrame,c); when z is z:Error do ( if z.message == breakMessage then buildErrorPacket(unwindMessage) else if z.message == returnMessage then ( @@ -1373,28 +1377,10 @@ export handleError(c:Code,e:Expr):Expr := ( e)) else e); -header "extern void M2_stack_push(char*);"; -header "extern void M2_stack_pop();"; -header "extern void M2_stack_trace();"; -stacktrace(e:Expr):Expr := ( - Ccode(void,"M2_stack_trace()"); e ); -setupfun("stacktrace",stacktrace); - -evalprof(c:Code):Expr; -evalraw(c:Code):Expr; export eval(c:Code):Expr := ( - if Ccode(bool,"PROFILING == 1") - then Ccode(Expr,"evaluate_evalprof(",c,")") - else Ccode(Expr,"evaluate_evalraw(",c,")")); -export evalprof(c:Code):Expr := ( - when c is f:semiCode do ( -- what makes semiCode special? - -- printErrorMessage(codePosition(c),"--evaluating a semiCode"); - -- TODO: how to get f.name? - Ccode(void,"M2_stack_push(",tocharstar(tostring(codePosition(c))),")"); - e := evalraw(c); - Ccode(void,"M2_stack_pop()"); - e) - else evalraw(c)); + if profiling -- see evalprof in profiling.dd + then Ccode(Expr, "evaluate_evalprofpointer(", c, ")") + else Ccode(Expr, "evaluate_evalraw(", c, ")")); export evalraw(c:Code):Expr := ( -- # typical value: symbol SPACE, Function, Thing, Thing -- better would for cancellation requests to set exceptionFlag: diff --git a/M2/Macaulay2/d/interp.dd b/M2/Macaulay2/d/interp.dd index 9b39135ee63..42c2e56c067 100644 --- a/M2/Macaulay2/d/interp.dd +++ b/M2/Macaulay2/d/interp.dd @@ -5,6 +5,7 @@ use evaluate; use parser; use texmacs; use actors5; +use profiler; import dirname(s:string):string; @@ -493,6 +494,7 @@ debugger(f:Frame,c:Code):Expr := ( setDebuggingMode(false); oldDebuggerCode := getGlobalVariable(currentS); setGlobalVariable(currentS,Expr(PseudocodeClosure(f,c))); + printMessage(codePosition(c), "entering debugger (enter 'help' to see commands)"); incrementInterpreterDepth(); if debuggerHook != nullE then ( r := applyEE(debuggerHook,True); @@ -503,12 +505,14 @@ debugger(f:Frame,c:Code):Expr := ( r := applyEE(debuggerHook,False); when r is Error do return r else nothing; ); + if debugLevel != 0 then + printMessage(codePosition(c), "leaving debugger"); decrementInterpreterDepth(); setGlobalVariable(currentS,oldDebuggerCode); setDebuggingMode(true); recursionDepth = oldrecursionDepth; ret); -debuggerFun = debugger; +debuggerpointer = debugger; currentString := setupvar("currentString", nullE); value(e:Expr):Expr := ( diff --git a/M2/Macaulay2/d/profiler.dd b/M2/Macaulay2/d/profiler.dd new file mode 100644 index 00000000000..8e577a5a35b --- /dev/null +++ b/M2/Macaulay2/d/profiler.dd @@ -0,0 +1,95 @@ +-- Copyright 2024 by Mahrud Sayrafi + +use binding; -- for symbols +use evaluate; -- for eval +use chrono; -- for wallTimer +use sets; -- for Tally + +header " +#include +#include +#include + +// defined in main.cpp, uses Boost::stacktrace +void profiler_stacktrace(std::ostream &, int); + +// the top-level runtime stack +thread_local std::vector M2_stack;"; + +-- TODO: implement better std::vector utilities +-- push and pop the given positions in the top-level call stack log +stackpush(p:Position):Expr := ( Ccode(void, "M2_stack.emplace_back(", p, ")"); locate(p) ); +stackpop(e:Expr):Expr := ( Ccode(void, "M2_stack.pop_back()"); e ); + +-- if traceDepth = 0, _prints_ the engine stacktrace using Boost::stacktrace +-- if traceDepth > 0, returns a sequence of positions from the parsing tree +-- TODO: limit this to Code, packages, stc for traceDepth = 1, 2, 3, etc. +export stacktrace(traceDepth:int):Expr := ( + if traceDepth == 0 + then ( Ccode(void, "profiler_stacktrace(std::cout, 0)"); nullE ) + else Expr(new Sequence len Ccode(int, "M2_stack.size()") at i do + provide locate(Ccode(Position, "M2_stack[", i, "]")))); +stacktrace(e:Expr):Expr := ( + when e is n:ZZcell do if !isInt(n) then WrongArgSmallInteger(1) + else stacktrace(toInt(n)) + else WrongArgSmallInteger(1)); +setupfun("stacktrace", stacktrace); + +-- this hash table will contain the accumulation of +-- profiling data (ticks and times) in each session +-- TODO: this should be thread local as well +ProfileTable := newHashTable(Tally, nothingClass); +ProfileTable.beingInitialized = false; -- mutex locks will occur +setupconst("ProfileTable", Expr(ProfileTable)); + +-- increments the entry corresponding to key in ProfileTable. +increment(key:Expr, data:Sequence):Expr := ( + seconds := data.0; + outcome := data.1; + -- TODO: optimize the bucket search + current := lookup(ProfileTable, key); + storeInHashTable(ProfileTable, key, + Expr(when current is s:Sequence + do Sequence(seconds + s.0, oneE + s.1) + else Sequence(seconds, oneE))); + outcome); + +-- pushes the code position into the stack, times the evaluation, +-- increments the stopwatch, pops the stack, and returns the value! +-- Note: wallTimer calls evalraw, not eval +measure(c:Code):Expr := stackpop(increment(stackpush(codePosition(c)), wallTimer(c))); + +-- when profiling = true is set, eval is substituted with evalprof, +-- so that everytime evalraw recursively calls eval, this one runs. +export evalprof(c:Code):Expr := ( + -- stdIO << tostring(codePosition(c)) << " " << tostring(c) << endl; + when c + is c:ifCode do ( + p := measure(c.predicate); + when p is Error do p + else if p == True then measure(c.thenClause) + else if p == False then measure(c.elseClause) + else printErrorMessageE(c.predicate, "expected true or false")) + is v:semiCode do ( + i := 0; + w := v.w; + r := nullE; + n := length(w); + while i < n do ( + r = measure(w.i); + i = when r is Error do n else i+1); + r) + else evalraw(c)); +evalprofpointer = evalprof; + +-- briefly enables the profiler and evaluates the code that follows, +-- updating the resulting statistics in the global ProfileTable. +profile(c:Code):Expr := ( + if debugLevel == -2 then printMessage(codePosition(c), "entering profiler"); + profiling = true; + data := cpuTimer(c); -- calls eval, not evalraw + profiling = false; + if debugLevel == -2 then printMessage(codePosition(c), "leaving profiler"); + -- increment the total time in profiler and return the result + increment(toExpr("total"), data)); +setupop(profileS, profile); diff --git a/M2/Macaulay2/d/scclib.c b/M2/Macaulay2/d/scclib.c index bd44a26df0b..6ea015b5cf9 100644 --- a/M2/Macaulay2/d/scclib.c +++ b/M2/Macaulay2/d/scclib.c @@ -10,7 +10,7 @@ #include "../system/supervisorinterface.h" -extern void M2_stack_trace(); +extern void profiler_stacktrace(int); void fatal(const char *s,...) { @@ -23,7 +23,7 @@ fatal(const char *s,...) { #ifndef NDEBUG trap(); #endif - M2_stack_trace(); + profiler_stacktrace(0); exit(1); } diff --git a/M2/Macaulay2/d/stdiop.d b/M2/Macaulay2/d/stdiop.d index 5cd4a8c6a59..013264f5671 100644 --- a/M2/Macaulay2/d/stdiop.d +++ b/M2/Macaulay2/d/stdiop.d @@ -127,7 +127,7 @@ cleanscreen():void := ( if stdIO.outfd == stdError.outfd && !atEndOfLine(stdIO) || test(interruptedFlag) then stdIO << newline << flush;); -printMessage(position:Position,message:string):void := ( +export printMessage(position:Position,message:string):void := ( if !SuppressErrors then ( cleanscreen(); stdError << position; diff --git a/M2/Macaulay2/d/tokens.d b/M2/Macaulay2/d/tokens.d index 59e433da4a4..15d661261f8 100644 --- a/M2/Macaulay2/d/tokens.d +++ b/M2/Macaulay2/d/tokens.d @@ -55,12 +55,6 @@ export steppingMessage := "--stepping limit reached"; --export buildErrorPacket(message:string):Expr := Expr(Error(dummyPosition,message,nullE,false,dummyFrame)); --export buildErrorPacket(pos:Position,message:string):Expr := Expr(Error(pos,message,nullE,false,dummyFrame)); --export buildErrorPacketErrno(msg:string,errnum:int):Expr := buildErrorPacket( msg + ": " + strerror(errnum) ); -export cwd():Expr := ( - r := getcwd(); - if r === "" then buildErrorPacket("can't get current working directory: " + syserrmsg()) - else Expr(stringCell(r))); -dummyDebuggerFun(f:Frame,c:Code):Expr := nullE; -export debuggerFun := dummyDebuggerFun; export handleInterrupts := true; (threadLocal export stopIfError := true) = false; (threadLocal export debuggingMode := false) = true; diff --git a/M2/Macaulay2/m2/exports.m2 b/M2/Macaulay2/m2/exports.m2 index 2d9febd66ec..9e621c748b8 100644 --- a/M2/Macaulay2/m2/exports.m2 +++ b/M2/Macaulay2/m2/exports.m2 @@ -527,6 +527,7 @@ export { "binomial", "borel", "break", + "breakpoint", "cache", "cacheValue", "cancelTask", @@ -595,6 +596,7 @@ export { "coth", "cover", "coverMap", + "coverageSummary", "cpuTime", "createTask", "csc", @@ -1151,6 +1153,7 @@ export { "splitWWW", "sqrt", "stack", + "stacktrace", "standardForm", "standardPairs", "stashValue", diff --git a/M2/Macaulay2/m2/profile.m2 b/M2/Macaulay2/m2/profile.m2 index 1fc5d5fb589..7e07fb6b88b 100644 --- a/M2/Macaulay2/m2/profile.m2 +++ b/M2/Macaulay2/m2/profile.m2 @@ -1,32 +1,38 @@ --- Copyright 1997 by Daniel R. Grayson +-- Copyright 2024 by Mahrud Sayrafi -needs "methods.m2" - -profile = method() +-- 'profile' is an interpreter keyword, defined in d/profiler.dd, +-- which logs statistics of executed M2 code in 'ProfileTable'. +-- TODO: log the relationship between function calls and return +-- in an external format like Graphviz, pprof, etc. -record := new MutableHashTable - -profile Function := Function => f -> profile (toString f, f) +needs "methods.m2" -profile(String,Function) := (n,f) -> ( - record#n = m := new MutableList from {0,0.}; - args -> ( - ret := timing f args; - m#0 = m#0 + 1; - m#1 = m#1 + ret#0; - ret#1 - ) - ) +head := () -> ("#run", "%time", "position") +form := (ttime, t, n, loc) -> (n, format(4,2,2,2,"e", 100 * t / ttime), loc) +tail := (ttime, tticks) -> (tticks, format(4,4,4,4,"e",ttime) | "s", "elapsed total") -profileSummary = Command (() -> - scan(sort pairs record, (n,v) -> - if v#0 != 0 then - << n << ": " - << v#0 << " times, used " - << v#1 << " seconds" - << endl - )) +-- prints the statistics logged by the profiler in a readable table +profileSummary = method(Dispatch => Thing) +profileSummary Thing := x -> profileSummary if x === () then "" else first locate x +profileSummary String := filename -> ( + dataset := select(pairs ProfileTable, + (k, v) -> match_filename toString k); + if #dataset == 0 then return TABLE {head(), tail(0,0)}; + (ttime, tticks) := ProfileTable#"total"; + data := sort pairs hashTable(join, apply(dataset, + (k, v) -> if k =!= "total" then (v, {k}))); + rows := min(20, #data); + high := reverse take(data, {#data - rows - 1, #data - 1}); + body := apply(rows, i -> form_ttime splice high#i); + TABLE join({head()}, body, {tail(ttime, tticks)})) +profileSummary = new Command from profileSummary --- Local Variables: --- compile-command: "make -C $M2BUILDDIR/Macaulay2/m2 " --- End: +-- prints a list of lines which have been seen by the profiler so far +-- TODO: also highlight missing lines or sections within a line +-- TODO: compute the percentage of covered code +coverageSummary = method(Dispatch => Thing) +coverageSummary Thing := x -> coverageSummary if x === () then "" else first locate x +coverageSummary String := filename -> ( + body := sort select(toString \ keys ProfileTable, match_filename); + stack join({"covered lines:"}, body)) +coverageSummary = new Command from coverageSummary diff --git a/M2/Macaulay2/m2/typicalvalues.m2 b/M2/Macaulay2/m2/typicalvalues.m2 index c353e341f3f..2c32f4438d2 100644 --- a/M2/Macaulay2/m2/typicalvalues.m2 +++ b/M2/Macaulay2/m2/typicalvalues.m2 @@ -6,7 +6,12 @@ needs "methods.m2" needs "lists.m2" +-- TODO: are these used in the documentation? +-- TODO: how to find all missing keywords? +-- TODO: is there a better place for these? typicalValues#(symbol timing) = Time +typicalValues#(symbol elapsedTiming) = Time +typicalValues#(symbol threadLocal) = Symbol typicalValues#(symbol local) = Symbol typicalValues#(symbol global) = Symbol typicalValues#(symbol symbol) = Symbol @@ -118,10 +123,11 @@ generateTypicalValues = (srcdir) -> ( extracted := select(typicalValuesFormat | " -- (.*)$", "typval(\\1, \\2) -- \\3", toString srcstring); extracted = apply(extracted, line -> first select("(.*?)--(.*?)$", "\\1" | pad_(91-#line) "\t-- \\2", line)); if 0 < #extracted then outfile << comment << endl << stack extracted << endl); - close outfile) + outfile << "-- DONE: generated based on " | version#"git description" << endl << close) --- if missing, tvalues.m2 is regenerated directly -if not fileExists typicalValuesSource then generateTypicalValues(currentFileDirectory | "../d/") +-- if missing or not successfully generated, tvalues.m2 is regenerated directly +if not fileExists typicalValuesSource or not match("-- DONE", get typicalValuesSource) +then generateTypicalValues(currentFileDirectory | "../d/") ----------------------------------------------------------------------------- -- numerical functions that will be wrapped diff --git a/M2/Macaulay2/packages/EngineTests/LinearAlgebra.Test.Base.m2 b/M2/Macaulay2/packages/EngineTests/LinearAlgebra.Test.Base.m2 index 6d3189d10f6..edcb4d8937e 100644 --- a/M2/Macaulay2/packages/EngineTests/LinearAlgebra.Test.Base.m2 +++ b/M2/Macaulay2/packages/EngineTests/LinearAlgebra.Test.Base.m2 @@ -349,17 +349,17 @@ testRank = (R) -> ( assert(rank M == j); )) -makeMatrixWithColumnRankProfile = (R, nrows, ncols, profile) -> ( - -- profile is a list of column indices (so each entry should be in range [0,ncols-1]) +makeMatrixWithColumnRankProfile = (R, nrows, ncols, prof) -> ( + -- prof is a list of column indices (so each entry should be in range [0,ncols-1]) A := first randomFullRank(R, nrows); B := mutableMatrix(R, nrows, ncols); -- now set B with random values - for p from 0 to #profile-1 do B_(p,profile#p) = 1_R; + for p from 0 to #prof-1 do B_(p,prof#p) = 1_R; rk := 1; - profile = append(profile, ncols); - for i from 1 to #profile - 1 do ( - -- set all entries in rows 0..rk in columns profile#(i-1) to profile#i-1 to random values - for r from 0 to rk-1 do for c from profile#(i-1)+1 to profile#i - 1 do B_(r,c) = random R; + prof = append(prof, ncols); + for i from 1 to #prof - 1 do ( + -- set all entries in rows 0..rk in columns prof#(i-1) to prof#i-1 to random values + for r from 0 to rk-1 do for c from prof#(i-1)+1 to prof#i - 1 do B_(r,c) = random R; rk = rk+1; ); A*B diff --git a/M2/Macaulay2/packages/Macaulay2Doc/debugging.m2 b/M2/Macaulay2/packages/Macaulay2Doc/debugging.m2 index 637f14b2936..896f20809cc 100644 --- a/M2/Macaulay2/packages/Macaulay2Doc/debugging.m2 +++ b/M2/Macaulay2/packages/Macaulay2Doc/debugging.m2 @@ -219,6 +219,7 @@ document { } +-* document { Key => {profile,(profile, Function),(profile, String, Function)}, Headline => "profile a function", @@ -243,7 +244,7 @@ document { TT "profileSummary", " -- a command that will display the data accumulated by running functions produced with ", TO "profile", "." } - +*- document { Key => uncurry, diff --git a/M2/Macaulay2/tests/normal/locate.m2 b/M2/Macaulay2/tests/normal/locate.m2 index 6f3c933e067..170bfa0d144 100644 --- a/M2/Macaulay2/tests/normal/locate.m2 +++ b/M2/Macaulay2/tests/normal/locate.m2 @@ -19,6 +19,22 @@ assert( (getcols locate c) === {4,23,4} ); assert( (getcols locate F) === {15,18,16} ); assert( (getcols locate C) === {15,18,16} ); f = (a,b) -> a +f = () -> A B C +assert( ((C = pseudocode functionBody f;)) === null ); +assert( (getcols locate f) === {4,15,4} ); +assert( (getcols locate C) === {10,15,11} ); +assert( (getcols locate C_0) === {10,11,10} ); +assert( (getcols locate C_1) === {12,15,13} ); +assert( (getcols locate C_1_0) === {12,13,12} ); +assert( (getcols locate C_1_1) === {14,15,14} ); +f = () -> (A B) C +assert( ((C = pseudocode functionBody f;)) === null ); +assert( (getcols locate f) === {4,17,4} ); +assert( (getcols locate C) === {10,17,15} ); +assert( (getcols locate C_0) === {11,14,12} ); +assert( (getcols locate C_1) === {16,17,16} ); +assert( (getcols locate C_0_0) === {11,12,11} ); +assert( (getcols locate C_0_1) === {13,14,13} ); end-- -* -- to update this file simply run these lines: @@ -58,4 +74,26 @@ getcols locate C f = (a,b) -> a -- FIXME: match(".*:.*:4:.*:.*: error: expected 2 arguments but got 1", last capture "f(1)") +-- Adjacent +-- 1 1 1 +-- 4 7 0 2 4 +f = () -> A B C +(C = pseudocode functionBody f;) +getcols locate f +getcols locate C +getcols locate C_0 +getcols locate C_1 +getcols locate C_1_0 +getcols locate C_1_1 +-- 1 1 1 +-- 4 7 0 3 5 +f = () -> (A B) C +(C = pseudocode functionBody f;) +getcols locate f +getcols locate C +getcols locate C_0 +getcols locate C_1 +getcols locate C_0_0 +getcols locate C_0_1 + -- TODO: add tests for other kinds of code as well diff --git a/M2/Macaulay2/tests/normal/names.m2 b/M2/Macaulay2/tests/normal/names.m2 index eef28dd065d..95dfba5c55e 100644 --- a/M2/Macaulay2/tests/normal/names.m2 +++ b/M2/Macaulay2/tests/normal/names.m2 @@ -905,7 +905,6 @@ assert( (toString printingTimeLimit) === "20" ) assert( (toString printingTrailLimit) === "5" ) assert( (toString processID) === "processID" ) assert( (toString product) === "product" ) -assert( (toString profile) === "profile" ) assert( (toString profileSummary) === "profileSummary" ) assert( (toString projectiveHilbertPolynomial) === "projectiveHilbertPolynomial" ) assert( (toString promote) === "promote" )