1 /* SPDX-License-Identifier: BSD-3-Clause 2 * Copyright (c) Intel Corporation. 3 * All rights reserved. 4 */ 5 6 #include "spdk/stdinc.h" 7 8 #include "spdk/env.h" 9 #include "spdk/string.h" 10 #include "spdk/trace.h" 11 #include "spdk/util.h" 12 #include "spdk/barrier.h" 13 14 #define TRACE_FILE_COPY_SIZE (32 * 1024) 15 #define TRACE_PATH_MAX 2048 16 17 static char *g_exe_name; 18 static int g_verbose = 1; 19 static uint64_t g_tsc_rate; 20 static uint64_t g_utsc_rate; 21 static bool g_shutdown = false; 22 static uint64_t g_histories_size; 23 24 struct lcore_trace_record_ctx { 25 char lcore_file[TRACE_PATH_MAX]; 26 int fd; 27 bool valid; 28 struct spdk_trace_history *in_history; 29 struct spdk_trace_history *out_history; 30 31 /* Recorded next entry index in record */ 32 uint64_t rec_next_entry; 33 34 /* Record tsc for report */ 35 uint64_t first_entry_tsc; 36 uint64_t last_entry_tsc; 37 38 /* Total number of entries in lcore trace file */ 39 uint64_t num_entries; 40 }; 41 42 struct aggr_trace_record_ctx { 43 const char *out_file; 44 int out_fd; 45 int shm_fd; 46 struct lcore_trace_record_ctx lcore_ports[SPDK_TRACE_MAX_LCORE]; 47 struct spdk_trace_histories *trace_histories; 48 }; 49 50 static int 51 input_trace_file_mmap(struct aggr_trace_record_ctx *ctx, const char *shm_name) 52 { 53 void *history_ptr; 54 int i; 55 56 ctx->shm_fd = shm_open(shm_name, O_RDONLY, 0); 57 if (ctx->shm_fd < 0) { 58 fprintf(stderr, "Could not open %s.\n", shm_name); 59 return -1; 60 } 61 62 /* Map the header of trace file */ 63 history_ptr = mmap(NULL, sizeof(struct spdk_trace_histories), PROT_READ, MAP_SHARED, ctx->shm_fd, 64 0); 65 if (history_ptr == MAP_FAILED) { 66 fprintf(stderr, "Could not mmap shm %s.\n", shm_name); 67 close(ctx->shm_fd); 68 return -1; 69 } 70 71 ctx->trace_histories = (struct spdk_trace_histories *)history_ptr; 72 73 g_tsc_rate = ctx->trace_histories->flags.tsc_rate; 74 g_utsc_rate = g_tsc_rate / 1000; 75 if (g_tsc_rate == 0) { 76 fprintf(stderr, "Invalid tsc_rate %ju\n", g_tsc_rate); 77 munmap(history_ptr, sizeof(struct spdk_trace_histories)); 78 close(ctx->shm_fd); 79 return -1; 80 } 81 82 if (g_verbose) { 83 printf("TSC Rate: %ju\n", g_tsc_rate); 84 } 85 86 /* Remap the entire trace file */ 87 g_histories_size = spdk_get_trace_histories_size(ctx->trace_histories); 88 munmap(history_ptr, sizeof(struct spdk_trace_histories)); 89 history_ptr = mmap(NULL, g_histories_size, PROT_READ, MAP_SHARED, ctx->shm_fd, 0); 90 if (history_ptr == MAP_FAILED) { 91 fprintf(stderr, "Could not remmap shm %s.\n", shm_name); 92 close(ctx->shm_fd); 93 return -1; 94 } 95 96 ctx->trace_histories = (struct spdk_trace_histories *)history_ptr; 97 for (i = 0; i < SPDK_TRACE_MAX_LCORE; i++) { 98 struct spdk_trace_history *history; 99 100 history = spdk_get_per_lcore_history(ctx->trace_histories, i); 101 ctx->lcore_ports[i].in_history = history; 102 ctx->lcore_ports[i].valid = (history != NULL); 103 104 if (g_verbose && history) { 105 printf("Number of trace entries for lcore (%d): %ju\n", i, 106 history->num_entries); 107 } 108 } 109 110 return 0; 111 } 112 113 static int 114 output_trace_files_prepare(struct aggr_trace_record_ctx *ctx, const char *aggr_path) 115 { 116 int flags = O_CREAT | O_EXCL | O_RDWR; 117 struct lcore_trace_record_ctx *port_ctx; 118 int name_len; 119 int i, rc; 120 121 /* Assign file names for related trace files */ 122 ctx->out_file = aggr_path; 123 for (i = 0; i < SPDK_TRACE_MAX_LCORE; i++) { 124 port_ctx = &ctx->lcore_ports[i]; 125 126 /* Get the length of trace file name for each lcore with format "%s-%d" */ 127 name_len = snprintf(port_ctx->lcore_file, TRACE_PATH_MAX, "%s-%d", ctx->out_file, i); 128 if (name_len >= TRACE_PATH_MAX) { 129 fprintf(stderr, "Length of file path (%s) exceeds limitation for lcore file.\n", 130 aggr_path); 131 goto err; 132 } 133 } 134 135 /* If output trace file already exists, try to unlink it together with its temporary files */ 136 if (access(ctx->out_file, F_OK) == 0) { 137 rc = unlink(ctx->out_file); 138 if (rc) { 139 goto err; 140 } 141 142 for (i = 0; i < SPDK_TRACE_MAX_LCORE; i++) { 143 port_ctx = &ctx->lcore_ports[i]; 144 if (access(port_ctx->lcore_file, F_OK) == 0) { 145 rc = unlink(port_ctx->lcore_file); 146 if (rc) { 147 goto err; 148 } 149 } 150 } 151 152 } 153 154 for (i = 0; i < SPDK_TRACE_MAX_LCORE; i++) { 155 port_ctx = &ctx->lcore_ports[i]; 156 157 if (!port_ctx->valid) { 158 continue; 159 } 160 161 port_ctx->fd = open(port_ctx->lcore_file, flags, 0600); 162 if (port_ctx->fd < 0) { 163 fprintf(stderr, "Could not open lcore file %s.\n", port_ctx->lcore_file); 164 goto err; 165 } 166 167 if (g_verbose) { 168 printf("Create tmp lcore trace file %s for lcore %d\n", port_ctx->lcore_file, i); 169 } 170 171 port_ctx->out_history = calloc(1, sizeof(struct spdk_trace_history)); 172 if (port_ctx->out_history == NULL) { 173 fprintf(stderr, "Failed to allocate memory for out_history.\n"); 174 goto err; 175 } 176 } 177 178 return 0; 179 180 err: 181 for (i = 0; i < SPDK_TRACE_MAX_LCORE; i++) { 182 port_ctx = &ctx->lcore_ports[i]; 183 free(port_ctx->out_history); 184 185 if (port_ctx->fd > 0) { 186 close(port_ctx->fd); 187 } 188 } 189 190 return -1; 191 } 192 193 static void 194 output_trace_files_finish(struct aggr_trace_record_ctx *ctx) 195 { 196 struct lcore_trace_record_ctx *port_ctx; 197 int i; 198 199 for (i = 0; i < SPDK_TRACE_MAX_LCORE; i++) { 200 port_ctx = &ctx->lcore_ports[i]; 201 202 free(port_ctx->out_history); 203 close(port_ctx->fd); 204 unlink(port_ctx->lcore_file); 205 206 if (g_verbose) { 207 printf("Remove tmp lcore trace file %s for lcore %d\n", port_ctx->lcore_file, i); 208 } 209 } 210 } 211 212 static int 213 cont_write(int fildes, const void *buf, size_t nbyte) 214 { 215 int rc; 216 int _nbyte = nbyte; 217 218 while (_nbyte) { 219 rc = write(fildes, buf, _nbyte); 220 if (rc < 0) { 221 if (errno != EINTR) { 222 return -1; 223 } 224 225 continue; 226 } 227 228 _nbyte -= rc; 229 } 230 231 return nbyte; 232 } 233 234 static int 235 cont_read(int fildes, void *buf, size_t nbyte) 236 { 237 int rc; 238 int _nbyte = nbyte; 239 240 while (_nbyte) { 241 rc = read(fildes, buf, _nbyte); 242 if (rc == 0) { 243 return nbyte - _nbyte; 244 } else if (rc < 0) { 245 if (errno != EINTR) { 246 return -1; 247 } 248 249 continue; 250 } 251 252 _nbyte -= rc; 253 } 254 255 return nbyte; 256 } 257 258 static int 259 lcore_trace_last_entry_idx(struct spdk_trace_history *in_history, int cir_next_idx) 260 { 261 int last_idx; 262 263 if (cir_next_idx == 0) { 264 last_idx = in_history->num_entries - 1; 265 } else { 266 last_idx = cir_next_idx - 1; 267 } 268 269 return last_idx; 270 } 271 272 static int 273 circular_buffer_padding_backward(int fd, struct spdk_trace_history *in_history, 274 int cir_start, int cir_end) 275 { 276 int rc; 277 278 if (cir_end <= cir_start) { 279 fprintf(stderr, "Wrong using of circular_buffer_padding_back\n"); 280 return -1; 281 } 282 283 rc = cont_write(fd, &in_history->entries[cir_start], 284 sizeof(struct spdk_trace_entry) * (cir_end - cir_start)); 285 if (rc < 0) { 286 fprintf(stderr, "Failed to append entries into lcore file\n"); 287 return rc; 288 } 289 290 return 0; 291 } 292 293 static int 294 circular_buffer_padding_across(int fd, struct spdk_trace_history *in_history, 295 int cir_start, int cir_end) 296 { 297 int rc; 298 int num_entries = in_history->num_entries; 299 300 if (cir_end > cir_start) { 301 fprintf(stderr, "Wrong using of circular_buffer_padding_across\n"); 302 return -1; 303 } 304 305 rc = cont_write(fd, &in_history->entries[cir_start], 306 sizeof(struct spdk_trace_entry) * (num_entries - cir_start)); 307 if (rc < 0) { 308 fprintf(stderr, "Failed to append entries into lcore file backward\n"); 309 return rc; 310 } 311 312 if (cir_end == 0) { 313 return 0; 314 } 315 316 rc = cont_write(fd, &in_history->entries[0], sizeof(struct spdk_trace_entry) * cir_end); 317 if (rc < 0) { 318 fprintf(stderr, "Failed to append entries into lcore file forward\n"); 319 return rc; 320 } 321 322 return 0; 323 } 324 325 static int 326 circular_buffer_padding_all(int fd, struct spdk_trace_history *in_history, 327 int cir_end) 328 { 329 return circular_buffer_padding_across(fd, in_history, cir_end, cir_end); 330 } 331 332 static int 333 lcore_trace_record(struct lcore_trace_record_ctx *lcore_port) 334 { 335 struct spdk_trace_history *in_history = lcore_port->in_history; 336 uint64_t rec_next_entry = lcore_port->rec_next_entry; 337 uint64_t rec_num_entries = lcore_port->num_entries; 338 int fd = lcore_port->fd; 339 uint64_t shm_next_entry; 340 uint64_t num_cir_entries; 341 uint64_t shm_cir_next; 342 uint64_t rec_cir_next; 343 int rc; 344 int last_idx; 345 346 shm_next_entry = in_history->next_entry; 347 348 /* Ensure all entries of spdk_trace_history are latest to next_entry */ 349 spdk_smp_rmb(); 350 351 if (shm_next_entry == rec_next_entry) { 352 /* There is no update */ 353 return 0; 354 } else if (shm_next_entry < rec_next_entry) { 355 /* Error branch */ 356 fprintf(stderr, "Trace porting error in lcore %d, trace rollback occurs.\n", in_history->lcore); 357 fprintf(stderr, "shm_next_entry is %ju, record_next_entry is %ju.\n", shm_next_entry, 358 rec_next_entry); 359 return -1; 360 } 361 362 num_cir_entries = in_history->num_entries; 363 shm_cir_next = shm_next_entry & (num_cir_entries - 1); 364 365 /* Record first entry's tsc and corresponding entries when recording first time. */ 366 if (lcore_port->first_entry_tsc == 0) { 367 if (shm_next_entry < num_cir_entries) { 368 /* Updates haven't been across circular buffer yet. 369 * The first entry in shared memory is the eldest one. 370 */ 371 lcore_port->first_entry_tsc = in_history->entries[0].tsc; 372 373 lcore_port->num_entries += shm_cir_next; 374 rc = circular_buffer_padding_backward(fd, in_history, 0, shm_cir_next); 375 } else { 376 /* Updates have already been across circular buffer. 377 * The eldest entry in shared memory is pointed by shm_cir_next. 378 */ 379 lcore_port->first_entry_tsc = in_history->entries[shm_cir_next].tsc; 380 381 lcore_port->num_entries += num_cir_entries; 382 rc = circular_buffer_padding_all(fd, in_history, shm_cir_next); 383 } 384 385 goto out; 386 } 387 388 if (shm_next_entry - rec_next_entry > num_cir_entries) { 389 /* There must be missed updates */ 390 fprintf(stderr, "Trace-record missed %ju trace entries\n", 391 shm_next_entry - rec_next_entry - num_cir_entries); 392 393 lcore_port->num_entries += num_cir_entries; 394 rc = circular_buffer_padding_all(fd, in_history, shm_cir_next); 395 } else if (shm_next_entry - rec_next_entry == num_cir_entries) { 396 /* All circular buffer is updated */ 397 lcore_port->num_entries += num_cir_entries; 398 rc = circular_buffer_padding_all(fd, in_history, shm_cir_next); 399 } else { 400 /* Part of circular buffer is updated */ 401 rec_cir_next = rec_next_entry & (num_cir_entries - 1); 402 403 if (shm_cir_next > rec_cir_next) { 404 /* Updates are not across circular buffer */ 405 lcore_port->num_entries += shm_cir_next - rec_cir_next; 406 rc = circular_buffer_padding_backward(fd, in_history, rec_cir_next, shm_cir_next); 407 } else { 408 /* Updates are across circular buffer */ 409 lcore_port->num_entries += num_cir_entries - rec_cir_next + shm_cir_next; 410 rc = circular_buffer_padding_across(fd, in_history, rec_cir_next, shm_cir_next); 411 } 412 } 413 414 out: 415 if (rc) { 416 return rc; 417 } 418 419 if (g_verbose) { 420 printf("Append %ju trace_entry for lcore %d\n", lcore_port->num_entries - rec_num_entries, 421 in_history->lcore); 422 } 423 424 /* Update tpoint_count info */ 425 memcpy(lcore_port->out_history, lcore_port->in_history, sizeof(struct spdk_trace_history)); 426 427 /* Update last_entry_tsc to align with appended entries */ 428 last_idx = lcore_trace_last_entry_idx(in_history, shm_cir_next); 429 lcore_port->last_entry_tsc = in_history->entries[last_idx].tsc; 430 lcore_port->rec_next_entry = shm_next_entry; 431 432 return rc; 433 } 434 435 static int 436 trace_files_aggregate(struct aggr_trace_record_ctx *ctx) 437 { 438 int flags = O_CREAT | O_EXCL | O_RDWR; 439 struct lcore_trace_record_ctx *lcore_port; 440 char copy_buff[TRACE_FILE_COPY_SIZE]; 441 uint64_t lcore_offsets[SPDK_TRACE_MAX_LCORE + 1]; 442 int rc, i; 443 ssize_t len = 0; 444 uint64_t current_offset; 445 uint64_t len_sum; 446 447 ctx->out_fd = open(ctx->out_file, flags, 0600); 448 if (ctx->out_fd < 0) { 449 fprintf(stderr, "Could not open aggregation file %s.\n", ctx->out_file); 450 return -1; 451 } 452 453 if (g_verbose) { 454 printf("Create trace file %s for output\n", ctx->out_file); 455 } 456 457 /* Write flags of histories into head of converged trace file, except num_entriess */ 458 rc = cont_write(ctx->out_fd, ctx->trace_histories, 459 sizeof(struct spdk_trace_histories) - sizeof(lcore_offsets)); 460 if (rc < 0) { 461 fprintf(stderr, "Failed to write trace header into trace file\n"); 462 goto out; 463 } 464 465 /* Update and append lcore offsets converged trace file */ 466 current_offset = sizeof(struct spdk_trace_flags); 467 for (i = 0; i < SPDK_TRACE_MAX_LCORE; i++) { 468 lcore_port = &ctx->lcore_ports[i]; 469 if (lcore_port->valid) { 470 lcore_offsets[i] = current_offset; 471 current_offset += spdk_get_trace_history_size(lcore_port->num_entries); 472 } else { 473 lcore_offsets[i] = 0; 474 } 475 } 476 lcore_offsets[SPDK_TRACE_MAX_LCORE] = current_offset; 477 478 rc = cont_write(ctx->out_fd, lcore_offsets, sizeof(lcore_offsets)); 479 if (rc < 0) { 480 fprintf(stderr, "Failed to write lcore offsets into trace file\n"); 481 goto out; 482 } 483 484 /* Append each lcore trace file into converged trace file */ 485 for (i = 0; i < SPDK_TRACE_MAX_LCORE; i++) { 486 lcore_port = &ctx->lcore_ports[i]; 487 488 if (!lcore_port->valid) { 489 continue; 490 } 491 492 lcore_port->out_history->num_entries = lcore_port->num_entries; 493 rc = cont_write(ctx->out_fd, lcore_port->out_history, sizeof(struct spdk_trace_history)); 494 if (rc < 0) { 495 fprintf(stderr, "Failed to write lcore trace header into trace file\n"); 496 goto out; 497 } 498 499 /* Move file offset to the start of trace_entries */ 500 rc = lseek(lcore_port->fd, 0, SEEK_SET); 501 if (rc != 0) { 502 fprintf(stderr, "Failed to lseek lcore trace file\n"); 503 goto out; 504 } 505 506 len_sum = 0; 507 while ((len = cont_read(lcore_port->fd, copy_buff, TRACE_FILE_COPY_SIZE)) > 0) { 508 len_sum += len; 509 rc = cont_write(ctx->out_fd, copy_buff, len); 510 if (rc != len) { 511 fprintf(stderr, "Failed to write lcore trace entries into trace file\n"); 512 goto out; 513 } 514 } 515 516 /* Clear rc so that the last cont_write() doesn't get interpreted as a failure. */ 517 rc = 0; 518 519 if (len_sum != lcore_port->num_entries * sizeof(struct spdk_trace_entry)) { 520 fprintf(stderr, "Len of lcore trace file doesn't match number of entries for lcore\n"); 521 } 522 } 523 524 printf("All lcores trace entries are aggregated into trace file %s\n", ctx->out_file); 525 526 out: 527 close(ctx->out_fd); 528 529 return rc; 530 } 531 532 static void 533 __shutdown_signal(int signo) 534 { 535 g_shutdown = true; 536 } 537 538 static int 539 setup_exit_signal_handler(void) 540 { 541 struct sigaction sigact; 542 int rc; 543 544 memset(&sigact, 0, sizeof(sigact)); 545 sigemptyset(&sigact.sa_mask); 546 /* Install the same handler for SIGINT and SIGTERM */ 547 sigact.sa_handler = __shutdown_signal; 548 549 rc = sigaction(SIGINT, &sigact, NULL); 550 if (rc < 0) { 551 fprintf(stderr, "sigaction(SIGINT) failed\n"); 552 553 return rc; 554 } 555 556 rc = sigaction(SIGTERM, &sigact, NULL); 557 if (rc < 0) { 558 fprintf(stderr, "sigaction(SIGTERM) failed\n"); 559 } 560 561 return rc; 562 } 563 564 static void 565 usage(void) 566 { 567 printf("\n%s is used to record all SPDK generated trace entries\n", g_exe_name); 568 printf("from SPDK trace shared-memory to specified file.\n\n"); 569 printf("usage:\n"); 570 printf(" %s <option>\n", g_exe_name); 571 printf(" option = '-q' to disable verbose mode\n"); 572 printf(" '-s' to specify spdk_trace shm name for a\n"); 573 printf(" currently running process\n"); 574 printf(" '-i' to specify the shared memory ID\n"); 575 printf(" '-p' to specify the trace PID\n"); 576 printf(" (one of -i or -p must be specified)\n"); 577 printf(" '-f' to specify output trace file name\n"); 578 printf(" '-h' to print usage information\n"); 579 } 580 581 int 582 main(int argc, char **argv) 583 { 584 const char *app_name = NULL; 585 const char *file_name = NULL; 586 int op; 587 char shm_name[64]; 588 int shm_id = -1, shm_pid = -1; 589 int rc = 0; 590 int i; 591 struct aggr_trace_record_ctx ctx = {}; 592 struct lcore_trace_record_ctx *lcore_port; 593 594 g_exe_name = argv[0]; 595 while ((op = getopt(argc, argv, "f:i:p:qs:h")) != -1) { 596 switch (op) { 597 case 'i': 598 shm_id = spdk_strtol(optarg, 10); 599 break; 600 case 'p': 601 shm_pid = spdk_strtol(optarg, 10); 602 break; 603 case 'q': 604 g_verbose = 0; 605 break; 606 case 's': 607 app_name = optarg; 608 break; 609 case 'f': 610 file_name = optarg; 611 break; 612 case 'h': 613 usage(); 614 exit(EXIT_SUCCESS); 615 default: 616 usage(); 617 exit(1); 618 } 619 } 620 621 if (file_name == NULL) { 622 fprintf(stderr, "-f must be specified\n"); 623 usage(); 624 exit(1); 625 } 626 627 if (app_name == NULL) { 628 fprintf(stderr, "-s must be specified\n"); 629 usage(); 630 exit(1); 631 } 632 633 if (shm_id == -1 && shm_pid == -1) { 634 fprintf(stderr, "-i or -p must be specified\n"); 635 usage(); 636 exit(1); 637 } 638 639 if (shm_id >= 0) { 640 snprintf(shm_name, sizeof(shm_name), "/%s_trace.%d", app_name, shm_id); 641 } else { 642 snprintf(shm_name, sizeof(shm_name), "/%s_trace.pid%d", app_name, shm_pid); 643 } 644 645 rc = setup_exit_signal_handler(); 646 if (rc) { 647 exit(1); 648 } 649 650 rc = input_trace_file_mmap(&ctx, shm_name); 651 if (rc) { 652 exit(1); 653 } 654 655 rc = output_trace_files_prepare(&ctx, file_name); 656 if (rc) { 657 exit(1); 658 } 659 660 printf("Start to poll trace shm file %s\n", shm_name); 661 while (!g_shutdown && rc == 0) { 662 for (i = 0; i < SPDK_TRACE_MAX_LCORE; i++) { 663 lcore_port = &ctx.lcore_ports[i]; 664 665 if (!lcore_port->valid) { 666 continue; 667 } 668 rc = lcore_trace_record(lcore_port); 669 if (rc) { 670 break; 671 } 672 } 673 } 674 675 if (rc) { 676 exit(1); 677 } 678 679 printf("Start to aggregate lcore trace files\n"); 680 rc = trace_files_aggregate(&ctx); 681 if (rc) { 682 exit(1); 683 } 684 685 /* Summary report */ 686 printf("TSC Rate: %ju\n", g_tsc_rate); 687 for (i = 0; i < SPDK_TRACE_MAX_LCORE; i++) { 688 lcore_port = &ctx.lcore_ports[i]; 689 690 if (lcore_port->num_entries == 0) { 691 continue; 692 } 693 694 printf("Port %ju trace entries for lcore (%d) in %ju usec\n", 695 lcore_port->num_entries, i, 696 (lcore_port->last_entry_tsc - lcore_port->first_entry_tsc) / g_utsc_rate); 697 698 } 699 700 munmap(ctx.trace_histories, g_histories_size); 701 close(ctx.shm_fd); 702 703 output_trace_files_finish(&ctx); 704 705 return 0; 706 } 707