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