diff options
Diffstat (limited to 'tools/dmtracedump/createtesttrace.cc')
| -rw-r--r-- | tools/dmtracedump/createtesttrace.cc | 449 |
1 files changed, 449 insertions, 0 deletions
diff --git a/tools/dmtracedump/createtesttrace.cc b/tools/dmtracedump/createtesttrace.cc new file mode 100644 index 0000000000..444cce4082 --- /dev/null +++ b/tools/dmtracedump/createtesttrace.cc @@ -0,0 +1,449 @@ +/* + * 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; +} |