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