
/*
 *  TRACE.C
 *
 *	System tracing routines
 */

#include <stdio.h>	/* XXX use own I/O routines */
#include <sys/file.h>	/* XXX use own I/O routines */

#include "tmp/c.h"

RcsId("$Header: /private/postgres/src/utils/error/RCS/trace.c,v 1.15 1991/11/11 19:31:43 mer Exp $");

#include "tmp/simplelists.h"
#include "utils/module.h"
#include "utils/log.h"
#include "utils/mcxt.h"

#ifdef sprite
#include "sprite_file.h"
#include "sprite_file2.h"
#else
#include "storage/fd.h"
#endif /* sprite */

typedef struct {
    char *Child;
    char *Parent1;
    char *Parent2;
} TraceHier;

/*
 *  This array defines the parent heirarchy.  Individual modules can 
 *  declare local trace variables with 'parents' such that turning the
 *  trace on for the parent will turn the trace on for all its children.
 *
 *	CHILD	    PARENT#1	PARENT#2
 */

static TraceHier TraceTree[] = {
	"Root",	    NULL,	NULL,
	"Memory",   "Root",	NULL,
	"Parser",   "Root",	NULL,
	"Planner",  "Root",	NULL,
	"Tcop",	    "Root",	NULL,
	"Rules",    "Root",	NULL,
	"Storage",  "Root",	NULL,
	"Executor", "Root",	NULL
};



extern String getenv();	/* XXX style: use include */

static GlobalMemory TraceCxt;   /* used for memory allocation		*/
static int     TSDepth;		/* how deep are we?			*/
static SLList  TBList;		/* list of known module trace blocks	*/
static SLList  TSList;		/* subroutine stack trace		*/
static FileNumber    TraceFile;	/* current trace file (fdcache)		*/
static char    *DefaultTraceFile = "/dev/null";

extern char    **environ;

/*
 * TraceInitialize --
 */

void TraceInitialize ARGS((TraceBlock *));
void WriteSpaces ARGS((File, int, int));
char *TailName    ARGS((char *));
Pointer TraceAlloc ARGS((int));
void TraceFree ARGS((Pointer, int));

/*
 *  Resource control
 */

static int NumEnabled;	/* have we been initialized? .. count # of times */

void
EnableTrace(enable)
{
    static int fail;

    Assert(fail == 0);

    if (!enable && --NumEnabled) 
	return;
    if (enable && NumEnabled++)
	return;

    ++fail;
    if (enable) {
	EnableFDCache(true);
	EnableMemoryContext(true);

        SLNewList(&TBList, offsetof(TraceBlock,Node));
        SLNewList(&TSList, offsetof(TraceStack,Node));
	TraceCxt = CreateGlobalMemory("Trace");
	InitializeTheGlobalArray();
	if (getenv("TRACEFILE"))
	    DefaultTraceFile = getenv("TRACEFILE");
	TraceFile = 
	    FileNameOpenFile(DefaultTraceFile,O_WRONLY|O_APPEND|O_CREAT,0666);
	Assert(TraceFile >= 0);
    	{
	    register char *ptr;
	    register short i, j;
	    char tracename[128];

	    for (i = 0; ptr = environ[i]; ++i) {
		uint32 traceLevel;
	        if (strncmp(ptr, "TRACE_", 6) != 0)
		    continue;
		strcpy(tracename, ptr + 6);
		for (j = 0; tracename[j] && tracename[j] != '='; ++j);
		tracename[j] = 0;
		traceLevel = atoi(tracename + j + 1);
		if (traceLevel == 0)
		    traceLevel = 1;
		SetTraceLevel(tracename, traceLevel);
	    }
	}
    } else {
	{
	    register TraceBlock *tb;

	    while (tb = (TraceBlock *)SLRemHead(&TBList)) {
		tb->Flags = 0;
		tb->Level = 0;
	    }
	}
	DefaultTraceFile = "/dev/tty";
	GlobalMemoryDestroy(TraceCxt);
	EnableFDCache(false);
	EnableMemoryContext(false);
    }
    --fail;
}

_TraceMsg(tb, level, nargs, arg0)
TraceBlock *tb;
int level, nargs;
Pointer arg0;
{
    Assert(NumEnabled);
    if ((tb->Flags & TBF_INITIALIZED) == 0)
	TraceInitialize(tb);
    if (level <= tb->Level) {
        Pointer *args = &arg0;
	char buf[1024];
	int len;

	sprintf(buf, args[0], args[1], args[2], args[3], args[4], args[5]);
	len = strlen(buf);
	Assert(len < sizeof(buf));
	buf[len++] = '\n';
	switch(tb->Stream) {
	case DefaultStream:
	case LogStream:
	case InstallationStream:
	case DBStream:
	case ConsoleStream:
	case FrontendStream:
	case PostmasterStream:
	case AuxStream:
	    FileWrite(TraceFile, buf, len);
	    break;
	default:
	    elog(WARN, "Unknown trace stream %s %d", tb->Name, tb->Stream);
	    break;
	}
    } 
}

/*
 *   If not already initialized, initialize a trace block.
 */

void
TraceInitialize(tb)
TraceBlock *tb;
{
    if (tb->Flags & TBF_INITIALIZED)
	return;

    Assert(NumEnabled);

    /*
     *  Remove header and tailer spaces from Parent1 and Parent2 that might
     *  have been stuck in by cpp.
     */

    RemoveHeadTailSpaces(&tb->Parent1);
    RemoveHeadTailSpaces(&tb->Parent2);

    /*
     *  Initial trace level determined by max of any parent's trace level.
     */

    {
	uint32 l1 = TraceLevelByName(tb->Parent1);
	uint32 l2 = TraceLevelByName(tb->Parent2);

	tb->Level = Max(l1,l2);
    }

    /*
     *  Trace level overriden by specific level already set for this name
     *  (level for all matched names are the same)
     */

    {
	register TraceBlock *t, *tnext;
	for (t = (TraceBlock *)SLGetHead(&TBList); t; t = tnext) {
	    tnext = (TraceBlock *)SLGetSucc(&t->Node);
	    if (strcmp(tb->Name, t->Name) == 0) {
		tb->Level = t->Level;
		break;
	    }
	}
    }

    /*
     *  Add trace block to TB list.
     */

    SLNewNode(&tb->Node);
    SLAddTail(&TBList, &tb->Node);
    tb->Flags |= TBF_INITIALIZED;
}

RemoveHeadTailSpaces(str)
char **str;
{
    register char *ptr = *str;

    if (ptr) {
	while (*ptr == ' ' || *ptr == '\t')
	    ++ptr;
	*str = ptr;
	while (*ptr && *ptr != ' ' && *ptr != '\t')
	    ++ptr;
	*ptr = 0;
    }
}

InitializeTheGlobalArray()
{
    register short i;

    for (i = 0; i < lengthof(TraceTree); ++i) {
	register TraceBlock *tb = (TraceBlock *)TraceAlloc(sizeof(TraceBlock));
	bzero(tb, sizeof(TraceBlock));
	tb->Name    = TraceTree[i].Child;
	tb->Parent1 = TraceTree[i].Parent1;
	tb->Parent2 = TraceTree[i].Parent2;
	tb->Flags = TBF_INITIALIZED;
	SLNewNode(&tb->Node);
	SLAddTail(&TBList, &tb->Node);
    }
}

/*
 *   TraceLevelByName()
 *
 *   Return the trace level associated with the given trace variable
 *   name.  (0 if not found)
 */

static
int
TraceLevelByName(name)
char *name;
{
    register TraceBlock *tb;

    Assert(NumEnabled);
    if (name == NULL)
	return(0);
    for (tb = (TraceBlock *)SLGetHead(&TBList); tb;
	 tb = (TraceBlock *)SLGetSucc(&tb->Node))
    {
	if (strcmp(tb->Name, name) == 0)
	    return(tb->Level);
    }
    return(0);
}

/*
 *	SetChildrenOf()
 *
 *	Subroutine used by SetTraceLevel() to search out the children of
 *	a name and modify their level recursively.
 */

static void
SetChildrenOf(Name, level)
char *Name;
{
    register TraceBlock *tb;

    Assert(NumEnabled);

    for (tb = (TraceBlock *)SLGetHead(&TBList); tb;
        tb = (TraceBlock *)SLGetSucc(&tb->Node))
    {
	if (tb->Parent1 && strcmp(tb->Parent1, Name) == 0) {
	    if (tb->Flags & TBF_FAULT)	/* circular tree! */
		Assert(0);
	    tb->Level = level;
	    tb->Flags |= TBF_FAULT;
	    SetChildrenOf(tb->Name, level);
	    tb->Flags &= ~TBF_FAULT;
	}
	if (tb->Parent2 && strcmp(tb->Parent2, Name) == 0) {
	    if (tb->Flags & TBF_FAULT)  /* circular tree! */
		Assert(0);
	    tb->Level = level;
	    tb->Flags |= TBF_FAULT;
	    SetChildrenOf(tb->Name, level);
	    tb->Flags &= ~TBF_FAULT;
	}
    }
}

/*
 *	SetTraceLevel()
 *
 *	Modify the current trace level for the given trace variable.  0 turns
 *	off the trace.  The level for all children is recursively set to
 *	this value too.
 *
 *	Note that more than one traceblock with the same name may exist.
 */

void
SetTraceLevel(traceName, level)
char *traceName;
int level;
{
    short found = 0;


    Assert(NumEnabled);

    /*
     *  Step (1), search list and update any matches.
     */

    {
	register TraceBlock *tb;

	for (tb = (TraceBlock *)SLGetHead(&TBList); tb;
	     tb = (TraceBlock *)SLGetSucc(&tb->Node))
	{
	    if (strcmp(traceName, tb->Name) == 0) {
		tb->Level = level;
		found = 1;
	    }
	}
    }

    /*
     *  Step (2), update children of this trace name, if any (recur)
     */

    SetChildrenOf(traceName, level);

    /*
     *  Step (3), if name was not in block list, add to block list.
     */

    if (found == 0) {
	register TraceBlock *tb = (TraceBlock *)TraceAlloc(sizeof(*tb));
	char *ptr = TraceAlloc(strlen(traceName)+1);

	SLNewNode(&tb->Node);
	strcpy(ptr, traceName);
	tb->Name = ptr;
	tb->Flags = TBF_INITIALIZED;
	tb->Level = level;
	SLAddHead(&TBList, &tb->Node);
    }
}

/*
 *  Change the TraceFile messages are sent to.  On failure, the old trace
 *  file continues to be used.
 */

bool
TraceFileName(fileName)
char *fileName;
{
    FileNumber newfile;
    char buf[256];

    newfile = FileNameOpenFile(fileName,O_WRONLY|O_APPEND|O_CREAT,0666);
    if (newfile < 0) {
	sprintf(buf, "Unable to open new trace file %s\n", fileName);
	FileWrite(TraceFile, buf, strlen(buf));
	return(false);
    }
    FileClose(TraceFile);
    TraceFile = newfile;
    return(true);
}

/*
 *  Subroutine Call/Return tracing
 */

void
_TracePush(tb, lineno, str)
TraceBlock *tb;
int lineno;
char *str;
{
    register TraceStack *ts;

    Assert(NumEnabled);
    if ((tb->Flags & TBF_INITIALIZED) == 0)
	TraceInitialize(tb);

    ts = (TraceStack *)TraceAlloc(sizeof(*ts));
    ts->Tb = tb;
    ts->LineNo = lineno;
    ts->Text   = str;
    SLNewNode(&ts->Node);
    SLAddTail(&TSList, &ts->Node);
    ++TSDepth;

    if (tb->Level) {
	char buf[256];
	WriteSpaces(TraceFile, TSDepth, '#');
	sprintf(buf, "BEGIN %s.%d %s\n", TailName(tb->Module), lineno, str);
	FileWrite(TraceFile, buf, strlen(buf));
    }
}

void
_TracePop(tb, lineno, str)
TraceBlock *tb;
int lineno;
char *str;
{
    register TraceStack *ts = (TraceStack *)SLGetTail(&TSList);

    Assert(PointerIsValid(ts));
    Assert(ts->Tb == tb);
    SLRemove(&ts->Node);
    TraceFree((Pointer)ts, sizeof(*ts));

    if (str[0] == '\0')
	str = ts->Text;

    if (tb->Level) {
	char buf[256];
	WriteSpaces(TraceFile, TSDepth, '#');
	sprintf(buf, "END   %s.%d %s\n", TailName(tb->Module), lineno, str);
	FileWrite(TraceFile, buf, strlen(buf));
    }
    --TSDepth;
}

void
_TraceReset(tb, lineno, str)
TraceBlock *tb;
int lineno;
char *str;
{

}

/*
 *  Called by exception handler
 */

void
_TraceDump()
{
    TraceStack *ts;
    char buf[256];
    static int ReEntered = 0;

    if (NumEnabled <= 0)
	return;
    if (ReEntered)
	exitpg(1234); 
    ++ReEntered;

    sprintf(buf, "\nTRACE STACK DUMP\n");
    FileWrite(TraceFile, buf, strlen(buf));

    for (ts = (TraceStack *)SLGetHead(&TSList);
	 PointerIsValid(ts);
	 ts = (TraceStack *)SLGetSucc(&ts->Node))
    {
	sprintf(buf, "%15s.%-4d\t%s\n", ts->Tb->Module, ts->LineNo, ts->Text);
	FileWrite(TraceFile, buf, strlen(buf));
    }
    sprintf(buf, "\nTRACE STACK END\n");
    FileWrite(TraceFile, buf, strlen(buf));
    --ReEntered;
}

static void
WriteSpaces(file, n, firstchar)
FileNumber file;
int n;
int firstchar;
{
    char buf[256];
    int i;

    ++n;
    for (i = 0; i < n && i < sizeof(buf); ++i)
	buf[i] = ' ';
    buf[0] = '#';
    while (n > sizeof(buf)) {
	FileWrite(file, buf, n);
	buf[0] = ' ';
	n -= sizeof(buf);
    }
    FileWrite(file, buf, n);
}

char *
TailName(str)
char *str;
{
    char *ptr = str + strlen(str);
    while (ptr >= str && *ptr != '/')
	--ptr;
    return(ptr + 1);
}

/*
 *  ALLOCATION AND FREEING OF MEMORY
 *
 *  Allocations smaller than 32 longwords are tabled for fast access.
 *  (that is, TraceFree() calls table the pointer instead of freeing it)
 */

static char **AllocArray[32];

Pointer
TraceAlloc(bytes)
{
    int longwords;
    char **res;

    if (bytes < sizeof(char *))
	bytes = sizeof(char *);
    longwords = (bytes + 3) >> 2;	/* # of longwords to allocate */
    if (longwords >= lengthof(AllocArray) || AllocArray[longwords] == NULL) {
	return(MemoryContextAlloc((MemoryContext)TraceCxt, longwords << 2));
    }
    res = AllocArray[longwords];
    AllocArray[longwords] = (char **)res[0];
    return((Pointer)res);
}

void
TraceFree(ptr, bytes)
Pointer ptr;
int bytes;
{
    int longwords;
    if (bytes < sizeof(char *))
	bytes = sizeof(char *);
    longwords = (bytes + 3) >> 2;	/* # of longwords to free */

    if (longwords >= lengthof(AllocArray)) {
	MemoryContextFree((MemoryContext)TraceCxt, ptr);
	return;
    }
    *(char **)ptr = (char *)AllocArray[longwords];
    AllocArray[longwords] = (char **)ptr;
}


