summaryrefslogtreecommitdiff
path: root/btreplay/btrecord.c
diff options
context:
space:
mode:
authorAlan D. Brunelle <Alan.Brunelle@hp.com>2007-10-02 12:35:07 -0400
committerJens Axboe <jens.axboe@oracle.com>2007-10-02 19:51:18 +0200
commitd47a3fec3f4bbcf6b0c6ef757a4eb449dd81d10a (patch)
treec23a7df0ca04c624a5d291d5ab4a96ff29a5a015 /btreplay/btrecord.c
parent4f93192893f41acfe1ded673c1111142b8f4cddd (diff)
downloadblktrace-d47a3fec3f4bbcf6b0c6ef757a4eb449dd81d10a.tar.gz
blktrace-d47a3fec3f4bbcf6b0c6ef757a4eb449dd81d10a.tar.bz2
Add btrecord/btreplay capability
These facilities allow one to attempt to replay a stream of IOs captured with blktrace. The general workflow is: 1. Initiate blktrace to capture traces 2. Do whatever to generate initial IO stream... 3. Stop blktrace 4. Run btrecord to convert traces into IO records 5. Run btreplay to replay IOs The IO stream characteristics during replay will try to respect the following characteristics of the original IO stream: 1. The IOs will target the same device(s) as originally seen. [One can alter this behavior by specifyin the -M option to btreplay, which allows one to remap IOs slated to one set of devices to a specified other set of devices.] 2. IO direction: the IOs will follow the same read/write (from-device/to-device) characteristics of the originating flow. [Note: By default replay will /not/ do writes, one must specify the -W option to do this. THis is a meager attempt to stop someone from shooting themselves in the foot (with a very large-caliber weapon).] 3. IO offset & size are maintained. 4. CPU: IOs are submitted on the originating CPU whenever possible. [Note: Since we are using asynchronous IO, IOs may be routed to another CPU prior to being processed by the block IO layer.] In order to try and replicate inter-IO timing as much as possible, btrecord will combine IOs "close in time" into one set, or bunch, of IOs. Then btreplay will replay all the IOs in one go (via asynchronous direct IO - io_submit). The size of the bunches are configurable via the -m flag to btrecord (which specifies the a time-based bunch size) and/or the -M flag (which specifies the maximum amount of IOs to put into a bunch). At the low-end, specifying '-M 1' instructs btrecord to act like fio - replay each IO as an individual unit. Besides the potential to remap devices (utilizing the -M option to replay, as noted above), one can also limit the number of CPUs on the replay machine - so if you have fewer CPUs on the replay machine you specify the -c option to btreplay. Lastly, one can specify the -N option to btreplay to instruct it to ignore inter-IO (inter-bunch of IOs) timings. Thus, this instructs btreplay to replay the bunches as fast as possible, ignoring the original delays between original IOs. The utilities include a write-up in the docs directory. Signed-off-by: Alan D. Brunelle <Alan.Brunelle@hp.com> Signed-off-by: Jens Axboe <jens.axboe@oracle.com>
Diffstat (limited to 'btreplay/btrecord.c')
-rw-r--r--btreplay/btrecord.c780
1 files changed, 780 insertions, 0 deletions
diff --git a/btreplay/btrecord.c b/btreplay/btrecord.c
new file mode 100644
index 0000000..e02c153
--- /dev/null
+++ b/btreplay/btrecord.c
@@ -0,0 +1,780 @@
+/*
+ * Blktrace record utility - Convert binary trace data into bunches of IOs
+ *
+ * Copyright (C) 2007 Alan D. Brunelle <Alan.Brunelle@hp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+static char build_date[] = __DATE__ " at "__TIME__;
+
+#include <assert.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <dirent.h>
+
+#if !defined(_GNU_SOURCE)
+# define _GNU_SOURCE
+#endif
+#include <getopt.h>
+
+#include "list.h"
+#include "btrecord.h"
+#include "blktrace.h"
+
+/*
+ * Per input file information
+ *
+ * @head: Used to link up on input_files
+ * @devnm: Device name portion of this input file
+ * @file_name: Fully qualified name for this input file
+ * @cpu: CPU that this file was collected on
+ * @ifd: Input file descriptor (when opened)
+ * @tpkts: Total number of packets processed.
+ */
+struct ifile_info {
+ struct list_head head;
+ char *devnm, *file_name;
+ int cpu, ifd;
+ __u64 tpkts, genesis;
+};
+
+/*
+ * Per IO trace information
+ *
+ * @time: Time stamp when trace was emitted
+ * @sector: IO sector identifier
+ * @bytes: Number of bytes transferred
+ * @rw: Read (1) or write (0)
+ */
+struct io_spec {
+ __u64 time;
+ __u64 sector;
+ __u32 bytes;
+ int rw;
+};
+
+/*
+ * Per output file information
+ *
+ * @ofp: Output file
+ * @vfp: Verbose output file
+ * @file_name: Fully qualified name for this file
+ * @vfn: Fully qualified name for this file
+ * @cur: Current IO bunch being collected
+ * @iip: Input file this is associated with
+ * @start_time: Start time of th ecurrent bunch
+ * @last_time: Time of last packet put in
+ * @bunches: Number of bunches processed
+ * @pkts: Number of packets stored in bunches
+ */
+struct io_stream {
+ FILE *ofp, *vfp;
+ char *file_name, *vfn;
+ struct io_bunch *cur;
+ struct ifile_info *iip;
+ __u64 start_time, last_time, bunches, pkts;
+};
+
+int data_is_native; // Indicates whether to swap
+static LIST_HEAD(input_files); // List of all input files
+static char *idir = "."; // Input directory base
+static char *odir = "."; // Output directory base
+static char *obase = "replay"; // Output file base
+static __u64 max_bunch_tm = (10 * 1000 * 1000); // 10 milliseconds
+static __u64 max_pkts_per_bunch = 8; // Default # of pkts per bunch
+static int verbose = 0; // Boolean: output stats
+static int find_traces = 0; // Boolean: Find traces in dir
+
+static char usage_str[] = \
+ "\n" \
+ "\t[ -d <dir> : --input-directory=<dir> ] Default: .\n" \
+ "\t[ -D <dir> : --output-directory=<dir>] Default: .\n" \
+ "\t[ -F : --find-traces ] Default: Off\n" \
+ "\t[ -h : --help ] Default: Off\n" \
+ "\t[ -m <nsec> : --max-bunch-time=<nsec> ] Default: 10 msec\n" \
+ "\t[ -M <pkts> : --max-pkts=<pkts> ] Default: 8\n" \
+ "\t[ -o <base> : --output-base=<base> ] Default: replay\n" \
+ "\t[ -v : --verbose ] Default: Off\n" \
+ "\t[ -V : --version ] Default: Off\n" \
+ "\t<dev>... Default: None\n" \
+ "\n";
+
+#define S_OPTS "d:D:Fhm:M:o:vV"
+static struct option l_opts[] = {
+ {
+ .name = "input-directory",
+ .has_arg = required_argument,
+ .flag = NULL,
+ .val = 'd'
+ },
+ {
+ .name = "output-directory",
+ .has_arg = required_argument,
+ .flag = NULL,
+ .val = 'D'
+ },
+ {
+ .name = "find-traces",
+ .has_arg = no_argument,
+ .flag = NULL,
+ .val = 'F'
+ },
+ {
+ .name = "help",
+ .has_arg = no_argument,
+ .flag = NULL,
+ .val = 'h'
+ },
+ {
+ .name = "max-bunch-time",
+ .has_arg = required_argument,
+ .flag = NULL,
+ .val = 'm'
+ },
+ {
+ .name = "max_pkts",
+ .has_arg = required_argument,
+ .flag = NULL,
+ .val = 'M'
+ },
+ {
+ .name = "output-base",
+ .has_arg = required_argument,
+ .flag = NULL,
+ .val = 'o'
+ },
+ {
+ .name = "verbose",
+ .has_arg = no_argument,
+ .flag = NULL,
+ .val = 'v'
+ },
+ {
+ .name = "version",
+ .has_arg = no_argument,
+ .flag = NULL,
+ .val = 'V'
+ },
+ {
+ .name = NULL
+ }
+};
+
+#define ERR_ARGS 1
+#define ERR_SYSCALL 2
+#define fatal(errstring, exitval, arg...) \
+ do { \
+ if (errstring) perror(errstring); \
+ fprintf(stderr, ##arg); \
+ exit(exitval); \
+ /*NOTREACHED*/ \
+ } while (0)
+
+/**
+ * match - Return true if this trace is a proper QUEUE transaction
+ * @action: Action field from trace
+ */
+static inline int match(__u32 action)
+{
+ return ((action & 0xffff) == __BLK_TA_QUEUE) &&
+ (action & BLK_TC_ACT(BLK_TC_QUEUE));
+}
+
+/**
+ * usage - Display usage string and version
+ */
+static void usage(void)
+{
+ fprintf(stderr, "Usage: btrecord -- version %s\n%s",
+ my_btversion, usage_str);
+}
+
+/**
+ * write_file_hdr - Seek to and write btrecord file header
+ * @stream: Output file information
+ * @hdr: Header to write
+ */
+static void write_file_hdr(struct io_stream *stream, struct io_file_hdr *hdr)
+{
+ hdr->version = mk_btversion(btver_mjr, btver_mnr, btver_sub);
+
+ if (verbose) {
+ fprintf(stderr, "\t%s: %llx %llx %llx %llx\n",
+ stream->file_name,
+ (long long unsigned)hdr->version,
+ (long long unsigned)hdr->genesis,
+ (long long unsigned)hdr->nbunches,
+ (long long unsigned)hdr->total_pkts);
+ }
+
+ fseek(stream->ofp, 0, SEEK_SET);
+ if (fwrite(hdr, sizeof(*hdr), 1, stream->ofp) != 1) {
+ fatal(stream->file_name, ERR_SYSCALL, "Hdr write failed\n");
+ /*NOTREACHED*/
+ }
+}
+
+/**
+ * io_bunch_create - Allocate & initialize an io_bunch
+ * @io_stream: IO stream being added to
+ * @pre_stall: Amount of time that this bunch should be delayed by
+ * @start_time: Records current start
+ */
+static inline void io_bunch_create(struct io_stream *stream, __u64 start_time)
+{
+ struct io_bunch *cur = malloc(sizeof(*cur));
+
+ memset(cur, 0, sizeof(*cur));
+
+ cur->hdr.npkts = 0;
+ cur->hdr.time_stamp = stream->start_time = start_time;
+
+ stream->cur = cur;
+}
+
+/**
+ * io_bunch_add - Add an IO to the current bunch of IOs
+ * @stream: Per-output file stream information
+ * @spec: IO trace specification
+ *
+ * Returns update bunch information
+ */
+static void io_bunch_add(struct io_stream *stream, struct io_spec *spec)
+{
+ struct io_bunch *cur = stream->cur;
+ struct io_pkt iop = {
+ .sector = spec->sector,
+ .nbytes = spec->bytes,
+ .rw = spec->rw
+ };
+
+ assert(cur != NULL);
+ assert(cur->hdr.npkts < BT_MAX_PKTS);
+ assert(stream->last_time == 0 || stream->last_time <= spec->time);
+
+ cur->pkts[cur->hdr.npkts++] = iop; // Struct copy
+ stream->last_time = spec->time;
+}
+
+/**
+ * rem_input_file - Release resources associated with an input file
+ * @iip: Per-input file information
+ */
+static void rem_input_file(struct ifile_info *iip)
+{
+ list_del(&iip->head);
+
+ close(iip->ifd);
+ free(iip->file_name);
+ free(iip->devnm);
+ free(iip);
+}
+
+/**
+ * __add_input_file - Allocate and initialize per-input file structure
+ * @cpu: CPU for this file
+ * @devnm: Device name for this file
+ * @file_name: Fully qualifed input file name
+ */
+static void __add_input_file(int cpu, char *devnm, char *file_name)
+{
+ struct ifile_info *iip = malloc(sizeof(*iip));
+
+ iip->cpu = cpu;
+ iip->tpkts = 0;
+ iip->genesis = 0;
+ iip->devnm = strdup(devnm);
+ iip->file_name = strdup(file_name);
+ iip->ifd = open(file_name, O_RDONLY);
+ if (iip->ifd < 0) {
+ fatal(file_name, ERR_ARGS, "Unable to open\n");
+ /*NOTREACHED*/
+ }
+
+ list_add_tail(&iip->head, &input_files);
+}
+
+/**
+ * add_input_file - Set up the input file name
+ * @devnm: Device name to use
+ */
+static void add_input_file(char *devnm)
+{
+ struct list_head *p;
+ int cpu, found = 0;
+
+ __list_for_each(p, &input_files) {
+ struct ifile_info *iip = list_entry(p, struct ifile_info, head);
+ if (strcmp(iip->devnm, devnm) == 0)
+ return;
+ }
+
+ for (cpu = 0; ; cpu++) {
+ char full_name[MAXPATHLEN];
+
+ sprintf(full_name, "%s/%s.blktrace.%d", idir, devnm, cpu);
+ if (access(full_name, R_OK) != 0)
+ break;
+
+ __add_input_file(cpu, devnm, full_name);
+ found++;
+ }
+
+ if (!found) {
+ fatal(NULL, ERR_ARGS, "No traces found for %s\n", devnm);
+ /*NOTREACHED*/
+ }
+}
+
+static void find_input_files(char *idir)
+{
+ struct dirent *ent;
+ DIR *dir = opendir(idir);
+
+ if (dir == NULL) {
+ fatal(idir, ERR_ARGS, "Unable to open %s\n", idir);
+ /*NOTREACHED*/
+ }
+
+ while ((ent = readdir(dir)) != NULL) {
+ char *p, *dsf = malloc(256);
+
+ if (strstr(ent->d_name, ".blktrace.") == NULL)
+ continue;
+
+ dsf = strdup(ent->d_name);
+ p = index(dsf, '.');
+ assert(p != NULL);
+ *p = '\0';
+ add_input_file(dsf);
+ free(dsf);
+ }
+
+ closedir(dir);
+}
+
+/**
+ * handle_args - Parse passed in argument list
+ * @argc: Number of arguments in argv
+ * @argv: Arguments passed in
+ *
+ * Does rudimentary parameter verification as well.
+ */
+void handle_args(int argc, char *argv[])
+{
+ int c;
+
+ while ((c = getopt_long(argc, argv, S_OPTS, l_opts, NULL)) != -1) {
+ switch (c) {
+ case 'd':
+ idir = optarg;
+ if (access(idir, R_OK | X_OK) != 0) {
+ fatal(idir, ERR_ARGS,
+ "Invalid input directory specified\n");
+ /*NOTREACHED*/
+ }
+ break;
+
+ case 'D':
+ odir = optarg;
+ if (access(odir, R_OK | X_OK) != 0) {
+ fatal(odir, ERR_ARGS,
+ "Invalid output directory specified\n");
+ /*NOTREACHED*/
+ }
+ break;
+
+ case 'F':
+ find_traces = 1;
+ break;
+
+ case 'h':
+ usage();
+ exit(0);
+ /*NOTREACHED*/
+
+ case 'm':
+ max_bunch_tm = (__u64)atoll(optarg);
+ if (max_bunch_tm < 1) {
+ fprintf(stderr, "Invalid bunch time %llu\n",
+ (unsigned long long)max_bunch_tm);
+ exit(ERR_ARGS);
+ /*NOTREACHED*/
+ }
+ break;
+
+ case 'M':
+ max_pkts_per_bunch = (__u64)atoll(optarg);
+ if (!((1 <= max_pkts_per_bunch) &&
+ (max_pkts_per_bunch < 513))) {
+ fprintf(stderr, "Invalid max pkts %llu\n",
+ (unsigned long long)max_pkts_per_bunch);
+ exit(ERR_ARGS);
+ /*NOTREACHED*/
+ }
+ break;
+
+ case 'o':
+ obase = optarg;
+ break;
+
+ case 'V':
+ fprintf(stderr, "btrecord -- version %s\n",
+ my_btversion);
+ fprintf(stderr, " Built on %s\n", build_date);
+ exit(0);
+ /*NOTREACHED*/
+
+ case 'v':
+ verbose++;
+ break;
+
+ default:
+ usage();
+ fatal(NULL, ERR_ARGS, "Invalid command line\n");
+ /*NOTREACHED*/
+ }
+ }
+
+ while (optind < argc)
+ add_input_file(argv[optind++]);
+
+ if (find_traces)
+ find_input_files(idir);
+
+ if (list_len(&input_files) == 0) {
+ fatal(NULL, ERR_ARGS, "Missing required input file name(s)\n");
+ /*NOTREACHED*/
+ }
+}
+
+/**
+ * next_io - Retrieve next Q trace from input stream
+ * @iip: Per-input file information
+ * @spec: IO specifier for trace
+ *
+ * Returns 0 on end of file, 1 if valid data returned.
+ */
+static int next_io(struct ifile_info *iip, struct io_spec *spec)
+{
+ ssize_t ret;
+ __u32 action;
+ __u16 pdu_len;
+ struct blk_io_trace t;
+
+again:
+ ret = read(iip->ifd, &t, sizeof(t));
+ if (ret < 0) {
+ fatal(iip->file_name, ERR_SYSCALL, "Read failed\n");
+ /*NOTREACHED*/
+ }
+ else if (ret == 0)
+ return 0;
+ else if (ret < (ssize_t)sizeof(t)) {
+ fprintf(stderr, "WARNING: Short read on %s (%d)\n",
+ iip->file_name, (int)ret);
+ return 0;
+ }
+
+ if (data_is_native == -1)
+ check_data_endianness(t.magic);
+
+ assert(data_is_native >= 0);
+ if (data_is_native) {
+ spec->time = t.time;
+ spec->sector = t.sector;
+ spec->bytes = t.bytes;
+ action = t.action;
+ pdu_len = t.pdu_len;
+ }
+ else {
+ spec->time = be64_to_cpu(t.time);
+ spec->sector = be64_to_cpu(t.sector);
+ spec->bytes = be32_to_cpu(t.bytes);
+ action = be32_to_cpu(t.action);
+ pdu_len = be16_to_cpu(t.pdu_len);
+ }
+
+
+ if (pdu_len) {
+ char buf[pdu_len];
+
+ ret = read(iip->ifd, buf, pdu_len);
+ if (ret < 0) {
+ fatal(iip->file_name, ERR_SYSCALL, "Read PDU failed\n");
+ /*NOTREACHED*/
+ }
+ else if (ret < (ssize_t)pdu_len) {
+ fprintf(stderr, "WARNING: Short PDU read on %s (%d)\n",
+ iip->file_name, (int)ret);
+ return 0;
+ }
+ }
+
+ iip->tpkts++;
+ if (!match(action))
+ goto again;
+
+ spec->rw = (action & BLK_TC_ACT(BLK_TC_READ)) ? 1 : 0;
+ if (verbose > 1)
+ fprintf(stderr, "%2d: %10llu+%10llu (%d) @ %10llx\n",
+ iip->cpu, (long long unsigned)spec->sector,
+ (long long unsigned)spec->bytes / 512LLU,
+ spec->rw, (long long unsigned)spec->time);
+
+ if (iip->genesis == 0) {
+ iip->genesis = spec->time;
+ if (verbose > 1)
+ fprintf(stderr, "\tSetting new genesis: %llx(%d)\n",
+ (long long unsigned)iip->genesis, iip->cpu);
+ }
+ else if (iip->genesis > spec->time)
+ fatal(NULL, ERR_SYSCALL,
+ "Time inversion? %llu ... %llu\n",
+ (long long unsigned )iip->genesis,
+ (long long unsigned )spec->time);
+
+ return 1;
+}
+
+/**
+ * bunch_output_hdr - Output bunch header
+ */
+static inline void bunch_output_hdr(struct io_stream *stream)
+{
+ struct io_bunch_hdr *hdrp = &stream->cur->hdr;
+
+ assert(0 < hdrp->npkts && hdrp->npkts <= BT_MAX_PKTS);
+ if (fwrite(hdrp, sizeof(struct io_bunch_hdr), 1, stream->ofp) != 1) {
+ fatal(stream->file_name, ERR_SYSCALL, "fwrite(hdr) failed\n");
+ /*NOTREACHED*/
+ }
+
+ if (verbose) {
+ __u64 off = hdrp->time_stamp - stream->iip->genesis;
+
+ assert(stream->vfp);
+ fprintf(stream->vfp, "------------------\n");
+ fprintf(stream->vfp, "%4llu.%09llu %3llu\n",
+ (unsigned long long)off / (1000 * 1000 * 1000),
+ (unsigned long long)off % (1000 * 1000 * 1000),
+ (unsigned long long)hdrp->npkts);
+ fprintf(stream->vfp, "------------------\n");
+ }
+}
+
+/**
+ * bunch_output_pkt - Output IO packets
+ */
+static inline void bunch_output_pkts(struct io_stream *stream)
+{
+ struct io_pkt *p = stream->cur->pkts;
+ size_t npkts = stream->cur->hdr.npkts;
+
+ assert(0 < npkts && npkts <= BT_MAX_PKTS);
+ if (fwrite(p, sizeof(struct io_pkt), npkts, stream->ofp) != npkts) {
+ fatal(stream->file_name, ERR_SYSCALL, "fwrite(pkts) failed\n");
+ /*NOTREACHED*/
+ }
+
+ if (verbose) {
+ size_t i;
+
+ assert(stream->vfp);
+ for (i = 0; i < npkts; i++, p++)
+ fprintf(stream->vfp, "\t%1d %10llu\t%10llu\n",
+ p->rw,
+ (unsigned long long)p->sector,
+ (unsigned long long)p->nbytes / 512);
+ }
+}
+
+/**
+ * stream_flush - Flush current bunch of IOs out to the output stream
+ * @stream: Per-output file stream information
+ */
+static void stream_flush(struct io_stream *stream)
+{
+ struct io_bunch *cur = stream->cur;
+
+ if (cur) {
+ if (cur->hdr.npkts) {
+ assert(cur->hdr.npkts <= BT_MAX_PKTS);
+ bunch_output_hdr(stream);
+ bunch_output_pkts(stream);
+
+ stream->bunches++;
+ stream->pkts += cur->hdr.npkts;
+ }
+ free(cur);
+ }
+}
+
+/**
+ * bunch_done - Returns true if current bunch is either full, or next IO is late
+ * @stream: Output stream information
+ * @spec: IO trace specification
+ */
+static inline int bunch_done(struct io_stream *stream, struct io_spec *spec)
+{
+ if (stream->cur->hdr.npkts >= max_pkts_per_bunch)
+ return 1;
+
+ if ((spec->time - stream->start_time) > max_bunch_tm)
+ return 1;
+
+ return 0;
+}
+
+/**
+ * stream_add_io - Add an IO trace to the current stream
+ * @stream: Output stream information
+ * @spec: IO trace specification
+ */
+static void stream_add_io(struct io_stream *stream, struct io_spec *spec)
+{
+
+ if (stream->cur == NULL)
+ io_bunch_create(stream, spec->time);
+ else if (bunch_done(stream, spec)) {
+ stream_flush(stream);
+ io_bunch_create(stream, spec->time);
+ }
+
+ io_bunch_add(stream, spec);
+}
+
+/**
+ * stream_open - Open output stream for specified input stream
+ * @iip: Per-input file information
+ */
+static struct io_stream *stream_open(struct ifile_info *iip)
+{
+ char ofile_name[MAXPATHLEN];
+ struct io_stream *stream = malloc(sizeof(*stream));
+ struct io_file_hdr io_file_hdr = {
+ .genesis = 0,
+ .nbunches = 0,
+ .total_pkts = 0
+ };
+
+ memset(stream, 0, sizeof(*stream));
+
+ sprintf(ofile_name, "%s/%s.%s.%d", odir, iip->devnm, obase, iip->cpu);
+ stream->ofp = fopen(ofile_name, "w");
+ if (!stream->ofp) {
+ fatal(ofile_name, ERR_SYSCALL, "Open failed\n");
+ /*NOTREACHED*/
+ }
+
+ stream->iip = iip;
+ stream->cur = NULL;
+ stream->bunches = stream->pkts = 0;
+ stream->last_time = 0;
+ stream->file_name = strdup(ofile_name);
+
+ write_file_hdr(stream, &io_file_hdr);
+
+ if (verbose) {
+ char vfile_name[MAXPATHLEN];
+
+ sprintf(vfile_name, "%s/%s.%s.%d.rec", odir, iip->devnm,
+ obase, iip->cpu);
+ stream->vfp = fopen(vfile_name, "w");
+ if (!stream->vfp) {
+ fatal(vfile_name, ERR_SYSCALL, "Open failed\n");
+ /*NOTREACHED*/
+ }
+
+ stream->vfn = strdup(vfile_name);
+ }
+
+ data_is_native = -1;
+ return stream;
+}
+
+/**
+ * stream_close - Release resources associated with an output stream
+ * @stream: Stream to release
+ */
+static void stream_close(struct io_stream *stream)
+{
+ struct io_file_hdr io_file_hdr = {
+ .genesis = stream->iip->genesis,
+ .nbunches = stream->bunches,
+ .total_pkts = stream->pkts
+ };
+
+ stream_flush(stream);
+ write_file_hdr(stream, &io_file_hdr);
+ fclose(stream->ofp);
+
+ if (verbose && stream->bunches) {
+ fprintf(stderr,
+ "%s:%d: %llu pkts (tot), %llu pkts (replay), "
+ "%llu bunches, %.1lf pkts/bunch\n",
+ stream->iip->devnm, stream->iip->cpu,
+ (unsigned long long)stream->iip->tpkts,
+ (unsigned long long)stream->pkts,
+ (unsigned long long)stream->bunches,
+ (double)(stream->pkts) / (double)(stream->bunches));
+
+ fclose(stream->vfp);
+ free(stream->vfn);
+ }
+
+ free(stream->file_name);
+ free(stream);
+}
+
+/**
+ * process - Process one input file to an output file
+ * @iip: Per-input file information
+ */
+static void process(struct ifile_info *iip)
+{
+ struct io_spec spec;
+ struct io_stream *stream;
+
+ stream = stream_open(iip);
+ while (next_io(iip, &spec))
+ stream_add_io(stream, &spec);
+ stream_close(stream);
+
+ rem_input_file(iip);
+}
+
+/**
+ * main -
+ * @argc: Number of arguments
+ * @argv: Array of arguments
+ */
+int main(int argc, char *argv[])
+{
+ struct list_head *p, *q;
+
+ handle_args(argc, argv);
+ list_for_each_safe(p, q, &input_files)
+ process(list_entry(p, struct ifile_info, head));
+
+ return 0;
+}