/* * Copyright 2015, 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. */ #include "profile.h" // from VM header #include <assert.h> #include <ctype.h> #include <errno.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/time.h> #include <time.h> #include <unistd.h> /* * Values from the header of the data file. */ typedef struct DataHeader { uint32_t magic; int16_t version; int16_t offsetToData; int64_t startWhen; } DataHeader; #define VERSION 2 int32_t versionNumber = VERSION; int32_t verbose = 0; DataHeader header = {0x574f4c53, VERSION, sizeof(DataHeader), 0LL}; const char* versionHeader = "*version\n"; const char* clockDef = "clock=thread-cpu\n"; const char* keyThreads = "*threads\n" "1 main\n" "2 foo\n" "3 bar\n" "4 blah\n"; const char* keyEnd = "*end\n"; typedef struct dataRecord { uint32_t time; int32_t threadId; uint32_t action; /* 0=entry, 1=exit, 2=exception exit */ char* fullName; char* className; char* methodName; char* signature; uint32_t methodId; } dataRecord; dataRecord* records; #define BUF_SIZE 1024 char buf[BUF_SIZE]; typedef struct stack { dataRecord** frames; int32_t indentLevel; } stack; /* Mac OS doesn't have strndup(), so implement it here. */ char* strndup(const char* src, size_t len) { char* dest = new char[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) { FILE* inputFp = fopen(inputFileName, "r"); if (inputFp == nullptr) { perror(inputFileName); exit(1); } /* Count the number of lines in the buffer */ int32_t numRecords = 0; int32_t maxThreadId = 1; int32_t maxFrames = 0; char* indentEnd; while (fgets(buf, BUF_SIZE, inputFp)) { char* cp = buf; if (*cp == '#') continue; numRecords += 1; if (isdigit(*cp)) { while (isspace(*cp)) cp += 1; int32_t 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; } int32_t numThreads = maxThreadId + 1; /* Add space for a sentinel record at the end */ numRecords += 1; records = new dataRecord[numRecords]; stack* callStack = new stack[numThreads]; for (int32_t ii = 0; ii < numThreads; ++ii) { callStack[ii].frames = nullptr; callStack[ii].indentLevel = 0; } rewind(inputFp); uint32_t time = 0; int32_t linenum = 0; int32_t nextRecord = 0; int32_t indentLevel = 0; while (fgets(buf, BUF_SIZE, inputFp)) { uint32_t threadId; int32_t len; int32_t indent; int32_t 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 == nullptr) { dataRecord** stk = new 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 = nullptr; records[nextRecord].methodName = nullptr; records[nextRecord].signature = nullptr; 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 == nullptr) 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 == nullptr) 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] == nullptr) { 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, uint16_t val) { putc(val & 0xff, fp); putc(val >> 8, fp); } void write4LE(FILE* fp, uint32_t val) { putc(val & 0xff, fp); putc((val >> 8) & 0xff, fp); putc((val >> 16) & 0xff, fp); putc((val >> 24) & 0xff, fp); } void write8LE(FILE* fp, uint64_t 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, int32_t threadId, uint32_t methodVal, uint32_t 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); uint64_t 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) { const char* methodStr = "*methods\n"; fwrite(methodStr, strlen(methodStr), 1, keyFp); /* Assign method ids in multiples of 4 */ uint32_t methodId = 0; for (dataRecord* pRecord = records; pRecord->fullName; ++pRecord) { if (pRecord->methodId) continue; uint32_t id = ++methodId << 2; pRecord->methodId = id; /* Assign this id to all the other records that have the * same name. */ for (dataRecord* pNext = pRecord + 1; pNext->fullName; ++pNext) { if (pNext->methodId) continue; if (strcmp(pRecord->fullName, pNext->fullName) == 0) pNext->methodId = id; } if (pRecord->className == nullptr || pRecord->methodName == nullptr) { fprintf(keyFp, "%#x %s m ()\n", pRecord->methodId, pRecord->fullName); } else if (pRecord->signature == nullptr) { 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) { for (dataRecord* pRecord = records; pRecord->fullName; ++pRecord) { uint32_t 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 == nullptr) { perror(traceFileName); exit(1); } writeKeys(fp); writeDataHeader(fp); writeDataRecords(fp); fclose(fp); } int32_t parseOptions(int32_t argc, char** argv) { int32_t err = 0; while (1) { int32_t opt = getopt(argc, argv, "v:d"); if (opt == -1) break; switch (opt) { case 'v': versionNumber = strtoul(optarg, nullptr, 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; } int32_t main(int32_t argc, char** argv) { char* inputFile; char* traceFileName = nullptr; 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; }