/*
 * $RCSfile: datatrace.c,v $
 *
 * x-kernel v3.3
 *
 * Copyright (c) 1993,1991,1990,1996  Arizona Board of Regents
 *
 * $Log: datatrace.c,v $
 * Revision 1.4  1997/06/26 07:09:47  venkat
 * fixed problem with multiple log buffers in file. venkat
 *
 * Revision 1.3  1996/06/14  21:37:52  slm
 * Merged in brakmo's changes.
 *
 * Revision 1.2  1996/01/29  20:00:56  slm
 * Updated copyright and version.
 *
 * Revision 1.1  1995/07/28  21:45:22  slm
 * Initial revision
 *
 * Revision 1.1  1994/10/27  20:57:08  hkaram
 * Initial revision
 *
 * Revision 1.1  1994/07/22  21:08:40  hkaram
 * Initial revision
 *
 */

/*
 * The Data Tracing tool provides an API for accumulating trace data and
 * subsequently dumping it to file.  It provides the macros/functions to create
 * trace objects, copy tracing information to allocated buffers, write the data
 * to file etc.
 */

#include <stdio.h>
#include "xkernel.h"

#include "datatrace.h"
#include "datatrace_i.h"

#define MAX_FILENAME 256

static dtState dts = { NULL };

int tracedatatrace;

#ifdef __STDC__
static XkReturn dtLoadTraceEntry(Protl, char **, int, int, void *);
#else
static XkReturn dtLoadTraceEntry();
#endif

char *dtTraceDir = NULL;

/* dtCreateTraceObj
 *
 * Creates a trace object and fills in the parameters.  The trace name of the
 * object name is just traceName or "<traceName>_<instName>".  If instName is
 * non-NULL.  The trace name is also used as the file name with a .dt extension.
 */
dt *
dtCreateTraceObj(traceName, instName, logsize, fileSize)
char         *traceName;
char         *instName;
unsigned int logsize;
unsigned int fileSize;
{
    dt  *dtobj, *nextobj;
    int len;

    xAssert(traceName);

    xTrace0(datatrace, TR_FULL_TRACE, "In dtCreateTraceObj");

    dtobj = (dt *)xMalloc(sizeof(dt));
    if (! dtobj) return NULL;

    if (logsize) {
        dtobj->buffer = (char *)xMalloc(logsize);
        if (! dtobj->buffer) {
            xFree((char *)dtobj);
            return NULL;
        }
        bzero(dtobj->buffer, logsize);
    }

    len = strlen(traceName) + 1;
    if (instName) len += strlen(instName) + 1;
    dtobj->traceName = (char *)xMalloc(len);
    if (! dtobj->traceName) {
        xFree((char *)dtobj->buffer);
        xFree((char *)dtobj);
        return NULL;
    }
    strcpy(dtobj->traceName, traceName);
    if (instName) {
        strcat(dtobj->traceName, "_");
        strcat(dtobj->traceName, instName);
    }

    dtobj->current = dtobj->buffer;
    if (logsize) dtobj->last = dtobj->buffer + logsize - 1;
    dtobj->fileSize = fileSize;
    dtobj->post = NULL;
    dtobj->numPost = 0;

    dtobj->fileHdr.version = DT_VERSION;
    dtobj->fileHdr.numberBuffers = 0;
    dtobj->fileHdr.bufferSize = logsize;
    dtobj->fileHdr.lastBufferIdx = 0;
    dtobj->fileHdr.lastBufferSize = 0;

    /* add the trace object to a list of all trace objects */
    dtobj->next = NULL;
    if (dts.dtlist) {
        for (nextobj = dts.dtlist; nextobj->next; nextobj = nextobj->next)
            ;
        nextobj->next = dtobj;
    }
    else
        dts.dtlist = dtobj;

    dtobj->closeFunc = (dtCloseFunc)NULL;
    dtobj->closeArg = NULL;

    xTrace2(datatrace, TR_EVENTS, "dtCreateTraceObj - Created dt %s of size %d",
	    dtobj->traceName, dtobj->fileHdr.bufferSize);

    return dtobj;
}

/* dtFlushTraceObj
 *
 * Flushes the data in the buffers to the data file.  Also flushes the
 * postamble data on request.
 */
void
dtFlushTraceObj(dtobj, flush_post)
dt       *dtobj;
unsigned flush_post;
{
    FILE          *fop;
    unsigned long seek;
    char          filename[MAX_FILENAME];

    if (! dtobj || ! dtobj->fileSize) return;
    if (dtTraceDir != NULL)
        sprintf(filename, "%s/%s.dt", dtTraceDir, dtobj->traceName);
    else
        sprintf(filename, "%s.dt", dtobj->traceName);
    fop = fopen(filename, (dtobj->fileHdr.lastBufferIdx ? "r+" : "w"));
    if (! fop) return;

    if (dtobj->fileHdr.lastBufferIdx)
        seek = (dtobj->fileHdr.lastBufferIdx % dtobj->fileSize)
               * dtobj->fileHdr.lastBufferSize;
    else
        seek = 0;

    if (dtobj->fileHdr.numberBuffers < dtobj->fileSize)
        dtobj->fileHdr.numberBuffers++;
    dtobj->fileHdr.lastBufferIdx++;
    dtobj->fileHdr.lastBufferSize = dtobj->current - dtobj->buffer;

    xTrace2(datatrace, TR_EVENTS, "dtFlushTraceObj - flushing obj %s size %d",
	    dtobj->traceName, dtobj->fileHdr.lastBufferSize);

    /* write out the file hdr */
    fwrite((void *)&dtobj->fileHdr, sizeof(dthdr), 1, fop);

    /* write out the entire buffer */
    if (seek) fseek(fop, seek, 1);
    if (dtobj->fileHdr.lastBufferSize)
        fwrite((void *)dtobj->buffer, dtobj->fileHdr.lastBufferSize, 1, fop);

    /* flush postamble to end of file */
    if (flush_post) {
        dtpost *post;
        int sentinel = -1;

        xTrace1(datatrace, TR_EVENTS,
	"dtFlushTraceObj - Flushing obj %s's postamble data", dtobj->traceName);

        /*
         * if the trace buffer written out was the last one
         * i.e. at the end of the file and not in the middle
         * then flush the postamble data immediately after
         * the trace buffer, otherwise flush it at file end.
         */
        if (dtobj->fileHdr.lastBufferIdx % dtobj->fileHdr.numberBuffers != 0)
            fseek(fop, 0, 2);

        for (post = dtobj->post; post; post = post->next) {
	    xTrace2(datatrace, TR_EVENTS,
		    "dtFlushTraceObj - obj %s's postamble: %-10u",
		    dtobj->traceName, post->size );
            /*
            fprintf(fop, "P=%-10u", post->size);
            */
            fwrite((void *)&post->size, sizeof(int), 1, fop);
            fwrite((void *)post->buffer, post->size, 1, fop);
        }
        fwrite((void *)&sentinel, sizeof(int), 1, fop);
    }

    fclose(fop);
}

void
dtRegisterCloseFunc(dtobj, closefunc, closearg)
dt          *dtobj;
dtCloseFunc closefunc;
void        *closearg;
{
    if (! dtobj) return;

    dtobj->closeFunc = closefunc;
    dtobj->closeArg = closearg;
}

void
dtClose(dtobj)
dt *dtobj;
{
    dtpost *post, *freepost;

    if (! dtobj) return;

    if (dtobj->closeFunc)
        (dtobj->closeFunc)(dtobj, dtobj->closeArg);

    /* remove from list */
    if (dtobj == dts.dtlist)
        dts.dtlist = dts.dtlist->next;
    else {
        dt *elm;

        for (elm = dts.dtlist; elm && dtobj != elm->next; elm = elm->next)
            ;
        xAssert(elm && elm->next == dtobj);
        elm->next = dtobj->next;
    }
    dtFlushTraceObj(dtobj, 1);

    /* free dt obj pointers */
    xFree(dtobj->buffer);
    xFree(dtobj->traceName);
    for (post = dtobj->post; post; ) {
        freepost = post;
        post = post->next;
        xFree((char *)freepost);
    }
    xFree((char *)dtobj);
}

/*
 * dtCloseAll
 *
 * Closes all the trace objects and flushes them all to file.
 * Should be called at end of the trace.
 */
void
dtCloseAll()
{
    dt *dtobj, *nextobj;

    xTrace0(datatrace, TR_EVENTS, "dtCloseAll - closing all dt objects");

    for (dtobj = dts.dtlist; dtobj; ) {
        nextobj = dtobj->next;
        dtClose(dtobj);
        dtobj = nextobj;
    }
}

static XkReturn
dtLoadTraceEntry(prot, str, nFields, line, arg)
Protl prot;
char  **str;
int   nFields;
int   line;
void  *arg;
{
    int  logsize, filesize, nextField;
    char instance_name[MAX_FILENAME], *ext_name = NULL;
    dt   *dtobj;

    /* check if this is a trace statement */
    if (strcmp(str[1], "trace"))
        return XK_SUCCESS;

    if (nFields > 3) {
        if (sscanf(str[2], "name=%s", instance_name) < 1)
            return XK_FAILURE;
        nextField = 3;
        ext_name = instance_name;
    }
    else
        nextField = 2;

    if (sscanf(str[nextField], "logsize=%d", &logsize) < 1)
        return XK_FAILURE;
    if (sscanf(str[nextField+1], "filesize=%d", &filesize) < 1)
        return XK_FAILURE;

    dtobj = dtCreateTraceObj(prot->fullName, ext_name, logsize, filesize);
    return dtobj ? XK_SUCCESS : XK_FAILURE;
}

static ProtlRomOpt traceOpts[] = {
    { "", 3, dtLoadTraceEntry }
};

void
dtLoadProtlRomOpts(prot)
Protl prot;
{
    findProtlRomOpts(prot, traceOpts, sizeof(traceOpts)/sizeof(ProtlRomOpt), 0);
}

/* dtAppendPostAmble
 *
 * Adds a buffer to the trace object.  This is flushed to the end of the file
 * at the end of the program when all the files are closed.  Assumes that the
 * buffer has been preallocated. The buffer has been placed at the end of the
 * postamble list.
 */
XkReturn
dtAppendPostAmble(dtobj, buffer, size)
dt       *dtobj;
char     *buffer;
unsigned size;
{
    dtpost *post, *nextpost;

    if (! dtobj) return XK_SUCCESS;

    post = (dtpost *)xMalloc(sizeof(dtpost));
    if (! post) return XK_FAILURE;

    post->buffer = buffer;
    post->size = size;
    post->next = NULL;

    /* insert at end of list */
    if (nextpost = dtobj->post) {
        while (nextpost->next) nextpost = nextpost->next;
        nextpost->next = post;
    }
    else
        dtobj->post = post;

    dtobj->numPost++;

    xTrace3(datatrace, TR_EVENTS,
            "dtAppendPostAmble - postamble %d appended (%10u bytes) to obj %s",
	    dtobj->numPost, size, dtobj->traceName);

    return XK_SUCCESS;
}

/* dtInsertPostAmble
 *
 * Adds a buffer to the trace object.  This is flushed to the end of the file
 * at the end of the program when all the files are closed.  Assumes that the
 * buffer has been preallocated.  The buffer is placed at the beginning of
 * the postamble list.
 */
XkReturn
dtInsertPostAmble(dtobj, buffer, size)
dt       *dtobj;
char     *buffer;
unsigned size;
{
    dtpost *post;

    if (! dtobj) return XK_SUCCESS;

    post = (dtpost *)xMalloc(sizeof(dtpost));
    if (! post) return XK_FAILURE;

    post->buffer = buffer;
    post->size = size;

    /* insert at beginning of list */
    post->next = dtobj->post;
    dtobj->post = post;

    dtobj->numPost++;

    xTrace3(datatrace, TR_EVENTS,
            "dtInsertPostAmble - postamble %d inserted (%10u bytes) to obj %s",
	    dtobj->numPost, size, dtobj->traceName);

    return XK_SUCCESS;
}

dt *
dtGetTraceObj(traceName)
char *traceName;
{
    dt *dtobj;

    for (dtobj = dts.dtlist; dtobj; dtobj = dtobj->next)
        if (! strcmp(dtobj->traceName, traceName))
            return dtobj;
    return NULL;
}

dt *
dtGetTopTraceObj()
{
    return dts.dtlist;
}
