Home | History | Annotate | Download | only in dmtracedump
      1 /* //device/tools/dmtracedump/CreateTrace.c
      2 **
      3 ** Copyright 2006, The Android Open Source Project
      4 **
      5 ** Licensed under the Apache License, Version 2.0 (the "License");
      6 ** you may not use this file except in compliance with the License.
      7 ** You may obtain a copy of the License at
      8 **
      9 **     http://www.apache.org/licenses/LICENSE-2.0
     10 **
     11 ** Unless required by applicable law or agreed to in writing, software
     12 ** distributed under the License is distributed on an "AS IS" BASIS,
     13 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14 ** See the License for the specific language governing permissions and
     15 ** limitations under the License.
     16 */
     17 
     18 /*
     19  * Create a test file in the format required by dmtrace.
     20  */
     21 #define NOT_VM
     22 #include "Profile.h"        // from VM header
     23 
     24 #include <string.h>
     25 #include <stdio.h>
     26 #include <stdlib.h>
     27 #include <string.h>
     28 #include <errno.h>
     29 #include <assert.h>
     30 #include <unistd.h>
     31 #include <sys/time.h>
     32 #include <time.h>
     33 #include <ctype.h>
     34 
     35 /*
     36  * Values from the header of the data file.
     37  */
     38 typedef struct DataHeader {
     39     unsigned int magic;
     40     short version;
     41     short offsetToData;
     42     long long startWhen;
     43 } DataHeader;
     44 
     45 #define VERSION 2
     46 int versionNumber = VERSION;
     47 int verbose = 0;
     48 
     49 DataHeader header = { 0x574f4c53, VERSION, sizeof(DataHeader), 0LL };
     50 
     51 char *versionHeader = "*version\n";
     52 char *clockDef = "clock=thread-cpu\n";
     53 
     54 char *keyThreads =
     55 "*threads\n"
     56 "1      main\n"
     57 "2      foo\n"
     58 "3      bar\n"
     59 "4      blah\n";
     60 
     61 char *keyEnd = "*end\n";
     62 
     63 typedef struct dataRecord {
     64     unsigned int        time;
     65     int                 threadId;
     66     unsigned int        action;         /* 0=entry, 1=exit, 2=exception exit */
     67     char                *fullName;
     68     char                *className;
     69     char                *methodName;
     70     char                *signature;
     71     unsigned int        methodId;
     72 } dataRecord;
     73 
     74 dataRecord *records;
     75 
     76 #define BUF_SIZE 1024
     77 char buf[BUF_SIZE];
     78 
     79 typedef struct stack {
     80     dataRecord  **frames;
     81     int         indentLevel;
     82 } stack;
     83 
     84 /* Mac OS doesn't have strndup(), so implement it here.
     85  */
     86 char *strndup(const char *src, size_t len)
     87 {
     88     char *dest = (char *) malloc(len + 1);
     89     strncpy(dest, src, len);
     90     dest[len] = 0;
     91     return dest;
     92 }
     93 
     94 /*
     95  * Parse the input file.  It looks something like this:
     96  * # This is a comment line
     97  * 4  1 A
     98  * 6  1  B
     99  * 8  1  B
    100  * 10 1 A
    101  *
    102  * where the first column is the time, the second column is the thread id,
    103  * and the third column is the method (actually just the class name).  The
    104  * number of spaces between the 2nd and 3rd columns is the indentation and
    105  * determines the call stack.  Each called method must be indented by one
    106  * more space.  In the example above, A is called at time 4, A calls B at
    107  * time 6, B returns at time 8, and A returns at time 10.  Thread 1 is the
    108  * only thread that is running.
    109  *
    110  * An alternative file format leaves out the first two columns:
    111  * A
    112  *  B
    113  *  B
    114  * A
    115  *
    116  * In this file format, the thread id is always 1, and the time starts at
    117  * 2 and increments by 2 for each line.
    118  */
    119 void parseInputFile(const char *inputFileName)
    120 {
    121     unsigned int time = 0, threadId;
    122     int len;
    123     int linenum = 0;
    124     int nextRecord = 0;
    125     int indentLevel = 0;
    126     stack *callStack;
    127 
    128     FILE *inputFp = fopen(inputFileName, "r");
    129     if (inputFp == NULL) {
    130         perror(inputFileName);
    131         exit(1);
    132     }
    133 
    134     /* Count the number of lines in the buffer */
    135     int numRecords = 0;
    136     int maxThreadId = 1;
    137     int maxFrames = 0;
    138     char *indentEnd;
    139     while (fgets(buf, BUF_SIZE, inputFp)) {
    140         char *cp = buf;
    141         if (*cp == '#')
    142             continue;
    143         numRecords += 1;
    144         if (isdigit(*cp)) {
    145             int time = strtoul(cp, &cp, 0);
    146             while (isspace(*cp))
    147                 cp += 1;
    148             int threadId = strtoul(cp, &cp, 0);
    149             if (maxThreadId < threadId)
    150                 maxThreadId = threadId;
    151         }
    152         indentEnd = cp;
    153         while (isspace(*indentEnd))
    154             indentEnd += 1;
    155         if (indentEnd - cp + 1 > maxFrames)
    156             maxFrames = indentEnd - cp + 1;
    157     }
    158     int numThreads = maxThreadId + 1;
    159 
    160     /* Add space for a sentinel record at the end */
    161     numRecords += 1;
    162     records = (dataRecord *) malloc(sizeof(dataRecord) * numRecords);
    163     callStack = (stack *) malloc(sizeof(stack) * numThreads);
    164     int ii;
    165     for (ii = 0; ii < numThreads; ++ii) {
    166         callStack[ii].frames = NULL;
    167         callStack[ii].indentLevel = 0;
    168     }
    169 
    170     rewind(inputFp);
    171     while (fgets(buf, BUF_SIZE, inputFp)) {
    172         int indent;
    173         int action;
    174         char *save_cp;
    175 
    176         linenum += 1;
    177         char *cp = buf;
    178 
    179         /* Skip lines that start with '#' */
    180         if (*cp == '#')
    181             continue;
    182 
    183         /* Get time and thread id */
    184         if (!isdigit(*cp)) {
    185             /* If the line does not begin with a digit, then fill in
    186              * default values for the time and threadId.
    187              */
    188             time += 2;
    189             threadId = 1;
    190         } else {
    191             time = strtoul(cp, &cp, 0);
    192             while (isspace(*cp))
    193                 cp += 1;
    194             threadId = strtoul(cp, &cp, 0);
    195             cp += 1;
    196         }
    197 
    198         // Allocate space for the thread stack, if necessary
    199         if (callStack[threadId].frames == NULL) {
    200             dataRecord **stk;
    201             stk = (dataRecord **) malloc(sizeof(dataRecord *) * maxFrames);
    202             callStack[threadId].frames = stk;
    203         }
    204         indentLevel = callStack[threadId].indentLevel;
    205 
    206         save_cp = cp;
    207         while (isspace(*cp)) {
    208             cp += 1;
    209         }
    210         indent = cp - save_cp + 1;
    211         records[nextRecord].time = time;
    212         records[nextRecord].threadId = threadId;
    213 
    214         save_cp = cp;
    215         while (*cp != '\n')
    216             cp += 1;
    217 
    218         /* Remove trailing spaces */
    219         cp -= 1;
    220         while (isspace(*cp))
    221             cp -= 1;
    222         cp += 1;
    223         len = cp - save_cp;
    224         records[nextRecord].fullName = strndup(save_cp, len);
    225 
    226         /* Parse the name to support "class.method signature" */
    227         records[nextRecord].className = NULL;
    228         records[nextRecord].methodName = NULL;
    229         records[nextRecord].signature = NULL;
    230         cp = strchr(save_cp, '.');
    231         if (cp) {
    232             len = cp - save_cp;
    233             if (len > 0)
    234                 records[nextRecord].className = strndup(save_cp, len);
    235             save_cp = cp + 1;
    236             cp = strchr(save_cp, ' ');
    237             if (cp == NULL)
    238                 cp = strchr(save_cp, '\n');
    239             if (cp && cp > save_cp) {
    240                 len = cp - save_cp;
    241                 records[nextRecord].methodName = strndup(save_cp, len);
    242                 save_cp = cp + 1;
    243                 cp = strchr(save_cp, ' ');
    244                 if (cp == NULL)
    245                     cp = strchr(save_cp, '\n');
    246                 if (cp && cp > save_cp) {
    247                     len = cp - save_cp;
    248                     records[nextRecord].signature = strndup(save_cp, len);
    249                 }
    250             }
    251         }
    252 
    253         if (verbose) {
    254             printf("Indent: %d; IndentLevel: %d; Line: %s", indent, indentLevel, buf);
    255         }
    256 
    257         action = 0;
    258         if (indent == indentLevel + 1) { // Entering a method
    259             if (verbose)
    260                 printf("  Entering %s\n", records[nextRecord].fullName);
    261             callStack[threadId].frames[indentLevel] = &records[nextRecord];
    262         } else if (indent == indentLevel) { // Exiting a method
    263             // Exiting method must be currently on top of stack (unless stack is empty)
    264             if (callStack[threadId].frames[indentLevel - 1] == NULL) {
    265                 if (verbose)
    266                     printf("  Exiting %s (past bottom of stack)\n", records[nextRecord].fullName);
    267                 callStack[threadId].frames[indentLevel - 1] = &records[nextRecord];
    268                 action = 1;
    269             } else {
    270                 if (indentLevel < 1) {
    271                     fprintf(stderr, "Error: line %d: %s", linenum, buf);
    272                     fprintf(stderr, "  expected positive (>0) indentation, found %d\n",
    273                             indent);
    274                     exit(1);
    275                 }
    276                 char *name = callStack[threadId].frames[indentLevel - 1]->fullName;
    277                 if (strcmp(name, records[nextRecord].fullName) == 0) {
    278                     if (verbose)
    279                         printf("  Exiting %s\n", name);
    280                     action = 1;
    281                 } else { // exiting method doesn't match stack's top method
    282                     fprintf(stderr, "Error: line %d: %s", linenum, buf);
    283                     fprintf(stderr, "  expected exit from %s\n",
    284                             callStack[threadId].frames[indentLevel - 1]->fullName);
    285                     exit(1);
    286                 }
    287             }
    288         } else {
    289             if (nextRecord != 0) {
    290                 fprintf(stderr, "Error: line %d: %s", linenum, buf);
    291                 fprintf(stderr, "  expected indentation %d [+1], found %d\n",
    292                         indentLevel, indent);
    293                 exit(1);
    294             }
    295 
    296             if (verbose) {
    297                 printf("  Nonzero indent at first record\n");
    298                 printf("  Entering %s\n", records[nextRecord].fullName);
    299             }
    300 
    301             // This is the first line of data, so we allow a larger
    302             // initial indent.  This allows us to test popping off more
    303             // frames than we entered.
    304             indentLevel = indent - 1;
    305             callStack[threadId].frames[indentLevel] = &records[nextRecord];
    306         }
    307 
    308         if (action == 0)
    309             indentLevel += 1;
    310         else
    311             indentLevel -= 1;
    312         records[nextRecord].action = action;
    313         callStack[threadId].indentLevel = indentLevel;
    314 
    315         nextRecord += 1;
    316     }
    317 
    318     /* Mark the last record with a sentinel */
    319     memset(&records[nextRecord], 0, sizeof(dataRecord));
    320 }
    321 
    322 
    323 /*
    324  * Write values to the binary data file.
    325  */
    326 void write2LE(FILE* fp, unsigned short val)
    327 {
    328     putc(val & 0xff, fp);
    329     putc(val >> 8, fp);
    330 }
    331 
    332 void write4LE(FILE* fp, unsigned int val)
    333 {
    334     putc(val & 0xff, fp);
    335     putc((val >> 8) & 0xff, fp);
    336     putc((val >> 16) & 0xff, fp);
    337     putc((val >> 24) & 0xff, fp);
    338 }
    339 
    340 void write8LE(FILE* fp, unsigned long long val)
    341 {
    342     putc(val & 0xff, fp);
    343     putc((val >> 8) & 0xff, fp);
    344     putc((val >> 16) & 0xff, fp);
    345     putc((val >> 24) & 0xff, fp);
    346     putc((val >> 32) & 0xff, fp);
    347     putc((val >> 40) & 0xff, fp);
    348     putc((val >> 48) & 0xff, fp);
    349     putc((val >> 56) & 0xff, fp);
    350 }
    351 
    352 void writeDataRecord(FILE *dataFp, int threadId, unsigned int methodVal,
    353                    unsigned int elapsedTime)
    354 {
    355     if (versionNumber == 1)
    356         putc(threadId, dataFp);
    357     else
    358         write2LE(dataFp, threadId);
    359     write4LE(dataFp, methodVal);
    360     write4LE(dataFp, elapsedTime);
    361 }
    362 
    363 void writeDataHeader(FILE *dataFp)
    364 {
    365     struct timeval tv;
    366     struct timezone tz;
    367 
    368     gettimeofday(&tv, &tz);
    369     unsigned long long startTime = tv.tv_sec;
    370     startTime = (startTime << 32) | tv.tv_usec;
    371     header.version = versionNumber;
    372     write4LE(dataFp, header.magic);
    373     write2LE(dataFp, header.version);
    374     write2LE(dataFp, header.offsetToData);
    375     write8LE(dataFp, startTime);
    376 }
    377 
    378 void writeKeyMethods(FILE *keyFp)
    379 {
    380     dataRecord *pRecord, *pNext;
    381     char *methodStr = "*methods\n";
    382     fwrite(methodStr, strlen(methodStr), 1, keyFp);
    383 
    384     /* Assign method ids in multiples of 4 */
    385     unsigned int methodId = 0;
    386     for (pRecord = records; pRecord->fullName; ++pRecord) {
    387         if (pRecord->methodId)
    388             continue;
    389         unsigned int id = ++methodId << 2;
    390         pRecord->methodId = id;
    391 
    392         /* Assign this id to all the other records that have the
    393          * same name.
    394          */
    395         for (pNext = pRecord + 1; pNext->fullName; ++pNext) {
    396             if (pNext->methodId)
    397                 continue;
    398             if (strcmp(pRecord->fullName, pNext->fullName) == 0)
    399                 pNext->methodId = id;
    400         }
    401         if (pRecord->className == NULL || pRecord->methodName == NULL) {
    402             fprintf(keyFp, "%#x        %s      m       ()\n",
    403                     pRecord->methodId, pRecord->fullName);
    404         } else if (pRecord->signature == NULL) {
    405             fprintf(keyFp, "%#x        %s      %s      ()\n",
    406                     pRecord->methodId, pRecord->className,
    407                     pRecord->methodName);
    408         } else {
    409             fprintf(keyFp, "%#x        %s      %s      %s\n",
    410                     pRecord->methodId, pRecord->className,
    411                     pRecord->methodName, pRecord->signature);
    412         }
    413     }
    414 }
    415 
    416 void writeKeys(FILE *keyFp)
    417 {
    418     fprintf(keyFp, "%s%d\n%s", versionHeader, versionNumber, clockDef);
    419     fwrite(keyThreads, strlen(keyThreads), 1, keyFp);
    420     writeKeyMethods(keyFp);
    421     fwrite(keyEnd, strlen(keyEnd), 1, keyFp);
    422 }
    423 
    424 void writeDataRecords(FILE *dataFp)
    425 {
    426     dataRecord *pRecord;
    427 
    428     for (pRecord = records; pRecord->fullName; ++pRecord) {
    429         unsigned int val = METHOD_COMBINE(pRecord->methodId, pRecord->action);
    430         writeDataRecord(dataFp, pRecord->threadId, val, pRecord->time);
    431     }
    432 }
    433 
    434 void writeTrace(const char* traceFileName)
    435 {
    436     FILE *fp = fopen(traceFileName, "w");
    437     if (fp == NULL) {
    438         perror(traceFileName);
    439         exit(1);
    440     }
    441     writeKeys(fp);
    442     writeDataHeader(fp);
    443     writeDataRecords(fp);
    444     fclose(fp);
    445 }
    446 
    447 int parseOptions(int argc, char **argv)
    448 {
    449     int err = 0;
    450     while (1) {
    451         int opt = getopt(argc, argv, "v:d");
    452         if (opt == -1)
    453             break;
    454         switch (opt) {
    455             case 'v':
    456                 versionNumber = strtoul(optarg, NULL, 0);
    457                 if (versionNumber != 1 && versionNumber != 2) {
    458                     fprintf(stderr, "Error: version number (%d) must be 1 or 2\n",
    459                             versionNumber);
    460                     err = 1;
    461                 }
    462                 break;
    463             case 'd':
    464                 verbose = 1;
    465                 break;
    466             default:
    467                 err = 1;
    468                 break;
    469         }
    470     }
    471     return err;
    472 }
    473 
    474 int main(int argc, char** argv)
    475 {
    476     char *inputFile;
    477     char *traceFileName = NULL;
    478     int len;
    479 
    480     if (parseOptions(argc, argv) || argc - optind != 2) {
    481         fprintf(stderr, "Usage: %s [-v version] [-d] input_file trace_prefix\n",
    482                 argv[0]);
    483         exit(1);
    484     }
    485 
    486     inputFile = argv[optind++];
    487     parseInputFile(inputFile);
    488     traceFileName = argv[optind++];
    489 
    490     writeTrace(traceFileName);
    491 
    492     return 0;
    493 }
    494