aboutsummaryrefslogtreecommitdiff
path: root/btreplay/btrecord.c
diff options
context:
space:
mode:
Diffstat (limited to 'btreplay/btrecord.c')
-rw-r--r--btreplay/btrecord.c789
1 files changed, 789 insertions, 0 deletions
diff --git a/btreplay/btrecord.c b/btreplay/btrecord.c
new file mode 100644
index 0000000..88ab806
--- /dev/null
+++ b/btreplay/btrecord.c
@@ -0,0 +1,789 @@
+/*
+ * 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>
+#include <stdarg.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
+static inline void fatal(const char *errstring, const int exitval,
+ const char *fmt, ...)
+{
+ va_list ap;
+
+ if (errstring)
+ perror(errstring);
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+
+ exit(exitval);
+ /*NOTREACHED*/
+}
+
+/**
+ * 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;
+}