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  */
36 typedef 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
44 int32_t versionNumber = VERSION;
45 int32_t verbose = 0;
46 
47 DataHeader header = {0x574f4c53, VERSION, sizeof(DataHeader), 0LL};
48 
49 const char* versionHeader = "*version\n";
50 const char* clockDef = "clock=thread-cpu\n";
51 
52 const char* keyThreads =
53     "*threads\n"
54     "1      main\n"
55     "2      foo\n"
56     "3      bar\n"
57     "4      blah\n";
58 
59 const char* keyEnd = "*end\n";
60 
61 typedef 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 
72 dataRecord* records;
73 
74 #define BUF_SIZE 1024
75 char buf[BUF_SIZE];
76 
77 typedef struct stack {
78   dataRecord** frames;
79   int32_t indentLevel;
80 } stack;
81 
82 /* Mac OS doesn't have strndup(), so implement it here.
83  */
strndup(const char * src,size_t len)84 char* 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  */
parseInputFile(const char * inputFileName)116 void 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  */
write2LE(FILE * fp,uint16_t val)306 void write2LE(FILE* fp, uint16_t val) {
307   putc(val & 0xff, fp);
308   putc(val >> 8, fp);
309 }
310 
write4LE(FILE * fp,uint32_t val)311 void 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 
write8LE(FILE * fp,uint64_t val)318 void 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 
writeDataRecord(FILE * dataFp,int32_t threadId,uint32_t methodVal,uint32_t elapsedTime)329 void 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 
writeDataHeader(FILE * dataFp)338 void 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 
writeKeyMethods(FILE * keyFp)352 void 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 
writeKeys(FILE * keyFp)383 void 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 
writeDataRecords(FILE * dataFp)390 void 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 
writeTrace(const char * traceFileName)397 void 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 
parseOptions(int32_t argc,char ** argv)409 int32_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 
main(int32_t argc,char ** argv)433 int32_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