1 /* 2 * Blktrace record utility - Convert binary trace data into bunches of IOs 3 * 4 * Copyright (C) 2007 Alan D. Brunelle <Alan.Brunelle (at) hp.com> 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation; either version 2 of the License, or 9 * (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program; if not, write to the Free Software 18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 */ 20 21 #include <assert.h> 22 #include <fcntl.h> 23 #include <stdio.h> 24 #include <stdlib.h> 25 #include <string.h> 26 #include <unistd.h> 27 #include <sys/param.h> 28 #include <sys/stat.h> 29 #include <sys/types.h> 30 #include <dirent.h> 31 #include <stdarg.h> 32 33 #if !defined(_GNU_SOURCE) 34 # define _GNU_SOURCE 35 #endif 36 #include <getopt.h> 37 38 #include "list.h" 39 #include "btrecord.h" 40 #include "blktrace.h" 41 42 /* 43 * Per input file information 44 * 45 * @head: Used to link up on input_files 46 * @devnm: Device name portion of this input file 47 * @file_name: Fully qualified name for this input file 48 * @cpu: CPU that this file was collected on 49 * @ifd: Input file descriptor (when opened) 50 * @tpkts: Total number of packets processed. 51 */ 52 struct ifile_info { 53 struct list_head head; 54 char *devnm, *file_name; 55 int cpu, ifd; 56 __u64 tpkts, genesis; 57 }; 58 59 /* 60 * Per IO trace information 61 * 62 * @time: Time stamp when trace was emitted 63 * @sector: IO sector identifier 64 * @bytes: Number of bytes transferred 65 * @rw: Read (1) or write (0) 66 */ 67 struct io_spec { 68 __u64 time; 69 __u64 sector; 70 __u32 bytes; 71 int rw; 72 }; 73 74 /* 75 * Per output file information 76 * 77 * @ofp: Output file 78 * @vfp: Verbose output file 79 * @file_name: Fully qualified name for this file 80 * @vfn: Fully qualified name for this file 81 * @cur: Current IO bunch being collected 82 * @iip: Input file this is associated with 83 * @start_time: Start time of th ecurrent bunch 84 * @last_time: Time of last packet put in 85 * @bunches: Number of bunches processed 86 * @pkts: Number of packets stored in bunches 87 */ 88 struct io_stream { 89 FILE *ofp, *vfp; 90 char *file_name, *vfn; 91 struct io_bunch *cur; 92 struct ifile_info *iip; 93 __u64 start_time, last_time, bunches, pkts; 94 }; 95 96 int data_is_native; // Indicates whether to swap 97 static LIST_HEAD(input_files); // List of all input files 98 static char *idir = "."; // Input directory base 99 static char *odir = "."; // Output directory base 100 static char *obase = "replay"; // Output file base 101 static __u64 max_bunch_tm = (10 * 1000 * 1000); // 10 milliseconds 102 static __u64 max_pkts_per_bunch = 8; // Default # of pkts per bunch 103 static int verbose = 0; // Boolean: output stats 104 static int find_traces = 0; // Boolean: Find traces in dir 105 106 static char usage_str[] = \ 107 "\n" \ 108 "\t[ -d <dir> : --input-directory=<dir> ] Default: .\n" \ 109 "\t[ -D <dir> : --output-directory=<dir>] Default: .\n" \ 110 "\t[ -F : --find-traces ] Default: Off\n" \ 111 "\t[ -h : --help ] Default: Off\n" \ 112 "\t[ -m <nsec> : --max-bunch-time=<nsec> ] Default: 10 msec\n" \ 113 "\t[ -M <pkts> : --max-pkts=<pkts> ] Default: 8\n" \ 114 "\t[ -o <base> : --output-base=<base> ] Default: replay\n" \ 115 "\t[ -v : --verbose ] Default: Off\n" \ 116 "\t[ -V : --version ] Default: Off\n" \ 117 "\t<dev>... Default: None\n" \ 118 "\n"; 119 120 #define S_OPTS "d:D:Fhm:M:o:vV" 121 static struct option l_opts[] = { 122 { 123 .name = "input-directory", 124 .has_arg = required_argument, 125 .flag = NULL, 126 .val = 'd' 127 }, 128 { 129 .name = "output-directory", 130 .has_arg = required_argument, 131 .flag = NULL, 132 .val = 'D' 133 }, 134 { 135 .name = "find-traces", 136 .has_arg = no_argument, 137 .flag = NULL, 138 .val = 'F' 139 }, 140 { 141 .name = "help", 142 .has_arg = no_argument, 143 .flag = NULL, 144 .val = 'h' 145 }, 146 { 147 .name = "max-bunch-time", 148 .has_arg = required_argument, 149 .flag = NULL, 150 .val = 'm' 151 }, 152 { 153 .name = "max-pkts", 154 .has_arg = required_argument, 155 .flag = NULL, 156 .val = 'M' 157 }, 158 { 159 .name = "output-base", 160 .has_arg = required_argument, 161 .flag = NULL, 162 .val = 'o' 163 }, 164 { 165 .name = "verbose", 166 .has_arg = no_argument, 167 .flag = NULL, 168 .val = 'v' 169 }, 170 { 171 .name = "version", 172 .has_arg = no_argument, 173 .flag = NULL, 174 .val = 'V' 175 }, 176 { 177 .name = NULL 178 } 179 }; 180 181 #define ERR_ARGS 1 182 #define ERR_SYSCALL 2 183 static inline void fatal(const char *errstring, const int exitval, 184 const char *fmt, ...) 185 { 186 va_list ap; 187 188 if (errstring) 189 perror(errstring); 190 191 va_start(ap, fmt); 192 vfprintf(stderr, fmt, ap); 193 va_end(ap); 194 195 exit(exitval); 196 /*NOTREACHED*/ 197 } 198 199 /** 200 * match - Return true if this trace is a proper QUEUE transaction 201 * @action: Action field from trace 202 */ 203 static inline int match(__u32 action) 204 { 205 return ((action & 0xffff) == __BLK_TA_QUEUE) && 206 (action & BLK_TC_ACT(BLK_TC_QUEUE)); 207 } 208 209 /** 210 * usage - Display usage string and version 211 */ 212 static void usage(void) 213 { 214 fprintf(stderr, "Usage: btrecord -- version %s\n%s", 215 my_btversion, usage_str); 216 } 217 218 /** 219 * write_file_hdr - Seek to and write btrecord file header 220 * @stream: Output file information 221 * @hdr: Header to write 222 */ 223 static void write_file_hdr(struct io_stream *stream, struct io_file_hdr *hdr) 224 { 225 hdr->version = mk_btversion(btver_mjr, btver_mnr, btver_sub); 226 227 if (verbose) { 228 fprintf(stderr, "\t%s: %llx %llx %llx %llx\n", 229 stream->file_name, 230 (long long unsigned)hdr->version, 231 (long long unsigned)hdr->genesis, 232 (long long unsigned)hdr->nbunches, 233 (long long unsigned)hdr->total_pkts); 234 } 235 236 fseek(stream->ofp, 0, SEEK_SET); 237 if (fwrite(hdr, sizeof(*hdr), 1, stream->ofp) != 1) { 238 fatal(stream->file_name, ERR_SYSCALL, "Hdr write failed\n"); 239 /*NOTREACHED*/ 240 } 241 } 242 243 /** 244 * io_bunch_create - Allocate & initialize an io_bunch 245 * @io_stream: IO stream being added to 246 * @pre_stall: Amount of time that this bunch should be delayed by 247 * @start_time: Records current start 248 */ 249 static inline void io_bunch_create(struct io_stream *stream, __u64 start_time) 250 { 251 struct io_bunch *cur = malloc(sizeof(*cur)); 252 253 memset(cur, 0, sizeof(*cur)); 254 255 cur->hdr.npkts = 0; 256 cur->hdr.time_stamp = stream->start_time = start_time; 257 258 stream->cur = cur; 259 } 260 261 /** 262 * io_bunch_add - Add an IO to the current bunch of IOs 263 * @stream: Per-output file stream information 264 * @spec: IO trace specification 265 * 266 * Returns update bunch information 267 */ 268 static void io_bunch_add(struct io_stream *stream, struct io_spec *spec) 269 { 270 struct io_bunch *cur = stream->cur; 271 struct io_pkt iop = { 272 .sector = spec->sector, 273 .nbytes = spec->bytes, 274 .rw = spec->rw 275 }; 276 277 assert(cur != NULL); 278 assert(cur->hdr.npkts < BT_MAX_PKTS); 279 assert(stream->last_time == 0 || stream->last_time <= spec->time); 280 281 cur->pkts[cur->hdr.npkts++] = iop; // Struct copy 282 stream->last_time = spec->time; 283 } 284 285 /** 286 * rem_input_file - Release resources associated with an input file 287 * @iip: Per-input file information 288 */ 289 static void rem_input_file(struct ifile_info *iip) 290 { 291 list_del(&iip->head); 292 293 close(iip->ifd); 294 free(iip->file_name); 295 free(iip->devnm); 296 free(iip); 297 } 298 299 /** 300 * __add_input_file - Allocate and initialize per-input file structure 301 * @cpu: CPU for this file 302 * @devnm: Device name for this file 303 * @file_name: Fully qualifed input file name 304 */ 305 static void __add_input_file(int cpu, char *devnm, char *file_name) 306 { 307 struct ifile_info *iip = malloc(sizeof(*iip)); 308 309 iip->cpu = cpu; 310 iip->tpkts = 0; 311 iip->genesis = 0; 312 iip->devnm = strdup(devnm); 313 iip->file_name = strdup(file_name); 314 iip->ifd = open(file_name, O_RDONLY); 315 if (iip->ifd < 0) { 316 fatal(file_name, ERR_ARGS, "Unable to open\n"); 317 /*NOTREACHED*/ 318 } 319 320 list_add_tail(&iip->head, &input_files); 321 } 322 323 /** 324 * add_input_file - Set up the input file name 325 * @devnm: Device name to use 326 */ 327 static void add_input_file(char *devnm) 328 { 329 struct list_head *p; 330 int cpu, found = 0; 331 332 __list_for_each(p, &input_files) { 333 struct ifile_info *iip = list_entry(p, struct ifile_info, head); 334 if (strcmp(iip->devnm, devnm) == 0) 335 return; 336 } 337 338 for (cpu = 0; ; cpu++) { 339 char full_name[MAXPATHLEN]; 340 341 sprintf(full_name, "%s/%s.blktrace.%d", idir, devnm, cpu); 342 if (access(full_name, R_OK) != 0) 343 break; 344 345 __add_input_file(cpu, devnm, full_name); 346 found++; 347 } 348 349 if (!found) { 350 fatal(NULL, ERR_ARGS, "No traces found for %s\n", devnm); 351 /*NOTREACHED*/ 352 } 353 } 354 355 static void find_input_files(char *idir) 356 { 357 struct dirent *ent; 358 DIR *dir = opendir(idir); 359 360 if (dir == NULL) { 361 fatal(idir, ERR_ARGS, "Unable to open %s\n", idir); 362 /*NOTREACHED*/ 363 } 364 365 while ((ent = readdir(dir)) != NULL) { 366 char *p, *dsf; 367 368 if (strstr(ent->d_name, ".blktrace.") == NULL) 369 continue; 370 371 dsf = strdup(ent->d_name); 372 p = index(dsf, '.'); 373 assert(p != NULL); 374 *p = '\0'; 375 add_input_file(dsf); 376 free(dsf); 377 } 378 379 closedir(dir); 380 } 381 382 /** 383 * handle_args - Parse passed in argument list 384 * @argc: Number of arguments in argv 385 * @argv: Arguments passed in 386 * 387 * Does rudimentary parameter verification as well. 388 */ 389 void handle_args(int argc, char *argv[]) 390 { 391 int c; 392 393 while ((c = getopt_long(argc, argv, S_OPTS, l_opts, NULL)) != -1) { 394 switch (c) { 395 case 'd': 396 idir = optarg; 397 if (access(idir, R_OK | X_OK) != 0) { 398 fatal(idir, ERR_ARGS, 399 "Invalid input directory specified\n"); 400 /*NOTREACHED*/ 401 } 402 break; 403 404 case 'D': 405 odir = optarg; 406 if (access(odir, R_OK | X_OK) != 0) { 407 fatal(odir, ERR_ARGS, 408 "Invalid output directory specified\n"); 409 /*NOTREACHED*/ 410 } 411 break; 412 413 case 'F': 414 find_traces = 1; 415 break; 416 417 case 'h': 418 usage(); 419 exit(0); 420 /*NOTREACHED*/ 421 422 case 'm': 423 max_bunch_tm = (__u64)atoll(optarg); 424 if (max_bunch_tm < 1) { 425 fprintf(stderr, "Invalid bunch time %llu\n", 426 (unsigned long long)max_bunch_tm); 427 exit(ERR_ARGS); 428 /*NOTREACHED*/ 429 } 430 break; 431 432 case 'M': 433 max_pkts_per_bunch = (__u64)atoll(optarg); 434 if (!((1 <= max_pkts_per_bunch) && 435 (max_pkts_per_bunch < 513))) { 436 fprintf(stderr, "Invalid max pkts %llu\n", 437 (unsigned long long)max_pkts_per_bunch); 438 exit(ERR_ARGS); 439 /*NOTREACHED*/ 440 } 441 break; 442 443 case 'o': 444 obase = optarg; 445 break; 446 447 case 'V': 448 fprintf(stderr, "btrecord -- version %s\n", 449 my_btversion); 450 exit(0); 451 /*NOTREACHED*/ 452 453 case 'v': 454 verbose++; 455 break; 456 457 default: 458 usage(); 459 fatal(NULL, ERR_ARGS, "Invalid command line\n"); 460 /*NOTREACHED*/ 461 } 462 } 463 464 while (optind < argc) 465 add_input_file(argv[optind++]); 466 467 if (find_traces) 468 find_input_files(idir); 469 470 if (list_len(&input_files) == 0) { 471 fatal(NULL, ERR_ARGS, "Missing required input file name(s)\n"); 472 /*NOTREACHED*/ 473 } 474 } 475 476 /** 477 * next_io - Retrieve next Q trace from input stream 478 * @iip: Per-input file information 479 * @spec: IO specifier for trace 480 * 481 * Returns 0 on end of file, 1 if valid data returned. 482 */ 483 static int next_io(struct ifile_info *iip, struct io_spec *spec) 484 { 485 ssize_t ret; 486 __u32 action; 487 __u16 pdu_len; 488 struct blk_io_trace t; 489 490 again: 491 ret = read(iip->ifd, &t, sizeof(t)); 492 if (ret < 0) { 493 fatal(iip->file_name, ERR_SYSCALL, "Read failed\n"); 494 /*NOTREACHED*/ 495 } 496 else if (ret == 0) 497 return 0; 498 else if (ret < (ssize_t)sizeof(t)) { 499 fprintf(stderr, "WARNING: Short read on %s (%d)\n", 500 iip->file_name, (int)ret); 501 return 0; 502 } 503 504 if (data_is_native == -1) 505 check_data_endianness(t.magic); 506 507 assert(data_is_native >= 0); 508 if (data_is_native) { 509 spec->time = t.time; 510 spec->sector = t.sector; 511 spec->bytes = t.bytes; 512 action = t.action; 513 pdu_len = t.pdu_len; 514 } 515 else { 516 spec->time = be64_to_cpu(t.time); 517 spec->sector = be64_to_cpu(t.sector); 518 spec->bytes = be32_to_cpu(t.bytes); 519 action = be32_to_cpu(t.action); 520 pdu_len = be16_to_cpu(t.pdu_len); 521 } 522 523 524 if (pdu_len) { 525 char buf[pdu_len]; 526 527 ret = read(iip->ifd, buf, pdu_len); 528 if (ret < 0) { 529 fatal(iip->file_name, ERR_SYSCALL, "Read PDU failed\n"); 530 /*NOTREACHED*/ 531 } 532 else if (ret < (ssize_t)pdu_len) { 533 fprintf(stderr, "WARNING: Short PDU read on %s (%d)\n", 534 iip->file_name, (int)ret); 535 return 0; 536 } 537 } 538 539 iip->tpkts++; 540 if (!match(action)) 541 goto again; 542 543 spec->rw = (action & BLK_TC_ACT(BLK_TC_READ)) ? 1 : 0; 544 if (verbose > 1) 545 fprintf(stderr, "%2d: %10llu+%10llu (%d) @ %10llx\n", 546 iip->cpu, (long long unsigned)spec->sector, 547 (long long unsigned)spec->bytes / 512LLU, 548 spec->rw, (long long unsigned)spec->time); 549 550 if (iip->genesis == 0) { 551 iip->genesis = spec->time; 552 if (verbose > 1) 553 fprintf(stderr, "\tSetting new genesis: %llx(%d)\n", 554 (long long unsigned)iip->genesis, iip->cpu); 555 } 556 else if (iip->genesis > spec->time) 557 fatal(NULL, ERR_SYSCALL, 558 "Time inversion? %llu ... %llu\n", 559 (long long unsigned )iip->genesis, 560 (long long unsigned )spec->time); 561 562 return 1; 563 } 564 565 /** 566 * bunch_output_hdr - Output bunch header 567 */ 568 static inline void bunch_output_hdr(struct io_stream *stream) 569 { 570 struct io_bunch_hdr *hdrp = &stream->cur->hdr; 571 572 assert(0 < hdrp->npkts && hdrp->npkts <= BT_MAX_PKTS); 573 if (fwrite(hdrp, sizeof(struct io_bunch_hdr), 1, stream->ofp) != 1) { 574 fatal(stream->file_name, ERR_SYSCALL, "fwrite(hdr) failed\n"); 575 /*NOTREACHED*/ 576 } 577 578 if (verbose) { 579 __u64 off = hdrp->time_stamp - stream->iip->genesis; 580 581 assert(stream->vfp); 582 fprintf(stream->vfp, "------------------\n"); 583 fprintf(stream->vfp, "%4llu.%09llu %3llu\n", 584 (unsigned long long)off / (1000 * 1000 * 1000), 585 (unsigned long long)off % (1000 * 1000 * 1000), 586 (unsigned long long)hdrp->npkts); 587 fprintf(stream->vfp, "------------------\n"); 588 } 589 } 590 591 /** 592 * bunch_output_pkt - Output IO packets 593 */ 594 static inline void bunch_output_pkts(struct io_stream *stream) 595 { 596 struct io_pkt *p = stream->cur->pkts; 597 size_t npkts = stream->cur->hdr.npkts; 598 599 assert(0 < npkts && npkts <= BT_MAX_PKTS); 600 if (fwrite(p, sizeof(struct io_pkt), npkts, stream->ofp) != npkts) { 601 fatal(stream->file_name, ERR_SYSCALL, "fwrite(pkts) failed\n"); 602 /*NOTREACHED*/ 603 } 604 605 if (verbose) { 606 size_t i; 607 608 assert(stream->vfp); 609 for (i = 0; i < npkts; i++, p++) 610 fprintf(stream->vfp, "\t%1d %10llu\t%10llu\n", 611 p->rw, 612 (unsigned long long)p->sector, 613 (unsigned long long)p->nbytes / 512); 614 } 615 } 616 617 /** 618 * stream_flush - Flush current bunch of IOs out to the output stream 619 * @stream: Per-output file stream information 620 */ 621 static void stream_flush(struct io_stream *stream) 622 { 623 struct io_bunch *cur = stream->cur; 624 625 if (cur) { 626 if (cur->hdr.npkts) { 627 assert(cur->hdr.npkts <= BT_MAX_PKTS); 628 bunch_output_hdr(stream); 629 bunch_output_pkts(stream); 630 631 stream->bunches++; 632 stream->pkts += cur->hdr.npkts; 633 } 634 free(cur); 635 } 636 } 637 638 /** 639 * bunch_done - Returns true if current bunch is either full, or next IO is late 640 * @stream: Output stream information 641 * @spec: IO trace specification 642 */ 643 static inline int bunch_done(struct io_stream *stream, struct io_spec *spec) 644 { 645 if (stream->cur->hdr.npkts >= max_pkts_per_bunch) 646 return 1; 647 648 if ((spec->time - stream->start_time) > max_bunch_tm) 649 return 1; 650 651 return 0; 652 } 653 654 /** 655 * stream_add_io - Add an IO trace to the current stream 656 * @stream: Output stream information 657 * @spec: IO trace specification 658 */ 659 static void stream_add_io(struct io_stream *stream, struct io_spec *spec) 660 { 661 662 if (stream->cur == NULL) 663 io_bunch_create(stream, spec->time); 664 else if (bunch_done(stream, spec)) { 665 stream_flush(stream); 666 io_bunch_create(stream, spec->time); 667 } 668 669 io_bunch_add(stream, spec); 670 } 671 672 /** 673 * stream_open - Open output stream for specified input stream 674 * @iip: Per-input file information 675 */ 676 static struct io_stream *stream_open(struct ifile_info *iip) 677 { 678 char ofile_name[MAXPATHLEN]; 679 struct io_stream *stream = malloc(sizeof(*stream)); 680 struct io_file_hdr io_file_hdr = { 681 .genesis = 0, 682 .nbunches = 0, 683 .total_pkts = 0 684 }; 685 686 memset(stream, 0, sizeof(*stream)); 687 688 sprintf(ofile_name, "%s/%s.%s.%d", odir, iip->devnm, obase, iip->cpu); 689 stream->ofp = fopen(ofile_name, "w"); 690 if (!stream->ofp) { 691 fatal(ofile_name, ERR_SYSCALL, "Open failed\n"); 692 /*NOTREACHED*/ 693 } 694 695 stream->iip = iip; 696 stream->cur = NULL; 697 stream->bunches = stream->pkts = 0; 698 stream->last_time = 0; 699 stream->file_name = strdup(ofile_name); 700 701 write_file_hdr(stream, &io_file_hdr); 702 703 if (verbose) { 704 char vfile_name[MAXPATHLEN]; 705 706 sprintf(vfile_name, "%s/%s.%s.%d.rec", odir, iip->devnm, 707 obase, iip->cpu); 708 stream->vfp = fopen(vfile_name, "w"); 709 if (!stream->vfp) { 710 fatal(vfile_name, ERR_SYSCALL, "Open failed\n"); 711 /*NOTREACHED*/ 712 } 713 714 stream->vfn = strdup(vfile_name); 715 } 716 717 data_is_native = -1; 718 return stream; 719 } 720 721 /** 722 * stream_close - Release resources associated with an output stream 723 * @stream: Stream to release 724 */ 725 static void stream_close(struct io_stream *stream) 726 { 727 struct io_file_hdr io_file_hdr = { 728 .genesis = stream->iip->genesis, 729 .nbunches = stream->bunches, 730 .total_pkts = stream->pkts 731 }; 732 733 stream_flush(stream); 734 write_file_hdr(stream, &io_file_hdr); 735 fclose(stream->ofp); 736 737 if (verbose && stream->bunches) { 738 fprintf(stderr, 739 "%s:%d: %llu pkts (tot), %llu pkts (replay), " 740 "%llu bunches, %.1lf pkts/bunch\n", 741 stream->iip->devnm, stream->iip->cpu, 742 (unsigned long long)stream->iip->tpkts, 743 (unsigned long long)stream->pkts, 744 (unsigned long long)stream->bunches, 745 (double)(stream->pkts) / (double)(stream->bunches)); 746 747 fclose(stream->vfp); 748 free(stream->vfn); 749 } 750 751 free(stream->file_name); 752 free(stream); 753 } 754 755 /** 756 * process - Process one input file to an output file 757 * @iip: Per-input file information 758 */ 759 static void process(struct ifile_info *iip) 760 { 761 struct io_spec spec; 762 struct io_stream *stream; 763 764 stream = stream_open(iip); 765 while (next_io(iip, &spec)) 766 stream_add_io(stream, &spec); 767 stream_close(stream); 768 769 rem_input_file(iip); 770 } 771 772 /** 773 * main - 774 * @argc: Number of arguments 775 * @argv: Array of arguments 776 */ 777 int main(int argc, char *argv[]) 778 { 779 struct list_head *p, *q; 780 781 handle_args(argc, argv); 782 list_for_each_safe(p, q, &input_files) 783 process(list_entry(p, struct ifile_info, head)); 784 785 return 0; 786 } 787