Home | History | Annotate | Download | only in btreplay
      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