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