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