/* //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;
}