2 * Blktrace record utility - Convert binary trace data into bunches of IOs
4 * Copyright (C) 2007 Alan D. Brunelle <Alan.Brunelle@hp.com>
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.
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.
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
21 static char build_date[] = __DATE__ " at "__TIME__;
29 #include <sys/param.h>
31 #include <sys/types.h>
34 #if !defined(_GNU_SOURCE)
44 * Per input file information
46 * @head: Used to link up on input_files
47 * @devnm: Device name portion of this input file
48 * @file_name: Fully qualified name for this input file
49 * @cpu: CPU that this file was collected on
50 * @ifd: Input file descriptor (when opened)
51 * @tpkts: Total number of packets processed.
54 struct list_head head;
55 char *devnm, *file_name;
61 * Per IO trace information
63 * @time: Time stamp when trace was emitted
64 * @sector: IO sector identifier
65 * @bytes: Number of bytes transferred
66 * @rw: Read (1) or write (0)
76 * Per output file information
79 * @vfp: Verbose output file
80 * @file_name: Fully qualified name for this file
81 * @vfn: Fully qualified name for this file
82 * @cur: Current IO bunch being collected
83 * @iip: Input file this is associated with
84 * @start_time: Start time of th ecurrent bunch
85 * @last_time: Time of last packet put in
86 * @bunches: Number of bunches processed
87 * @pkts: Number of packets stored in bunches
91 char *file_name, *vfn;
93 struct ifile_info *iip;
94 __u64 start_time, last_time, bunches, pkts;
97 int data_is_native; // Indicates whether to swap
98 static LIST_HEAD(input_files); // List of all input files
99 static char *idir = "."; // Input directory base
100 static char *odir = "."; // Output directory base
101 static char *obase = "replay"; // Output file base
102 static __u64 max_bunch_tm = (10 * 1000 * 1000); // 10 milliseconds
103 static __u64 max_pkts_per_bunch = 8; // Default # of pkts per bunch
104 static int verbose = 0; // Boolean: output stats
105 static int find_traces = 0; // Boolean: Find traces in dir
107 static char usage_str[] = \
109 "\t[ -d <dir> : --input-directory=<dir> ] Default: .\n" \
110 "\t[ -D <dir> : --output-directory=<dir>] Default: .\n" \
111 "\t[ -F : --find-traces ] Default: Off\n" \
112 "\t[ -h : --help ] Default: Off\n" \
113 "\t[ -m <nsec> : --max-bunch-time=<nsec> ] Default: 10 msec\n" \
114 "\t[ -M <pkts> : --max-pkts=<pkts> ] Default: 8\n" \
115 "\t[ -o <base> : --output-base=<base> ] Default: replay\n" \
116 "\t[ -v : --verbose ] Default: Off\n" \
117 "\t[ -V : --version ] Default: Off\n" \
118 "\t<dev>... Default: None\n" \
121 #define S_OPTS "d:D:Fhm:M:o:vV"
122 static struct option l_opts[] = {
124 .name = "input-directory",
125 .has_arg = required_argument,
130 .name = "output-directory",
131 .has_arg = required_argument,
136 .name = "find-traces",
137 .has_arg = no_argument,
143 .has_arg = no_argument,
148 .name = "max-bunch-time",
149 .has_arg = required_argument,
155 .has_arg = required_argument,
160 .name = "output-base",
161 .has_arg = required_argument,
167 .has_arg = no_argument,
173 .has_arg = no_argument,
183 #define ERR_SYSCALL 2
184 #define fatal(errstring, exitval, arg...) \
186 if (errstring) perror(errstring); \
187 fprintf(stderr, ##arg); \
193 * match - Return true if this trace is a proper QUEUE transaction
194 * @action: Action field from trace
196 static inline int match(__u32 action)
198 return ((action & 0xffff) == __BLK_TA_QUEUE) &&
199 (action & BLK_TC_ACT(BLK_TC_QUEUE));
203 * usage - Display usage string and version
205 static void usage(void)
207 fprintf(stderr, "Usage: btrecord -- version %s\n%s",
208 my_btversion, usage_str);
212 * write_file_hdr - Seek to and write btrecord file header
213 * @stream: Output file information
214 * @hdr: Header to write
216 static void write_file_hdr(struct io_stream *stream, struct io_file_hdr *hdr)
218 hdr->version = mk_btversion(btver_mjr, btver_mnr, btver_sub);
221 fprintf(stderr, "\t%s: %llx %llx %llx %llx\n",
223 (long long unsigned)hdr->version,
224 (long long unsigned)hdr->genesis,
225 (long long unsigned)hdr->nbunches,
226 (long long unsigned)hdr->total_pkts);
229 fseek(stream->ofp, 0, SEEK_SET);
230 if (fwrite(hdr, sizeof(*hdr), 1, stream->ofp) != 1) {
231 fatal(stream->file_name, ERR_SYSCALL, "Hdr write failed\n");
237 * io_bunch_create - Allocate & initialize an io_bunch
238 * @io_stream: IO stream being added to
239 * @pre_stall: Amount of time that this bunch should be delayed by
240 * @start_time: Records current start
242 static inline void io_bunch_create(struct io_stream *stream, __u64 start_time)
244 struct io_bunch *cur = malloc(sizeof(*cur));
246 memset(cur, 0, sizeof(*cur));
249 cur->hdr.time_stamp = stream->start_time = start_time;
255 * io_bunch_add - Add an IO to the current bunch of IOs
256 * @stream: Per-output file stream information
257 * @spec: IO trace specification
259 * Returns update bunch information
261 static void io_bunch_add(struct io_stream *stream, struct io_spec *spec)
263 struct io_bunch *cur = stream->cur;
264 struct io_pkt iop = {
265 .sector = spec->sector,
266 .nbytes = spec->bytes,
271 assert(cur->hdr.npkts < BT_MAX_PKTS);
272 assert(stream->last_time == 0 || stream->last_time <= spec->time);
274 cur->pkts[cur->hdr.npkts++] = iop; // Struct copy
275 stream->last_time = spec->time;
279 * rem_input_file - Release resources associated with an input file
280 * @iip: Per-input file information
282 static void rem_input_file(struct ifile_info *iip)
284 list_del(&iip->head);
287 free(iip->file_name);
293 * __add_input_file - Allocate and initialize per-input file structure
294 * @cpu: CPU for this file
295 * @devnm: Device name for this file
296 * @file_name: Fully qualifed input file name
298 static void __add_input_file(int cpu, char *devnm, char *file_name)
300 struct ifile_info *iip = malloc(sizeof(*iip));
305 iip->devnm = strdup(devnm);
306 iip->file_name = strdup(file_name);
307 iip->ifd = open(file_name, O_RDONLY);
309 fatal(file_name, ERR_ARGS, "Unable to open\n");
313 list_add_tail(&iip->head, &input_files);
317 * add_input_file - Set up the input file name
318 * @devnm: Device name to use
320 static void add_input_file(char *devnm)
325 __list_for_each(p, &input_files) {
326 struct ifile_info *iip = list_entry(p, struct ifile_info, head);
327 if (strcmp(iip->devnm, devnm) == 0)
331 for (cpu = 0; ; cpu++) {
332 char full_name[MAXPATHLEN];
334 sprintf(full_name, "%s/%s.blktrace.%d", idir, devnm, cpu);
335 if (access(full_name, R_OK) != 0)
338 __add_input_file(cpu, devnm, full_name);
343 fatal(NULL, ERR_ARGS, "No traces found for %s\n", devnm);
348 static void find_input_files(char *idir)
351 DIR *dir = opendir(idir);
354 fatal(idir, ERR_ARGS, "Unable to open %s\n", idir);
358 while ((ent = readdir(dir)) != NULL) {
359 char *p, *dsf = malloc(256);
361 if (strstr(ent->d_name, ".blktrace.") == NULL)
364 dsf = strdup(ent->d_name);
376 * handle_args - Parse passed in argument list
377 * @argc: Number of arguments in argv
378 * @argv: Arguments passed in
380 * Does rudimentary parameter verification as well.
382 void handle_args(int argc, char *argv[])
386 while ((c = getopt_long(argc, argv, S_OPTS, l_opts, NULL)) != -1) {
390 if (access(idir, R_OK | X_OK) != 0) {
391 fatal(idir, ERR_ARGS,
392 "Invalid input directory specified\n");
399 if (access(odir, R_OK | X_OK) != 0) {
400 fatal(odir, ERR_ARGS,
401 "Invalid output directory specified\n");
416 max_bunch_tm = (__u64)atoll(optarg);
417 if (max_bunch_tm < 1) {
418 fprintf(stderr, "Invalid bunch time %llu\n",
419 (unsigned long long)max_bunch_tm);
426 max_pkts_per_bunch = (__u64)atoll(optarg);
427 if (!((1 <= max_pkts_per_bunch) &&
428 (max_pkts_per_bunch < 513))) {
429 fprintf(stderr, "Invalid max pkts %llu\n",
430 (unsigned long long)max_pkts_per_bunch);
441 fprintf(stderr, "btrecord -- version %s\n",
443 fprintf(stderr, " Built on %s\n", build_date);
453 fatal(NULL, ERR_ARGS, "Invalid command line\n");
458 while (optind < argc)
459 add_input_file(argv[optind++]);
462 find_input_files(idir);
464 if (list_len(&input_files) == 0) {
465 fatal(NULL, ERR_ARGS, "Missing required input file name(s)\n");
471 * next_io - Retrieve next Q trace from input stream
472 * @iip: Per-input file information
473 * @spec: IO specifier for trace
475 * Returns 0 on end of file, 1 if valid data returned.
477 static int next_io(struct ifile_info *iip, struct io_spec *spec)
482 struct blk_io_trace t;
485 ret = read(iip->ifd, &t, sizeof(t));
487 fatal(iip->file_name, ERR_SYSCALL, "Read failed\n");
492 else if (ret < (ssize_t)sizeof(t)) {
493 fprintf(stderr, "WARNING: Short read on %s (%d)\n",
494 iip->file_name, (int)ret);
498 if (data_is_native == -1)
499 check_data_endianness(t.magic);
501 assert(data_is_native >= 0);
502 if (data_is_native) {
504 spec->sector = t.sector;
505 spec->bytes = t.bytes;
510 spec->time = be64_to_cpu(t.time);
511 spec->sector = be64_to_cpu(t.sector);
512 spec->bytes = be32_to_cpu(t.bytes);
513 action = be32_to_cpu(t.action);
514 pdu_len = be16_to_cpu(t.pdu_len);
521 ret = read(iip->ifd, buf, pdu_len);
523 fatal(iip->file_name, ERR_SYSCALL, "Read PDU failed\n");
526 else if (ret < (ssize_t)pdu_len) {
527 fprintf(stderr, "WARNING: Short PDU read on %s (%d)\n",
528 iip->file_name, (int)ret);
537 spec->rw = (action & BLK_TC_ACT(BLK_TC_READ)) ? 1 : 0;
539 fprintf(stderr, "%2d: %10llu+%10llu (%d) @ %10llx\n",
540 iip->cpu, (long long unsigned)spec->sector,
541 (long long unsigned)spec->bytes / 512LLU,
542 spec->rw, (long long unsigned)spec->time);
544 if (iip->genesis == 0) {
545 iip->genesis = spec->time;
547 fprintf(stderr, "\tSetting new genesis: %llx(%d)\n",
548 (long long unsigned)iip->genesis, iip->cpu);
550 else if (iip->genesis > spec->time)
551 fatal(NULL, ERR_SYSCALL,
552 "Time inversion? %llu ... %llu\n",
553 (long long unsigned )iip->genesis,
554 (long long unsigned )spec->time);
560 * bunch_output_hdr - Output bunch header
562 static inline void bunch_output_hdr(struct io_stream *stream)
564 struct io_bunch_hdr *hdrp = &stream->cur->hdr;
566 assert(0 < hdrp->npkts && hdrp->npkts <= BT_MAX_PKTS);
567 if (fwrite(hdrp, sizeof(struct io_bunch_hdr), 1, stream->ofp) != 1) {
568 fatal(stream->file_name, ERR_SYSCALL, "fwrite(hdr) failed\n");
573 __u64 off = hdrp->time_stamp - stream->iip->genesis;
576 fprintf(stream->vfp, "------------------\n");
577 fprintf(stream->vfp, "%4llu.%09llu %3llu\n",
578 (unsigned long long)off / (1000 * 1000 * 1000),
579 (unsigned long long)off % (1000 * 1000 * 1000),
580 (unsigned long long)hdrp->npkts);
581 fprintf(stream->vfp, "------------------\n");
586 * bunch_output_pkt - Output IO packets
588 static inline void bunch_output_pkts(struct io_stream *stream)
590 struct io_pkt *p = stream->cur->pkts;
591 size_t npkts = stream->cur->hdr.npkts;
593 assert(0 < npkts && npkts <= BT_MAX_PKTS);
594 if (fwrite(p, sizeof(struct io_pkt), npkts, stream->ofp) != npkts) {
595 fatal(stream->file_name, ERR_SYSCALL, "fwrite(pkts) failed\n");
603 for (i = 0; i < npkts; i++, p++)
604 fprintf(stream->vfp, "\t%1d %10llu\t%10llu\n",
606 (unsigned long long)p->sector,
607 (unsigned long long)p->nbytes / 512);
612 * stream_flush - Flush current bunch of IOs out to the output stream
613 * @stream: Per-output file stream information
615 static void stream_flush(struct io_stream *stream)
617 struct io_bunch *cur = stream->cur;
620 if (cur->hdr.npkts) {
621 assert(cur->hdr.npkts <= BT_MAX_PKTS);
622 bunch_output_hdr(stream);
623 bunch_output_pkts(stream);
626 stream->pkts += cur->hdr.npkts;
633 * bunch_done - Returns true if current bunch is either full, or next IO is late
634 * @stream: Output stream information
635 * @spec: IO trace specification
637 static inline int bunch_done(struct io_stream *stream, struct io_spec *spec)
639 if (stream->cur->hdr.npkts >= max_pkts_per_bunch)
642 if ((spec->time - stream->start_time) > max_bunch_tm)
649 * stream_add_io - Add an IO trace to the current stream
650 * @stream: Output stream information
651 * @spec: IO trace specification
653 static void stream_add_io(struct io_stream *stream, struct io_spec *spec)
656 if (stream->cur == NULL)
657 io_bunch_create(stream, spec->time);
658 else if (bunch_done(stream, spec)) {
659 stream_flush(stream);
660 io_bunch_create(stream, spec->time);
663 io_bunch_add(stream, spec);
667 * stream_open - Open output stream for specified input stream
668 * @iip: Per-input file information
670 static struct io_stream *stream_open(struct ifile_info *iip)
672 char ofile_name[MAXPATHLEN];
673 struct io_stream *stream = malloc(sizeof(*stream));
674 struct io_file_hdr io_file_hdr = {
680 memset(stream, 0, sizeof(*stream));
682 sprintf(ofile_name, "%s/%s.%s.%d", odir, iip->devnm, obase, iip->cpu);
683 stream->ofp = fopen(ofile_name, "w");
685 fatal(ofile_name, ERR_SYSCALL, "Open failed\n");
691 stream->bunches = stream->pkts = 0;
692 stream->last_time = 0;
693 stream->file_name = strdup(ofile_name);
695 write_file_hdr(stream, &io_file_hdr);
698 char vfile_name[MAXPATHLEN];
700 sprintf(vfile_name, "%s/%s.%s.%d.rec", odir, iip->devnm,
702 stream->vfp = fopen(vfile_name, "w");
704 fatal(vfile_name, ERR_SYSCALL, "Open failed\n");
708 stream->vfn = strdup(vfile_name);
716 * stream_close - Release resources associated with an output stream
717 * @stream: Stream to release
719 static void stream_close(struct io_stream *stream)
721 struct io_file_hdr io_file_hdr = {
722 .genesis = stream->iip->genesis,
723 .nbunches = stream->bunches,
724 .total_pkts = stream->pkts
727 stream_flush(stream);
728 write_file_hdr(stream, &io_file_hdr);
731 if (verbose && stream->bunches) {
733 "%s:%d: %llu pkts (tot), %llu pkts (replay), "
734 "%llu bunches, %.1lf pkts/bunch\n",
735 stream->iip->devnm, stream->iip->cpu,
736 (unsigned long long)stream->iip->tpkts,
737 (unsigned long long)stream->pkts,
738 (unsigned long long)stream->bunches,
739 (double)(stream->pkts) / (double)(stream->bunches));
745 free(stream->file_name);
750 * process - Process one input file to an output file
751 * @iip: Per-input file information
753 static void process(struct ifile_info *iip)
756 struct io_stream *stream;
758 stream = stream_open(iip);
759 while (next_io(iip, &spec))
760 stream_add_io(stream, &spec);
761 stream_close(stream);
768 * @argc: Number of arguments
769 * @argv: Array of arguments
771 int main(int argc, char *argv[])
773 struct list_head *p, *q;
775 handle_args(argc, argv);
776 list_for_each_safe(p, q, &input_files)
777 process(list_entry(p, struct ifile_info, head));