diff options
Diffstat (limited to 'btreplay/btrecord.c')
-rw-r--r-- | btreplay/btrecord.c | 789 |
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; +} |