/* //device/tools/dmtracedump/CreateTrace.c
**
** Copyright 2006, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
**     http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/

/*
 * Create a test file in the format required by dmtrace.
 */
#define NOT_VM
#include "Profile.h"        // from VM header

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <unistd.h>
#include <sys/time.h>
#include <time.h>
#include <ctype.h>

/*
 * Values from the header of the data file.
 */
typedef struct DataHeader {
    unsigned int magic;
    short version;
    short offsetToData;
    long long startWhen;
} DataHeader;

#define VERSION 2
int versionNumber = VERSION;
int verbose = 0;

DataHeader header = { 0x574f4c53, VERSION, sizeof(DataHeader), 0LL };

char *versionHeader = "*version\n";
char *clockDef = "clock=thread-cpu\n";

char *keyThreads =
"*threads\n"
"1      main\n"
"2      foo\n"
"3      bar\n"
"4      blah\n";

char *keyEnd = "*end\n";

typedef struct dataRecord {
    unsigned int        time;
    int                 threadId;
    unsigned int        action;         /* 0=entry, 1=exit, 2=exception exit */
    char                *fullName;
    char                *className;
    char                *methodName;
    char                *signature;
    unsigned int        methodId;
} dataRecord;

dataRecord *records;

#define BUF_SIZE 1024
char buf[BUF_SIZE];

typedef struct stack {
    dataRecord  **frames;
    int         indentLevel;
} stack;

/* Mac OS doesn't have strndup(), so implement it here.
 */
char *strndup(const char *src, size_t len)
{
    char *dest = (char *) malloc(len + 1);
    strncpy(dest, src, len);
    dest[len] = 0;
    return dest;
}

/*
 * Parse the input file.  It looks something like this:
 * # This is a comment line
 * 4  1 A
 * 6  1  B
 * 8  1  B
 * 10 1 A
 *
 * where the first column is the time, the second column is the thread id,
 * and the third column is the method (actually just the class name).  The
 * number of spaces between the 2nd and 3rd columns is the indentation and
 * determines the call stack.  Each called method must be indented by one
 * more space.  In the example above, A is called at time 4, A calls B at
 * time 6, B returns at time 8, and A returns at time 10.  Thread 1 is the
 * only thread that is running.
 *
 * An alternative file format leaves out the first two columns:
 * A
 *  B
 *  B
 * A
 *
 * In this file format, the thread id is always 1, and the time starts at
 * 2 and increments by 2 for each line.
 */
void parseInputFile(const char *inputFileName)
{
    unsigned int time = 0, threadId;
    int len;
    int linenum = 0;
    int nextRecord = 0;
    int indentLevel = 0;
    stack *callStack;

    FILE *inputFp = fopen(inputFileName, "r");
    if (inputFp == NULL) {
        perror(inputFileName);
        exit(1);
    }

    /* Count the number of lines in the buffer */
    int numRecords = 0;
    int maxThreadId = 1;
    int maxFrames = 0;
    char *indentEnd;
    while (fgets(buf, BUF_SIZE, inputFp)) {
        char *cp = buf;
        if (*cp == '#')
            continue;
        numRecords += 1;
        if (isdigit(*cp)) {
            int time = strtoul(cp, &cp, 0);
            while (isspace(*cp))
                cp += 1;
            int threadId = strtoul(cp, &cp, 0);
            if (maxThreadId < threadId)
                maxThreadId = threadId;
        }
        indentEnd = cp;
        while (isspace(*indentEnd))
            indentEnd += 1;
        if (indentEnd - cp + 1 > maxFrames)
            maxFrames = indentEnd - cp + 1;
    }
    int numThreads = maxThreadId + 1;

    /* Add space for a sentinel record at the end */
    numRecords += 1;
    records = (dataRecord *) malloc(sizeof(dataRecord) * numRecords);
    callStack = (stack *) malloc(sizeof(stack) * numThreads);
    int ii;
    for (ii = 0; ii < numThreads; ++ii) {
        callStack[ii].frames = NULL;
        callStack[ii].indentLevel = 0;
    }

    rewind(inputFp);
    while (fgets(buf, BUF_SIZE, inputFp)) {
        int indent;
        int action;
        char *save_cp;

        linenum += 1;
        char *cp = buf;

        /* Skip lines that start with '#' */
        if (*cp == '#')
            continue;

        /* Get time and thread id */
        if (!isdigit(*cp)) {
            /* If the line does not begin with a digit, then fill in
             * default values for the time and threadId.
             */
            time += 2;
            threadId = 1;
        } else {
            time = strtoul(cp, &cp, 0);
            while (isspace(*cp))
                cp += 1;
            threadId = strtoul(cp, &cp, 0);
            cp += 1;
        }

        // Allocate space for the thread stack, if necessary
        if (callStack[threadId].frames == NULL) {
            dataRecord **stk;
            stk = (dataRecord **) malloc(sizeof(dataRecord *) * maxFrames);
            callStack[threadId].frames = stk;
        }
        indentLevel = callStack[threadId].indentLevel;

        save_cp = cp;
        while (isspace(*cp)) {
            cp += 1;
        }
        indent = cp - save_cp + 1;
        records[nextRecord].time = time;
        records[nextRecord].threadId = threadId;

        save_cp = cp;
        while (*cp != '\n')
            cp += 1;

        /* Remove trailing spaces */
        cp -= 1;
        while (isspace(*cp))
            cp -= 1;
        cp += 1;
        len = cp - save_cp;
        records[nextRecord].fullName = strndup(save_cp, len);

        /* Parse the name to support "class.method signature" */
        records[nextRecord].className = NULL;
        records[nextRecord].methodName = NULL;
        records[nextRecord].signature = NULL;
        cp = strchr(save_cp, '.');
        if (cp) {
            len = cp - save_cp;
            if (len > 0)
                records[nextRecord].className = strndup(save_cp, len);
            save_cp = cp + 1;
            cp = strchr(save_cp, ' ');
            if (cp == NULL)
                cp = strchr(save_cp, '\n');
            if (cp && cp > save_cp) {
                len = cp - save_cp;
                records[nextRecord].methodName = strndup(save_cp, len);
                save_cp = cp + 1;
                cp = strchr(save_cp, ' ');
                if (cp == NULL)
                    cp = strchr(save_cp, '\n');
                if (cp && cp > save_cp) {
                    len = cp - save_cp;
                    records[nextRecord].signature = strndup(save_cp, len);
                }
            }
        }

        if (verbose) {
            printf("Indent: %d; IndentLevel: %d; Line: %s", indent, indentLevel, buf);
        }

        action = 0;
        if (indent == indentLevel + 1) { // Entering a method
            if (verbose)
                printf("  Entering %s\n", records[nextRecord].fullName);
            callStack[threadId].frames[indentLevel] = &records[nextRecord];
        } else if (indent == indentLevel) { // Exiting a method
            // Exiting method must be currently on top of stack (unless stack is empty)
            if (callStack[threadId].frames[indentLevel - 1] == NULL) {
                if (verbose)
                    printf("  Exiting %s (past bottom of stack)\n", records[nextRecord].fullName);
                callStack[threadId].frames[indentLevel - 1] = &records[nextRecord];
                action = 1;
            } else {
                if (indentLevel < 1) {
                    fprintf(stderr, "Error: line %d: %s", linenum, buf);
                    fprintf(stderr, "  expected positive (>0) indentation, found %d\n",
                            indent);
                    exit(1);
                }
                char *name = callStack[threadId].frames[indentLevel - 1]->fullName;
                if (strcmp(name, records[nextRecord].fullName) == 0) {
                    if (verbose)
                        printf("  Exiting %s\n", name);
                    action = 1;
                } else { // exiting method doesn't match stack's top method
                    fprintf(stderr, "Error: line %d: %s", linenum, buf);
                    fprintf(stderr, "  expected exit from %s\n",
                            callStack[threadId].frames[indentLevel - 1]->fullName);
                    exit(1);
                }
            }
        } else {
            if (nextRecord != 0) {
                fprintf(stderr, "Error: line %d: %s", linenum, buf);
                fprintf(stderr, "  expected indentation %d [+1], found %d\n",
                        indentLevel, indent);
                exit(1);
            }

            if (verbose) {
                printf("  Nonzero indent at first record\n");
                printf("  Entering %s\n", records[nextRecord].fullName);
            }

            // This is the first line of data, so we allow a larger
            // initial indent.  This allows us to test popping off more
            // frames than we entered.
            indentLevel = indent - 1;
            callStack[threadId].frames[indentLevel] = &records[nextRecord];
        }

        if (action == 0)
            indentLevel += 1;
        else
            indentLevel -= 1;
        records[nextRecord].action = action;
        callStack[threadId].indentLevel = indentLevel;

        nextRecord += 1;
    }

    /* Mark the last record with a sentinel */
    memset(&records[nextRecord], 0, sizeof(dataRecord));
}


/*
 * Write values to the binary data file.
 */
void write2LE(FILE* fp, unsigned short val)
{
    putc(val & 0xff, fp);
    putc(val >> 8, fp);
}

void write4LE(FILE* fp, unsigned int val)
{
    putc(val & 0xff, fp);
    putc((val >> 8) & 0xff, fp);
    putc((val >> 16) & 0xff, fp);
    putc((val >> 24) & 0xff, fp);
}

void write8LE(FILE* fp, unsigned long long val)
{
    putc(val & 0xff, fp);
    putc((val >> 8) & 0xff, fp);
    putc((val >> 16) & 0xff, fp);
    putc((val >> 24) & 0xff, fp);
    putc((val >> 32) & 0xff, fp);
    putc((val >> 40) & 0xff, fp);
    putc((val >> 48) & 0xff, fp);
    putc((val >> 56) & 0xff, fp);
}

void writeDataRecord(FILE *dataFp, int threadId, unsigned int methodVal,
                   unsigned int elapsedTime)
{
    if (versionNumber == 1)
        putc(threadId, dataFp);
    else
        write2LE(dataFp, threadId);
    write4LE(dataFp, methodVal);
    write4LE(dataFp, elapsedTime);
}

void writeDataHeader(FILE *dataFp)
{
    struct timeval tv;
    struct timezone tz;

    gettimeofday(&tv, &tz);
    unsigned long long startTime = tv.tv_sec;
    startTime = (startTime << 32) | tv.tv_usec;
    header.version = versionNumber;
    write4LE(dataFp, header.magic);
    write2LE(dataFp, header.version);
    write2LE(dataFp, header.offsetToData);
    write8LE(dataFp, startTime);
}

void writeKeyMethods(FILE *keyFp)
{
    dataRecord *pRecord, *pNext;
    char *methodStr = "*methods\n";
    fwrite(methodStr, strlen(methodStr), 1, keyFp);

    /* Assign method ids in multiples of 4 */
    unsigned int methodId = 0;
    for (pRecord = records; pRecord->fullName; ++pRecord) {
        if (pRecord->methodId)
            continue;
        unsigned int id = ++methodId << 2;
        pRecord->methodId = id;

        /* Assign this id to all the other records that have the
         * same name.
         */
        for (pNext = pRecord + 1; pNext->fullName; ++pNext) {
            if (pNext->methodId)
                continue;
            if (strcmp(pRecord->fullName, pNext->fullName) == 0)
                pNext->methodId = id;
        }
        if (pRecord->className == NULL || pRecord->methodName == NULL) {
            fprintf(keyFp, "0x%x        %s      m       ()\n",
                    pRecord->methodId, pRecord->fullName);
        } else if (pRecord->signature == NULL) {
            fprintf(keyFp, "0x%x        %s      %s      ()\n",
                    pRecord->methodId, pRecord->className,
                    pRecord->methodName);
        } else {
            fprintf(keyFp, "0x%x        %s      %s      %s\n",
                    pRecord->methodId, pRecord->className,
                    pRecord->methodName, pRecord->signature);
        }
    }
}

void writeKeys(FILE *keyFp)
{
    fprintf(keyFp, "%s%d\n%s", versionHeader, versionNumber, clockDef);
    fwrite(keyThreads, strlen(keyThreads), 1, keyFp);
    writeKeyMethods(keyFp);
    fwrite(keyEnd, strlen(keyEnd), 1, keyFp);
}

void writeDataRecords(FILE *dataFp)
{
    dataRecord *pRecord;

    for (pRecord = records; pRecord->fullName; ++pRecord) {
        unsigned int val = METHOD_COMBINE(pRecord->methodId, pRecord->action);
        writeDataRecord(dataFp, pRecord->threadId, val, pRecord->time);
    }
}

void writeTrace(const char* traceFileName)
{
    FILE *fp = fopen(traceFileName, "w");
    if (fp == NULL) {
        perror(traceFileName);
        exit(1);
    }
    writeKeys(fp);
    writeDataHeader(fp);
    writeDataRecords(fp);
    fclose(fp);
}

int parseOptions(int argc, char **argv)
{
    int err = 0;
    while (1) {
        int opt = getopt(argc, argv, "v:d");
        if (opt == -1)
            break;
        switch (opt) {
            case 'v':
                versionNumber = strtoul(optarg, NULL, 0);
                if (versionNumber != 1 && versionNumber != 2) {
                    fprintf(stderr, "Error: version number (%d) must be 1 or 2\n",
                            versionNumber);
                    err = 1;
                }
                break;
            case 'd':
                verbose = 1;
                break;
            default:
                err = 1;
                break;
        }
    }
    return err;
}

int main(int argc, char** argv)
{
    char *inputFile;
    char *traceFileName = NULL;
    int len;

    if (parseOptions(argc, argv) || argc - optind != 2) {
        fprintf(stderr, "Usage: %s [-v version] [-d] input_file trace_prefix\n",
                argv[0]);
        exit(1);
    }

    inputFile = argv[optind++];
    parseInputFile(inputFile);
    traceFileName = argv[optind++];

    writeTrace(traceFileName);

    return 0;
}
