| /* //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, "%#x %s m ()\n", |
| pRecord->methodId, pRecord->fullName); |
| } else if (pRecord->signature == NULL) { |
| fprintf(keyFp, "%#x %s %s ()\n", |
| pRecord->methodId, pRecord->className, |
| pRecord->methodName); |
| } else { |
| fprintf(keyFp, "%#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; |
| } |