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