/**************************************************************************** ** ** This file is part of GAP, a system for computational discrete algebra. ** ** Copyright of GAP belongs to its developers, whose names are too numerous ** to list here. Please refer to the COPYRIGHT file for details. ** ** SPDX-License-Identifier: GPL-2.0-or-later ** ** This file contains profile related functionality. **
*/
#include <stdio.h> // for fprintf, fflush, popen, pclose, fopen, fclose #include <sys/time.h> // for gettimeofday #include <sys/types.h> // for pid_t #include <unistd.h> // for getpid #ifdef HAVE_SYS_RESOURCE_H #include <sys/resource.h> // definition of 'struct rusage' #endif
/**************************************************************************** ** ** Overview of GAP profiling ** ** The basic idea behind profiling comes in two parts: ** ** 1) In each stat and expr, we store at creation time a filenameid and a ** line number. This is always done (as it isn't very expensive) ** ** 2) When we want to profile, we wrap each of ** - ExecStatFuncs ** - ExecExprFuncs ** - EvalBoolFuncs ** and output the line number of filename we stored in the stat or expr. ** We also use 1 bit to mark that we have executed this statement, so ** we can (if we want to) only output each executed statement once. ** ** We use this information to output a straight list of every executed statement. ** ** There are not that many tricky cases here. We have to be careful that ** some Expr are integers or local variables, and not touch those. ** ** Limitations (and how we overcome them): ** ** There are three main limitations to this approach (2 of which we handle) ** ** 1) From our initial trace, we don't know if a line has a statement on ** it to be executed! Therefore we also provide the ability to store the ** line of each created statement, so we can check which lines have code ** on them. ** ** 2) If we wait until the user can run HookedLine, they have missed ** the reading and execution of lots of the standard library. Therefore ** we provide -P (profiling) and -c (code coverage) options to the GAP ** executable so the user can start before code loading starts ** ** 3) Operating at just a line basis can sometimes be too coarse. However, ** without some serious additional overheads, I can't see how to provide this ** functionality in output (basically we would have to store ** line and character positions for the start and end of every expression). ** ** ** Achieving 100% code coverage is a little tricky. Here is a list of ** the special cases which had to be considered (so far) ** ** GAP special cases for-loops of the form 'for i in [a..b]', by digging ** into the range to extract 'a' and 'b'. Therefore nothing on the line is ** ever evaluated and it appears to never be executed. We special case this ** if ForRange, by marking the range as evaluated. ** ** We purposefully ignore EXPR_TRUE and EXPR_FALSE, which represent the ** constants 'true' and 'false', as they are often read but not 'executed'. ** We already ignored all integer and float constants anyway. ** However, the main reason this was added is that GAP represents 'else' ** as 'elif true then', and this was leading to 'else' statements being ** never marked as executed.
*/
/**************************************************************************** ** ** Store the current state of the profiler
*/
static Obj OutputtedFilenameList;
struct StatementLocation
{ int fileid; int line;
};
staticstruct ProfileState
{ // Is profiling currently active
ProfileActiveEnum status; // C steam we are writing to
FILE* Stream; // Filename we are writing to char filename[GAP_PATH_MAX]; // Did we use 'popen' to open the stream (matters when closing) int StreamWasPopened; // Are we currently outputting repeats (false=code coverage) int OutputRepeats;
// Used to generate 'X' statements, to make sure we correctly // attach each function call to the line it was executed on struct StatementLocation lastNotOutputted;
// Record last executed statement, to avoid repeats struct StatementLocation lastOutputted; int lastOutputtedExec;
Int8 lastOutputtedTime;
TickMethod tickMethod;
int minimumProfileTick; #ifdef HPCGAP int profiledThread; #endif
// Have we previously profiled this execution of GAP? We need this because // code coverage doesn't work more than once, as we use a bit in each Stat // to mark if we previously executed this statement, which we can't // clear
UInt profiledPreviously;
Int LongJmpOccurred;
// We store the value of RecursionDepth each time we enter a function. // This is the only way to detect if GAP has left a function by performing // a longjmp. // We need to store the actual values, as RecursionDepth can increase // by more than one when a GAP function is called
Obj visitedDepths;
} profileState;
// Some GAP functionality (such as syntaxtree) evaluates expressions, which makes // them appear executed in profiles. The functions pauseProfiling and unpauseProfiling // temporarily enable and disable profiling to avoid this problem. void pauseProfiling(void)
{ if (profileState.status == Profile_Active) {
profileState.status = Profile_Paused;
}
}
// Output information about how this profile was configured staticvoid outputVersionInfo(void)
{ constchar timeTypeNames[3][10] = { "WallTime", "CPUTime", "Memory" };
fprintf(profileState.Stream, "{ \"Type\": \"_\", \"Version\":1, \"IsCover\": %s, " " \"TimeType\": \"%s\"}\n",
profileState.OutputRepeats ? "false" : "true",
timeTypeNames[profileState.tickMethod]); // Explicitly flush, so this information is in the file // even if GAP crashes
fflush(profileState.Stream);
}
// This function is called when we detect a longjmp occurred, and // outputs a 'return' into the profile for any function which was // jumped over. // It is fine for this function to be called when a longjmp has not // occurred, or when no function was longjmped over. staticvoid CheckLeaveFunctionsAfterLongjmp(void)
{ if (!profileState.LongJmpOccurred) return;
#ifdef HPCGAP if (profileState.profiledThread != TLS(threadID)) return; #endif
profileState.LongJmpOccurred = 0;
Int pos = LEN_PLIST(profileState.visitedDepths); Int depth = GetRecursionDepth();
while (pos > 0 && INT_INTOBJ(ELM_PLIST(profileState.visitedDepths, pos)) > depth) { // Give dummy values if we do not know
fprintf(profileState.Stream, "{\"Type\":\"O\",\"Fun\":\"nameless\",\"Line\":-1," "\"EndLine\":-1,\"File\":\"<missing filename>\"," "\"FileId\":-1}\n");
PopPlist(profileState.visitedDepths);
pos--;
}
}
// Escape a string for serialization in a JSON file, following the // escaping rules laid out at json.org. static Obj JsonEscapeString(Obj param)
{ Int lenString = LEN_LIST(param);
// Allocate an output string with twice the size of the input // string, as in the worst case every single has to be escaped.
Obj copy = NEW_STRING(lenString * 2);
UChar * in = CHARS_STRING(param);
UChar * base = CHARS_STRING(copy);
UChar * out = base;
for (Int i = 0; i < lenString; ++i) {
UChar u = in[i]; switch (u) { case'\\': case'"': case'/':
out[0] = '\\';
out[1] = u;
out += 2; break; #define ESCAPE_CASE(x, y) \ case x: \
out[0] = '\\'; \
out[1] = y; \
out += 2; \ break;
ESCAPE_CASE('\b', 'b');
ESCAPE_CASE('\t', 't');
ESCAPE_CASE('\n', 'n');
ESCAPE_CASE('\f', 'f');
ESCAPE_CASE('\r', 'r'); #undef ESCAPE_CASE default:
*(out++) = u;
}
}
staticinlinevoid outputFilenameIdIfRequired(UInt id)
{ if (id == 0) { return;
} if (LEN_PLIST(OutputtedFilenameList) < id ||
ELM_PLIST(OutputtedFilenameList, id) != True) {
AssPlist(OutputtedFilenameList, id, True);
fprintf(profileState.Stream, "{\"Type\":\"S\",\"File\":\"%s\",\"FileId\":%d}\n",
CONST_CSTR_STRING(JsonEscapeString(GetCachedFilename(id))),
(int)id);
}
}
// This function checks gets the filenameId of the current function. staticinline UInt getFilenameIdOfCurrentFunction(void)
{
Obj func = CURR_FUNC();
Obj body = BODY_FUNC(func); return GET_GAPNAMEID_BODY(body);
}
// We output 'File' here for compatibility with // profiling v1.3.0 and earlier, FileId provides the same information // in a more useful and compact form.
fprintf(profileState.Stream, "{\"Type\":\"%c\",\"Fun\":\"%s\",\"Line\":%" "d,\"EndLine\":%d,\"File\":\"%s\"," "\"FileId\":%d}\n",
type, name_c, (int)startline, (int)endline, filename_c,
(int)fileid);
}
HashUnlock(&profileState);
}
staticvoid leaveFunction(Obj func)
{ #ifdef HPCGAP if (profileState.profiledThread != TLS(threadID)) return; #endif // Do not crash if we exit the function in which // Profile was originally called. The profiling // package can handle such profiles. if (LEN_PLIST(profileState.visitedDepths) > 0) {
PopPlist(profileState.visitedDepths);
}
CheckLeaveFunctionsAfterLongjmp();
HookedLineOutput(func, 'O');
}
/**************************************************************************** ** ** Functionality to store streams compressed. ** If we could rely on the existence of the IO package, we would use that here. ** however, we want to be able to start compressing files right at the start ** of GAP's execution, before anything else is done.
*/
staticBOOL endsWithgz(constchar * s)
{
s = strrchr(s, '.'); return s && streq(s, ".gz");
}
// When a child is forked off, we force profile information to be stored // in a new file for the child, to avoid corruption void InformProfilingThatThisIsAForkedGAP(void)
{
HashLock(&profileState); if (profileState.status == Profile_Active) { char filenamecpy[GAP_PATH_MAX]; // Allow 20 characters to allow space for .%d.gz constint SUPPORTED_PATH_LEN = GAP_PATH_MAX - 20; if(strlen(profileState.filename) > SUPPORTED_PATH_LEN) {
Panic("Filename can be at most %d character when forking", SUPPORTED_PATH_LEN);
} if (endsWithgz(profileState.filename)) {
snprintf(filenamecpy, sizeof(filenamecpy), "%.*s.%d.gz",
SUPPORTED_PATH_LEN, profileState.filename, getpid());
} else {
snprintf(filenamecpy, sizeof(filenamecpy), "%.*s.%d",
SUPPORTED_PATH_LEN, (char*)profileState.filename, getpid());
}
fcloseMaybeCompressed(&profileState);
fopenMaybeCompressed(filenamecpy, &profileState);
outputVersionInfo(); // Need to flush list of outputted files, as we will start a fresh file
OutputtedFilenameList = NEW_PLIST(T_PLIST, 0);
}
HashUnlock(&profileState);
}
// type : the type of the statement // exec : are we executing this statement // visit: Was this statement previously visited (that is, executed) staticinlinevoid
outputStat(Int fileid, int line, int type, BOOL exec, BOOL visited)
{ // Explicitly skip these two cases, as they are often specially handled // and also aren't really interesting statements (something else will // be executed whenever they are). if (type == EXPR_TRUE || type == EXPR_FALSE) { return;
}
CheckLeaveFunctionsAfterLongjmp();
// Catch the case we arrive here and profiling is already disabled if (profileState.status != Profile_Active) { return;
}
outputFilenameIdIfRequired(fileid);
// Statement not attached to a file if (fileid == 0) { return;
}
printOutput(fileid, line, exec, visited);
}
staticinlinevoid outputInterpretedStat(int fileid, int line, BOOL exec)
{
CheckLeaveFunctionsAfterLongjmp();
// Catch the case we arrive here and profiling is already disabled if (profileState.status != Profile_Active) { return;
}
outputFilenameIdIfRequired(fileid);
// Statement not attached to a file if (fileid == 0) { return;
}
/**************************************************************************** ** ** This function exists to help with code coverage -- this outputs which ** lines have statements on expressions on them, so later we can ** check we executed something on those lines!
**/
staticvoid registerStat(int fileid, int line, int type)
{
HashLock(&profileState); if (profileState.status == Profile_Active) {
outputStat(fileid, line, type, FALSE, FALSE);
}
HashUnlock(&profileState);
}
staticvoid registerInterpretedStat(int fileid, int line)
{
HashLock(&profileState); if (profileState.status == Profile_Active) {
outputInterpretedStat(fileid, line, FALSE);
}
HashUnlock(&profileState);
}
staticvoid
enableAtStartup(constchar * filename, Int repeats, TickMethod tickMethod)
{ if (profileState.status == Profile_Active) {
Panic("-P or -C can only be passed once\n");
}
profileState.OutputRepeats = repeats;
fopenMaybeCompressed(filename, &profileState); if(!profileState.Stream) {
Panic("Failed to open '%s' for profiling output.\n", filename);
}
// This function is for when GAP is started with -c, and // enables profiling at startup. If anything goes wrong, // we quit straight away. int enableCodeCoverageAtStartup(constchar * argv[], void * dummy)
{
enableAtStartup(argv[0], 0, Tick_Mem); return 1;
}
// This function is for when GAP is started with -P, and // enables profiling at startup. If anything goes wrong, // we quit straight away. int enableProfilingAtStartup(constchar * argv[], void * dummy)
{
TickMethod tickMethod = Tick_WallTime; #ifdef HAVE_GETTIMEOFDAY
tickMethod = Tick_WallTime; #else #ifdef HAVE_GETRUSAGE
tickMethod = Tick_CPUTime; #endif #endif
enableAtStartup(argv[0], 1, tickMethod); return 1;
}
static Obj FuncACTIVATE_PROFILING(Obj self,
Obj filename, // filename to write to
Obj coverage,
Obj wallTime,
Obj recordMem,
Obj resolution)
{ if (profileState.status != Profile_Disabled) { return Fail;
}
if(profileState.profiledPreviously &&
coverage == True) {
ErrorMayQuit("Code coverage can only be started once per" " GAP session. Please exit GAP and restart. Sorry.",0,0);
}
if(coverage != True && coverage != False) {
ErrorMayQuit(" must be a boolean",0,0);
}
if(wallTime != True && wallTime != False) {
ErrorMayQuit(" must be a boolean",0,0);
}
#ifndef HAVE_GETTIMEOFDAY if(wallTime == True) {
ErrorMayQuit("This OS does not support wall-clock based timing",0,0);
} #endif #ifndef HAVE_GETRUSAGE if(wallTime == False) {
ErrorMayQuit("This OS does not support CPU based timing",0,0);
} #endif
staticInt PostRestore ( StructInitInfo * module )
{ /* When we restore a workspace, we start a new profile. * 'OutputtedFilenameList' is the only part of the profile which is * stored in the GAP memory space, so we need to clear it in case * it still has a value from a previous profile.
*/
OutputtedFilenameList = NEW_PLIST(T_PLIST, 0); return 0;
}
/**************************************************************************** ** *F InitInfoStats() . . . . . . . . . . . . . . . . . table of init functions
*/ static StructInitInfo module = { // init struct using C99 designated initializers; for a full list of // fields, please refer to the definition of StructInitInfo
.type = MODULE_BUILTIN,
.name = "profile",
.initKernel = InitKernel,
.initLibrary = InitLibrary,
.postRestore = PostRestore
};
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.