You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
450 lines
12 KiB
450 lines
12 KiB
/*
|
|
* 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;
|
|
}
|