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 */ 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 */ 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 */ 306 void write2LE(FILE* fp, uint16_t val) { 307 putc(val & 0xff, fp); 308 putc(val >> 8, fp); 309 } 310 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 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 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 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 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 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 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 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 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 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