• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Blktrace record utility - Convert binary trace data into bunches of IOs
3  *
4  * Copyright (C) 2007 Alan D. Brunelle <Alan.Brunelle@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
fatal(const char * errstring,const int exitval,const char * fmt,...)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  */
match(__u32 action)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  */
usage(void)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  */
write_file_hdr(struct io_stream * stream,struct io_file_hdr * hdr)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  */
io_bunch_create(struct io_stream * stream,__u64 start_time)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  */
io_bunch_add(struct io_stream * stream,struct io_spec * spec)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  */
rem_input_file(struct ifile_info * iip)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  */
__add_input_file(int cpu,char * devnm,char * file_name)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  */
add_input_file(char * devnm)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 
find_input_files(char * idir)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  */
handle_args(int argc,char * argv[])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  */
next_io(struct ifile_info * iip,struct io_spec * spec)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  */
bunch_output_hdr(struct io_stream * stream)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  */
bunch_output_pkts(struct io_stream * stream)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  */
stream_flush(struct io_stream * stream)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  */
bunch_done(struct io_stream * stream,struct io_spec * spec)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  */
stream_add_io(struct io_stream * stream,struct io_spec * spec)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  */
stream_open(struct ifile_info * iip)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  */
stream_close(struct io_stream * stream)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  */
process(struct ifile_info * iip)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  */
main(int argc,char * argv[])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