[PATCH] Add Alan's btt tool
authorJens Axboe <axboe@suse.de>
Thu, 25 May 2006 23:16:17 +0000 (01:16 +0200)
committerJens Axboe <axboe@suse.de>
Thu, 25 May 2006 23:16:17 +0000 (01:16 +0200)
17 files changed:
Makefile
btt/Makefile [new file with mode: 0644]
btt/README [new file with mode: 0644]
btt/args.c [new file with mode: 0644]
btt/bt_timeline.c [new file with mode: 0644]
btt/cylist.c [new file with mode: 0644]
btt/devmap.c [new file with mode: 0644]
btt/devs.c [new file with mode: 0644]
btt/globals.h [new file with mode: 0644]
btt/inlines.h [new file with mode: 0644]
btt/iofree.c [new file with mode: 0644]
btt/list.h [new file with mode: 0644]
btt/misc.c [new file with mode: 0644]
btt/output.c [new file with mode: 0644]
btt/proc.c [new file with mode: 0644]
btt/trace.c [new file with mode: 0644]
btt/traverse.c [new file with mode: 0644]

index a69a0c92a30e01b18e8a41a2fc02e02de52c87d5..8583f0d0a8f47a633575557e255023a1931c6274 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -6,6 +6,7 @@ LIBS    = -lpthread
 SCRIPTS        = btrace
 
 all: depend $(PROGS) $(SCRIPTS)
+       $(MAKE) -C btt
 
 %.o: %.c
        $(CC) -o $*.o -c $(ALL_CFLAGS) $<
@@ -52,10 +53,12 @@ rpm: dist
 
 clean: docsclean
        -rm -f *.o $(PROGS) .depend btrace-1.0.tar.bz2
+       $(MAKE) -C btt clean
 
 install: $(PROGS) $(SCRIPTS)
        $(INSTALL) -m755 -d $(DESTDIR)$(bindir)
        $(INSTALL) $(PROGS) $(SCRIPTS) $(DESTDIR)$(bindir)
+       $(INSTALL) btt/btt $(DESTDIR)$(bindir)
 
 ifneq ($(wildcard .depend),)
 include .depend
diff --git a/btt/Makefile b/btt/Makefile
new file mode 100644 (file)
index 0000000..964fb28
--- /dev/null
@@ -0,0 +1,29 @@
+CC     = gcc
+MYFLAGS = -DLVM_REMAP_WORKAROUND
+CFLAGS = -Wall -O2 -W -I.. -DDO_INLINE ${MYFLAGS}
+#CFLAGS        = -Wall -g -W -I.. -UDO_INLINE -DDEBUG ${MYFLAGS}
+ALL_CFLAGS = $(CFLAGS) -D_GNU_SOURCE -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64
+PROGS  = btt
+#ELIBS = -lefence
+#PLIBS = -lpthread
+LIBS   = $(PLIBS) $(ELIBS)
+OBJS   = bt_timeline.o args.o output.o proc.o trace.o misc.o devs.o \
+         traverse.o iofree.o devmap.o cylist.o
+
+all: depend $(PROGS)
+
+depend:
+       @$(CC) -MM $(ALL_CFLAGS) -I.. *.c 1> .depend
+
+clean:
+       -rm -f *.o $(PROGS) .depend
+
+%.o: %.c
+       $(CC) -o $*.o -c $(ALL_CFLAGS) $<
+
+btt: $(OBJS)
+       $(CC) $(ALL_CFLAGS) -o $@ $(filter %.o,$^) $(LIBS)
+
+ifneq ($(wildcard .depend),)
+include .depend
+endif
diff --git a/btt/README b/btt/README
new file mode 100644 (file)
index 0000000..23eac04
--- /dev/null
@@ -0,0 +1,57 @@
+blktrace Timeline
+-----------------
+
+Alan D. Brunelle (initial version)
+
+Usage
+-----
+
+$ btt -i <input file> (-l | -m) [-d <range delta>] [-D <devices...>]
+      [-e <executables...>] [-h] [-M map information] [-o <output file>]
+      [-v] [-V]
+
+You are required to:
+
+o  Specify an input file (-i)
+
+o  Specify MD (-m) or LVM2/DM (-l) devices present (If no software RAID
+   devices, use -m.) This is a workaround for issues between LVM2 and MD
+   remap events that needs to be cleaned up. [DM devices are not working
+   as of right now, always specify -m for individual disks and/or MD
+   devices.]
+
+The -d argument allows you to specify the granularity which dtermines
+"activity" with regard to the .dat files -- this specific the time
+(in seconds) that must elapse without a particular event occuring to
+signify inactivity. The larger the number, the fewer ranges output --
+the default is 0.1 seconds.
+
+The -D argument supplies the devices which should be looked at when
+analyzing the input. This is a ":" separated list of devices, devices are
+specified by a mjr,mnr tuple (e.g.: -D "8,0:8,8" specifies two devices
+with major 8 and minor 0 and 8 respectively).
+
+The -e argument supplies the list of executables that will have I/Os
+analyzed.
+
+The -M argument takes in a file generated by the provided script
+(gen_disk_info.py), and allows for better output of device names.
+
+Overview
+--------
+
+btt will take in binary dump data from blkparse, and analyze the events,
+producing a series of output from the analysis. It will also build .dat
+files containing "range data" -- showing things like Q activity (periods
+of time while Q events are being produced), C activity (likewise for
+command completions), and etc.
+
+Resources
+---------
+
+vger hosts a mailing list dedicated to btrace discussion and development.
+The list is called linux-btrace@vger.kernel.org, subscribe by sending
+a mail to majordomo@vger.kernel.org with 'subscribe linux-btrace' in
+the mail body.
+
+2006-05-17, Alan D. Brunelle <alan.brunelle@hp.com>
diff --git a/btt/args.c b/btt/args.c
new file mode 100644 (file)
index 0000000..d7ecb48
--- /dev/null
@@ -0,0 +1,205 @@
+/*
+ * blktrace output analysis: generate a timeline & gather statistics
+ *
+ * Copyright (C) 2006 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
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "globals.h"
+
+#define S_OPTS "d:D:e:hlmM:i:o:Vv"
+static struct option l_opts[] = {
+       {
+               .name = "range-delta",
+               .has_arg = required_argument,
+               .flag = NULL,
+               .val = 'd'
+       },
+       {
+               .name = "devices",
+               .has_arg = required_argument,
+               .flag = NULL,
+               .val = 'D'
+       },
+       {
+               .name = "exes",
+               .has_arg = required_argument,
+               .flag = NULL,
+               .val = 'e'
+       },
+       {
+               .name = "help",
+               .has_arg = no_argument,
+               .flag = NULL,
+               .val = 'h'
+       },
+       {
+               .name = "lvm",
+               .has_arg = no_argument,
+               .flag = NULL,
+               .val = 'l'
+       },
+       {
+               .name = "md",
+               .has_arg = no_argument,
+               .flag = NULL,
+               .val = 'm'
+       },
+       {
+               .name = "dev-maps",
+               .has_arg = required_argument,
+               .flag = NULL,
+               .val = 'M'
+       },
+       {
+               .name = "input-file",
+               .has_arg = required_argument,
+               .flag = NULL,
+               .val = 'i'
+       },
+       {
+               .name = "output-file",
+               .has_arg = required_argument,
+               .flag = NULL,
+               .val = 'o'
+       },
+       {
+               .name = "version",
+               .has_arg = no_argument,
+               .flag = NULL,
+               .val = 'V'
+       },
+       {
+               .name = "verbose",
+               .has_arg = no_argument,
+               .flag = NULL,
+               .val = 'v'
+       },
+       {
+               .name = NULL,
+       }
+};
+
+static char usage_str[] = \
+       "\n[ -d <seconds>     | --range-delta=<seconds> ]\n" \
+       "[ -e <exe,...>     | --exes=<exe,...>  ]\n" \
+       "[ -h               | --help ]\n" \
+       "[ -i <input name>  | --input-file=<input name> ]\n" \
+       "(-l | -m)          | (--lvm | -md)\n" \
+       "[ -o <output name> | --output-file=<output name> ]\n" \
+       "[ -V               | --version ]\n" \
+       "[ -v               | --verbose ]\n\n";
+
+static void usage(char *prog)
+{
+       fprintf(stderr, "Usage: %s %s %s", prog, bt_timeline_version,
+               usage_str);
+}
+
+void handle_args(int argc, char *argv[])
+{
+       int c;
+       char *dev_map_fname = NULL;
+
+       while ((c = getopt_long(argc, argv, S_OPTS, l_opts, NULL)) != -1) {
+               switch (c) {
+               case 'd':
+                       sscanf(optarg, "%lf", &range_delta);
+                       break;
+               case 'D':
+                       devices = optarg;
+                       break;
+               case 'e':
+                       exes = optarg;
+                       break;
+               case 'h':
+                       usage(argv[0]);
+                       exit(0);
+               case 'i':
+                       input_name = optarg;
+                       break;
+               case 'l':
+                       is_lvm = 1;
+                       break;
+               case 'm':
+                       is_lvm = 0;
+                       break;
+               case 'M':
+                       dev_map_fname = optarg;
+                       break;
+               case 'o':
+                       output_name = optarg;
+                       break;
+               case 'v':
+                       verbose = 1;
+                       break;
+               case 'V':
+                       printf("%s version %s\n", argv[0], bt_timeline_version);
+                       exit(0);
+               default:
+                       usage(argv[0]);
+                       exit(1);
+               }
+       }
+
+       if (input_name == NULL || is_lvm < 0) {
+               usage(argv[0]);
+               exit(1);
+       }
+
+       ifd = open(input_name, O_RDONLY);
+       if (ifd < 0) {
+               perror(input_name);
+               exit(1);
+       }
+
+       if (dev_map_fname && dev_map_read(dev_map_fname))
+               exit(1);
+
+       if (output_name == NULL)
+               ranges_ofp = avgs_ofp = stdout;
+       else {
+               char *fname = malloc(sizeof(output_name) + 20);
+
+               sprintf(fname, "%s.dat", output_name);
+               ranges_ofp = fopen(fname, "w");
+               if (ranges_ofp == NULL) {
+                       perror(fname);
+                       exit(1);
+               }
+               if (verbose)
+                       printf("Sending range data to %s\n", output_name);
+
+               sprintf(fname, "%s.avg", output_name);
+               avgs_ofp = fopen(fname, "w");
+               if (avgs_ofp == NULL) {
+                       perror(fname);
+                       exit(1);
+               }
+               if (verbose)
+                       printf("Sending stats data to %s\n", output_name);
+
+               free(fname);
+       }
+}
diff --git a/btt/bt_timeline.c b/btt/bt_timeline.c
new file mode 100644 (file)
index 0000000..87255fc
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * blktrace output analysis: generate a timeline & gather statistics
+ *
+ * Copyright (C) 2006 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
+ *
+ */
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "globals.h"
+
+char bt_timeline_version[] = "0.99";
+
+char *devices = NULL;
+char *exes = NULL;
+char *input_name = NULL;
+char *output_name = NULL;
+double range_delta = 0.1;
+FILE *ranges_ofp, *avgs_ofp;
+int is_lvm = -1;
+int verbose = 0;
+int ifd;
+unsigned long n_traces, n_io_allocs, n_io_frees;
+struct avgs_info all_avgs;
+__u64 last_q = (__u64)-1;
+unsigned int pending_xs;
+
+unsigned int n_devs;
+LIST_HEAD(all_devs);
+LIST_HEAD(all_ios);
+LIST_HEAD(all_procs);
+struct my_mem *free_ios = NULL;
+struct my_mem *free_bits = NULL;
+
+struct region_info all_regions = {
+       .qranges = LIST_HEAD_INIT(all_regions.qranges),
+       .cranges = LIST_HEAD_INIT(all_regions.cranges),
+       .qr_cur = NULL,
+       .cr_cur = NULL
+};
+
+char iop_map[] = { 'Q', 'X', 'A', 'M', 'I', 'D', 'C', 'Y' };
+
+struct blk_io_trace *convert_to_cpu(struct blk_io_trace *t);
+int process(void);
+
+int main(int argc, char *argv[])
+{
+       handle_args(argc, argv);
+
+       cy_init();
+       if (process() || output_avgs(avgs_ofp) || output_ranges(ranges_ofp))
+               return 1;
+
+       return 0;
+}
+
+int process(void)
+{
+       int ret = 0;
+       struct blk_io_trace *t;
+       struct io *iop = IO_ZALLOC();
+
+       while (!do_read(ifd, &iop->t, sizeof(iop->t))) {
+               t = convert_to_cpu(&iop->t);
+               if (t->pdu_len > 0) {
+                       iop->pdu = malloc(t->pdu_len);
+                       if (do_read(ifd, iop->pdu, t->pdu_len)) {
+                               free(iop->pdu);
+                               ret = 1;
+                               break;
+                       }
+               }
+               add_trace(iop);
+               iop = IO_ZALLOC();
+       }
+       IO_FREE(iop);
+
+       if (verbose)
+               printf("\n%10lu traces, %10lu mallocs %1lu frees\n",
+                                      n_traces, n_io_allocs, n_io_frees);
+
+       cy_shutdown();
+       return ret;
+}
diff --git a/btt/cylist.c b/btt/cylist.c
new file mode 100644 (file)
index 0000000..64fb7c9
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * blktrace output analysis: generate a timeline & gather statistics
+ *
+ * Copyright (C) 2006 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
+ *
+ */
+#include "globals.h"
+
+struct cy_list {
+       struct list_head head;
+       struct list_head list;
+};
+
+struct cy_list *pending_cys;
+
+static inline void __rem_cy(struct io_list *iolp)
+{
+       io_unlink(&iolp->iop);
+       if (--iolp->cy_users == 0) {
+               LIST_DEL(&iolp->head);
+               free(iolp);
+       }
+}
+
+void rem_c(struct io *iop)
+{
+       struct list_head *p, *q;
+       struct io_list *iolp;
+
+       ASSERT(iop->type == IOP_C);
+
+       list_for_each_safe(p, q, &pending_cys->list) {
+               iolp = list_entry(p, struct io_list, head);
+
+               if (iolp->iop == iop) {
+                       __rem_cy(iolp);
+                       break;
+               }
+       }
+}
+
+void run_cy_list(struct list_head *list)
+{
+       struct list_head *p, *q;
+       struct io_list *iolp;
+
+       list_for_each_safe(p, q, list) {
+               iolp = list_entry(p, struct io_list, head);
+               traverse(iolp->iop);
+               __rem_cy(iolp);
+       }
+}
+
+void add_cy(struct io *iop)
+{
+       struct io_list *iolp = malloc(sizeof(*iolp));
+
+       ASSERT(iop->type == IOP_C || iop->type == IOP_Y);
+
+       iolp->cy_users = 1;
+       io_link(&iolp->iop, iop);
+
+       list_add_tail(&iolp->head, &pending_cys->list);
+       if (pending_xs == 0)
+               run_cy_list(&pending_cys->list);
+}
+
+void cy_init(void)
+{
+       pending_cys = zmalloc(sizeof(*pending_cys));
+       INIT_LIST_HEAD(&pending_cys->list);
+}
+
+void cy_shutdown(void)
+{
+       ASSERT(list_empty(&pending_cys->list));
+       free(pending_cys);
+}
diff --git a/btt/devmap.c b/btt/devmap.c
new file mode 100644 (file)
index 0000000..5de57e8
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * blktrace output analysis: generate a timeline & gather statistics
+ *
+ * Copyright (C) 2006 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
+ *
+ */
+#include <stdio.h>
+
+#include "globals.h"
+struct devmap *all_devmaps = NULL;
+void dev_map_add(struct devmap *dmp)
+{
+       struct devmap *this = zmalloc(sizeof(*this));
+
+       *this = *dmp;
+       this->next = all_devmaps;
+       all_devmaps = this;
+}
+
+struct devmap *dev_map_find(__u32 device)
+{
+       char this[128];
+       struct devmap *dmp;
+
+       sprintf(this, "%u,%u", MAJOR(device), MINOR(device));
+       for (dmp = all_devmaps; dmp != NULL; dmp = dmp->next)
+               if (!strcmp(this, dmp->devno))
+                       break;
+
+       return dmp;
+}
+
+int dev_map_read(char *fname)
+{
+       char line[256];
+       struct devmap dm;
+       FILE *fp = fopen(fname, "r");
+
+       if (!fp) {
+               perror(fname);
+               return 1;
+       }
+
+       while (fscanf(fp, "%255[a-zA-Z0-9 :.,/]\n", line) == 1) {
+               if (strstr(line, "Device") != NULL) continue;
+               if (sscanf(line, "%s %s %u %u %u %u %s %s %u %u %s",
+                               &dm.device[0], &dm.model[0], &dm.host, &dm.bus,
+                               &dm.target, &dm.lun, &dm.node[0], &dm.pci[0],
+                               &dm.irq, &dm.cpu, &dm.devno[0]) != 11)
+                       break;
+               dev_map_add(&dm);
+       }
+
+       return 0;
+}
diff --git a/btt/devs.c b/btt/devs.c
new file mode 100644 (file)
index 0000000..fe87942
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * blktrace output analysis: generate a timeline & gather statistics
+ *
+ * Copyright (C) 2006 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
+ *
+ */
+#include <stdio.h>
+
+#include "globals.h"
+
+#define N_DEV_HASH     128
+#define DEV_HASH(dev)  ((MAJOR(dev) ^ MINOR(dev)) & (N_DEV_HASH - 1))
+struct list_head       *dev_heads = NULL;
+
+static void init_dev_heads(void)
+{
+       int i;
+
+       dev_heads = zmalloc(N_DEV_HASH * sizeof(struct list_head));
+       for (i = 0; i < N_DEV_HASH; i++)
+               INIT_LIST_HEAD(&dev_heads[i]);
+}
+
+struct d_info *__dip_find(__u32 device)
+{
+       struct list_head *p;
+       struct d_info *dip;
+
+       if (dev_heads == NULL) {
+               init_dev_heads();
+               return NULL;
+       }
+
+       __list_for_each(p, &dev_heads[DEV_HASH(device)]) {
+               dip = list_entry(p, struct d_info, hash_head);
+               if (device == dip->device)
+                       return dip;
+       }
+
+       return NULL;
+}
+
+struct d_info *dip_add(__u32 device, struct io *iop)
+{
+       struct d_info *dip = __dip_find(device);
+
+       if (dip == NULL) {
+               int i;
+
+               dip = zmalloc(sizeof(*dip));
+               dip->device = device;
+               dip->last_q = (__u64)-1;
+               for (i = 0; i < N_IOP_TYPES; i++)
+                       INIT_LIST_HEAD(&dip->iop_heads[i]);
+               init_region(&dip->regions);
+               dip->map = dev_map_find(device);
+
+               if (dev_heads == NULL) init_dev_heads();
+               list_add_tail(&dip->hash_head, &dev_heads[DEV_HASH(device)]);
+
+               list_add_tail(&dip->head, &all_devs);
+               n_devs++;
+       }
+
+       list_add(&iop->dev_head, dip_get_head(dip, iop->type));
+
+       return dip;
+}
diff --git a/btt/globals.h b/btt/globals.h
new file mode 100644 (file)
index 0000000..137d276
--- /dev/null
@@ -0,0 +1,215 @@
+/*
+ * blktrace output analysis: generate a timeline & gather statistics
+ *
+ * Copyright (C) 2006 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
+ *
+ */
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "blktrace.h"
+#include "list.h"
+
+#define pdu_start(t)   (((void *) (t) + sizeof(struct blk_io_trace)))
+
+#define BIT_TIME(t)    ((double)SECONDS(t) + ((double)NANO_SECONDS(t) / 1.0e9))
+
+#define BIT_START(iop) ((iop)->t.sector)
+#define BIT_END(iop)   ((iop)->t.sector + ((iop)->t.bytes >> 9))
+
+#if defined(DEBUG)
+#define ASSERT(truth)   do {                                           \
+                               if (!(truth)) {                         \
+                                       DBG_PING();                     \
+                                       assert(truth);                  \
+                               }                                       \
+                       } while (0)
+
+#define DBG_PING()             dbg_ping()
+
+#define LIST_DEL(hp)   list_del(hp)
+
+#else
+#define ASSERT(truth)
+#define DBG_PING()
+
+#define LIST_DEL(hp)   do {                                            \
+                               if (((hp)->next != NULL) &&             \
+                                   ((hp)->next != LIST_POISON1))       \
+                                       list_del(hp);                   \
+                       } while (0)
+#endif
+
+#define IO_ZALLOC()    my_zmalloc(&free_ios, sizeof(struct io))
+#define IO_FREE(iop)   my_free(&free_ios, iop)
+
+enum iop_type {
+       IOP_Q = 0,
+       IOP_X = 1,
+       IOP_A = 2,
+       IOP_M = 3,
+       IOP_I = 4,
+       IOP_D = 5,
+       IOP_C = 6,
+       IOP_Y = 7,
+};
+#define N_IOP_TYPES    (IOP_Y + 1)
+
+struct my_mem {
+       struct my_mem *next;
+};
+
+struct io;
+struct io_list {
+       struct list_head head;
+       struct io *iop;
+       int cy_users;
+};
+
+struct avg_info {
+       __u64 min, max, total;
+       double avg;
+       int n;
+};
+
+struct avgs_info {
+        struct avg_info q2q;
+       struct avg_info q2c;
+       struct avg_info q2a;            /* Q to (A or X) */
+       struct avg_info q2i;            /* Q to (I or M) */
+       struct avg_info i2d;            /* (I or M) to D */
+       struct avg_info d2c;
+
+       struct avg_info blks;           /* Blocks transferred */
+};
+
+struct range_info {
+       struct list_head head;          /* on: qranges OR cranges */
+       __u64 start, end;
+};
+
+struct region_info {
+       struct list_head qranges;
+       struct list_head cranges;
+       struct range_info *qr_cur, *cr_cur;
+};
+
+struct p_info;
+struct p_pid {
+       struct list_head head;
+       struct p_info *pip;
+       __u32 pid;
+};
+
+struct p_info {
+       struct list_head head;
+       struct region_info regions;
+       struct avgs_info avgs;
+       char *name;
+       __u64 last_q;
+};
+
+struct devmap {
+       struct devmap *next;
+       char device[32];
+       char model[64];
+       unsigned int host, bus, target, lun;
+       char node[32], pci[32];
+       unsigned int irq, cpu;
+       char devno[32];
+};
+
+struct d_info {
+       struct list_head head, hash_head;
+       struct list_head iop_heads[N_IOP_TYPES];
+       struct region_info regions;
+       struct avgs_info avgs;
+       __u64 last_q;
+       __u32 device;
+       __u64 n_ds;
+       struct devmap *map;
+};
+
+struct io {
+       struct list_head all_head, dev_head;
+       struct d_info *dip;
+       struct p_info *pip;
+       void *pdu;
+
+       struct blk_io_trace t;
+
+       int users, traversed;
+       enum iop_type type;
+
+       union {
+               struct {
+                       union {
+                               struct io *q_a;
+                               struct io *q_x;
+                       } qp;
+                       enum {
+                               Q_NONE = 10,
+                               Q_A = 20,
+                               Q_X = 30,
+                       } qp_type;
+               }                                         q;
+               struct { struct io *x_q;                } x;
+               struct { struct io *a_q;                } a;
+               struct { struct io *m_q;                } m;
+               struct { struct list_head i_qs_head;    } i;
+               struct { struct list_head d_im_head;    } d;
+               struct { struct io *c_d;                } c;
+               struct { struct io *y_c1, *y_c2;        } y;
+       } u;
+};
+
+extern char bt_timeline_version[], *devices, *exes, *input_name, *output_name;
+extern double range_delta;
+extern FILE *ranges_ofp, *avgs_ofp;
+extern int is_lvm, verbose, ifd;
+extern unsigned int n_devs;
+extern unsigned long n_traces, n_io_allocs, n_io_frees;
+extern struct list_head all_devs, all_ios, all_procs;
+extern struct avgs_info all_avgs;
+extern __u64 last_q;
+extern struct region_info all_regions;
+extern struct my_mem *free_ios, *free_bits;
+extern char iop_map[];
+extern unsigned int pending_xs;
+
+void add_trace(struct io *iop);
+int in_devices(struct blk_io_trace *t);
+int output_avgs(FILE *ofp);
+int output_ranges(FILE *ofp);
+unsigned int do_read(int ifd, void *buf, int len);
+void add_process(__u32 pid, char *name);
+struct p_info *find_process(__u32 pid, char *name);
+void pip_update_q(struct io *iop);
+void handle_args(int argc, char *argv[]);
+struct devmap *dev_map_find(__u32 device);
+int dev_map_read(char *fname);
+void add_cy(struct io *iop);
+void rem_c(struct io *iop);
+void cy_init(void);
+void cy_shutdown(void);
+struct d_info *__dip_find(__u32 device);
+struct d_info *dip_add(__u32 device, struct io *iop);
+void traverse(struct io *iop);
+void io_free_resources(struct io *iop);
+
+#include "inlines.h"
diff --git a/btt/inlines.h b/btt/inlines.h
new file mode 100644 (file)
index 0000000..cda74b8
--- /dev/null
@@ -0,0 +1,290 @@
+/*
+ * blktrace output analysis: generate a timeline & gather statistics
+ *
+ * Copyright (C) 2006 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
+ *
+ */
+#if defined(DO_INLINE)
+#      define INLINE_PREFIX    static inline
+#      define INLINE_DECLARE
+#else
+#      define INLINE_PREFIX
+#endif
+
+static inline void dbg_ping(void) { }
+
+#if defined(INLINE_DECLARE)
+INLINE_PREFIX void *zmalloc(size_t len)
+{
+       return memset(malloc(len), 0, len);
+}
+
+INLINE_PREFIX int is_dev(struct io *iop, unsigned int mjr, unsigned int mnr)
+{
+       return MAJOR(iop->t.device) == mjr && MINOR(iop->t.device) == mnr;
+}
+
+INLINE_PREFIX int in_bit(struct io *in, struct io *out)
+{
+       return (BIT_START(out) <= BIT_START(in)) &&
+              (BIT_END(in) <= BIT_END(out));
+}
+
+INLINE_PREFIX int is_bit(struct io *i1, struct io *i2)
+{
+       return (BIT_START(i1) == BIT_START(i2)) && (BIT_END(i1) == BIT_END(i2));
+}
+
+INLINE_PREFIX struct range_info *new_cur(__u64 time)
+{
+       struct range_info *cur = zmalloc(sizeof(*cur));
+
+       INIT_LIST_HEAD(&cur->head);
+       cur->start = time;
+       return cur;
+}
+
+INLINE_PREFIX  void update_range(struct list_head *head_p,
+                                struct range_info **cur_p, __u64 time)
+{
+       if (*cur_p == NULL)
+               *cur_p = new_cur(time);
+       else {
+               __u64 my_delta = time - (*cur_p)->end;
+               if (BIT_TIME(my_delta) >= range_delta) {
+                       list_add_tail(&(*cur_p)->head, head_p);
+                       *cur_p = new_cur(time);
+               }
+       }
+
+       (*cur_p)->end = time;
+}
+
+INLINE_PREFIX void init_region(struct region_info *reg)
+{
+       INIT_LIST_HEAD(&reg->qranges);
+       INIT_LIST_HEAD(&reg->cranges);
+       reg->qr_cur = reg->cr_cur = NULL;
+}
+
+INLINE_PREFIX void update_qregion(struct region_info *reg, __u64 time)
+{
+       update_range(&reg->qranges, &reg->qr_cur, time);
+}
+
+INLINE_PREFIX void update_cregion(struct region_info *reg, __u64 time)
+{
+       update_range(&reg->cranges, &reg->cr_cur, time);
+}
+
+INLINE_PREFIX void avg_update(struct avg_info *ap, __u64 t)
+{
+        if (ap->n++ == 0)
+                ap->min = ap->total = ap->max = t;
+        else {
+                if (t < ap->min)
+                        ap->min = t;
+                else if (t > ap->max)
+                        ap->max = t;
+                ap->total += t;
+        }
+}
+
+INLINE_PREFIX void update_lq(__u64 *last_q, struct avg_info *avg, __u64 time)
+{
+       if (*last_q != ((__u64)-1))
+               avg_update(avg, time - *last_q);
+       *last_q = time;
+}
+
+INLINE_PREFIX struct list_head *dip_get_head(struct d_info *dip,
+                                             enum iop_type type)
+{
+       return &dip->iop_heads[type];
+}
+
+INLINE_PREFIX struct list_head *dip_get_head_dev(__u32 dev, enum iop_type type)
+{
+       return dip_get_head(__dip_find(dev), type);
+}
+
+INLINE_PREFIX void dip_update_q(struct d_info *dip, struct io *iop)
+{
+       update_lq(&dip->last_q, &dip->avgs.q2q, iop->t.time);
+       update_qregion(&dip->regions, iop->t.time);
+}
+
+INLINE_PREFIX void dip_rem(struct io *iop)
+{
+       LIST_DEL(&iop->dev_head);
+}
+
+INLINE_PREFIX void *my_malloc(struct my_mem **head_p, size_t len)
+{
+       struct my_mem *this = *head_p;
+
+       if (this)
+               *head_p = this->next;
+       else
+               this = malloc(len);
+
+       return this;
+}
+
+INLINE_PREFIX void *my_zmalloc(struct my_mem **head_p, size_t len)
+{
+       return memset(my_malloc(head_p, len), 0, len);
+}
+
+INLINE_PREFIX void my_free(struct my_mem **head_p, void *p)
+{
+       struct my_mem *this = p;
+
+       this->next = *head_p;
+       *head_p = this;
+}
+
+INLINE_PREFIX void io_setup(struct io *iop, enum iop_type type)
+{
+       iop->type = type;
+       iop->dip = dip_add(iop->t.device, iop);
+       iop->pip = find_process(iop->t.pid, NULL);
+
+       n_io_allocs++;
+       list_add_tail(&iop->all_head, &all_ios);
+}
+
+INLINE_PREFIX void io_free(struct io *iop)
+{
+       ASSERT(iop->users == 0);
+
+       LIST_DEL(&iop->all_head);
+       dip_rem(iop);
+       IO_FREE(iop);
+       n_io_frees++;
+}
+
+INLINE_PREFIX void io_get(struct io *iop)
+{
+       iop->users++;
+}
+
+
+INLINE_PREFIX int __io_put(struct io *iop)
+{
+       return --iop->users;
+}
+
+INLINE_PREFIX void io_put(struct io *iop)
+{
+       if (__io_put(iop) == 0) {
+               io_free_resources(iop);
+               io_free(iop);
+       }
+}
+
+INLINE_PREFIX void io_link(struct io **p_dst, struct io *iop)
+{
+       ASSERT(iop != NULL);
+       io_get(iop);
+       *p_dst = iop;
+}
+
+INLINE_PREFIX void io_unlink(struct io **p_dst)
+{
+       ASSERT(*p_dst != NULL);
+       io_put(*p_dst);
+
+#if defined(DEBUG)
+       *p_dst = NULL;
+#endif
+}
+
+#define UPDATE_AVGS(_avg, _iop, _pip, _time) do {                      \
+               avg_update(&all_avgs. _avg , _time);                    \
+               avg_update(&_iop->dip->avgs. _avg , _time);             \
+               if (_pip) avg_update(&_pip->avgs. _avg , _time);        \
+       } while (0)
+
+INLINE_PREFIX void update_q2c(struct io *iop, __u64 c_time)
+{
+       UPDATE_AVGS(q2c, iop, iop->pip, c_time);
+}
+
+INLINE_PREFIX void update_q2a(struct io *iop, __u64 a_time)
+{
+       UPDATE_AVGS(q2a, iop, iop->pip, a_time);
+}
+
+INLINE_PREFIX void update_q2i(struct io *iop, __u64 i_time)
+{
+       UPDATE_AVGS(q2i, iop, iop->pip, i_time);
+}
+
+INLINE_PREFIX void update_i2d(struct io *iop, __u64 d_time)
+{
+       UPDATE_AVGS(i2d, iop, iop->pip, d_time);
+}
+
+INLINE_PREFIX void update_d2c(struct io *iop, __u64 c_time)
+{
+       UPDATE_AVGS(d2c, iop, iop->pip, c_time);
+}
+
+INLINE_PREFIX void update_blks(struct io *iop)
+{
+       __u64 nblks = iop->t.bytes >> 9;
+       avg_update(&all_avgs.blks, nblks);
+       avg_update(&iop->dip->avgs.blks, nblks);
+       if (iop->pip)
+               avg_update(&iop->pip->avgs.blks, nblks);
+}
+#else
+void *zmalloc(size_t len);
+int is_dev(struct io *iop, unsigned int mjr, unsigned int mnr);
+int in_bit(struct io *in, struct io *out);
+int is_bit(struct io *i1, struct io *i2);
+struct range_info *new_cur(__u64 time);
+void update_range(struct list_head *head_p,
+                                struct range_info **cur_p, __u64 time);
+void init_region(struct region_info *reg);
+void update_qregion(struct region_info *reg, __u64 time);
+void update_cregion(struct region_info *reg, __u64 time);
+void avg_update(struct avg_info *ap, __u64 t);
+void update_lq(__u64 *last_q, struct avg_info *avg, __u64 time);
+struct list_head *dip_get_head(struct d_info *dip, enum iop_type type);
+void dip_update_q(struct d_info *dip, struct io *iop);
+void dip_rem(struct io *iop);
+void io_log(char *header, struct io *iop);
+void *my_malloc(struct my_mem **head_p, size_t len);
+void *my_zmalloc(struct my_mem **head_p, size_t len);
+void my_free(struct my_mem **head_p, void *p);
+void io_setup(struct io *iop, enum iop_type type);
+void io_free(struct io *iop);
+void io_get(struct io *iop);
+int __io_put(struct io *iop);
+void io_put(struct io *iop);
+void io_link(struct io **p_dst, struct io *iop);
+void io_unlink(struct io **p_dst);
+void update_q2c(struct io *iop, __u64 d_time);
+void update_q2a(struct io *iop, __u64 d_time);
+void update_q2i(struct io *iop, __u64 d_time);
+void update_i2d(struct io *iop, __u64 d_time);
+void update_d2c(struct io *iop, __u64 d_time);
+void update_blks(struct io *iop);
+struct list_head *dip_get_head_dev(__u32 dev, enum iop_type type);
+#endif
diff --git a/btt/iofree.c b/btt/iofree.c
new file mode 100644 (file)
index 0000000..a0af8bb
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * blktrace output analysis: generate a timeline & gather statistics
+ *
+ * Copyright (C) 2006 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
+ *
+ */
+#include <stdio.h>
+
+#include "globals.h"
+
+void io_free_resources(struct io *iop)
+{
+       switch (iop->type) {
+       case IOP_X: io_unlink(&iop->u.x.x_q); break;
+       case IOP_A: io_unlink(&iop->u.a.a_q); break;
+       case IOP_M: io_unlink(&iop->u.m.m_q); break;
+       case IOP_C: io_unlink(&iop->u.c.c_d); break;
+
+       case IOP_Q:
+               switch (iop->u.q.qp_type) {
+               case Q_NONE: break;
+               case Q_A: io_unlink(&iop->u.q.qp.q_a); break;
+               case Q_X: io_unlink(&iop->u.q.qp.q_x); break;
+               default:
+                       ASSERT(0);
+                       /*NOTREACHED*/
+               }
+               break;
+
+       case IOP_D: {
+               struct io_list *iolp;
+               struct list_head *p, *q;
+
+               list_for_each_safe(p, q, &iop->u.d.d_im_head) {
+                       iolp = list_entry(p, struct io_list, head);
+                       io_unlink(&iolp->iop);
+
+                       LIST_DEL(&iolp->head);
+                       free(iolp);
+               }
+               break;
+       }
+
+       case IOP_I: {
+               struct io_list *iolp;
+               struct list_head *p, *q;
+
+               list_for_each_safe(p, q, &iop->u.i.i_qs_head) {
+                       iolp = list_entry(p, struct io_list, head);
+                       io_unlink(&iolp->iop);
+
+                       LIST_DEL(&iolp->head);
+                       free(iolp);
+               }
+               break;
+       }
+
+       case IOP_Y:
+               io_unlink(&iop->u.y.y_c1);
+               io_unlink(&iop->u.y.y_c2);
+               break;
+
+       default:
+               ASSERT(0);
+               /*NOTREACHED*/
+       }
+}
diff --git a/btt/list.h b/btt/list.h
new file mode 100644 (file)
index 0000000..b165ecf
--- /dev/null
@@ -0,0 +1,148 @@
+#ifndef _LINUX_LIST_H
+#define _LINUX_LIST_H
+
+/*
+ * These are non-NULL pointers that will result in page faults
+ * under normal circumstances, used to verify that nobody uses
+ * non-initialized list entries.
+ */
+#define LIST_POISON1  ((void *) 0x00100100)
+#define LIST_POISON2  ((void *) 0x00200200)
+
+struct list_head {
+       struct list_head *next, *prev;
+};
+
+#define LIST_HEAD_INIT(name) { &(name), &(name) }
+
+#define LIST_HEAD(name) \
+       struct list_head name = LIST_HEAD_INIT(name)
+
+static inline void INIT_LIST_HEAD(struct list_head *list)
+{
+       list->next = list;
+       list->prev = list;
+}
+
+/*
+ * Insert a new entry between two known consecutive entries.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_add(struct list_head *new,
+                             struct list_head *prev,
+                             struct list_head *next)
+{
+       next->prev = new;
+       new->next = next;
+       new->prev = prev;
+       prev->next = new;
+}
+
+/**
+ * list_add - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it after
+ *
+ * Insert a new entry after the specified head.
+ * This is good for implementing stacks.
+ */
+static inline void list_add(struct list_head *new, struct list_head *head)
+{
+       __list_add(new, head, head->next);
+}
+
+/**
+ * list_add_tail - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it before
+ *
+ * Insert a new entry before the specified head.
+ * This is useful for implementing queues.
+ */
+static inline void list_add_tail(struct list_head *new, struct list_head *head)
+{
+       __list_add(new, head->prev, head);
+}
+
+/*
+ * Delete a list entry by making the prev/next entries
+ * point to each other.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_del(struct list_head * prev, struct list_head * next)
+{
+       next->prev = prev;
+       prev->next = next;
+}
+
+/**
+ * list_del - deletes entry from list.
+ * @entry: the element to delete from the list.
+ * Note: list_empty on entry does not return true after this, the entry is
+ * in an undefined state.
+ */
+static inline void list_del(struct list_head *entry)
+{
+       __list_del(entry->prev, entry->next);
+       entry->next = LIST_POISON1;
+       entry->prev = LIST_POISON2;
+}
+
+/**
+ * __list_for_each     -       iterate over a list
+ * @pos:       the &struct list_head to use as a loop counter.
+ * @head:      the head for your list.
+ *
+ * This variant differs from list_for_each() in that it's the
+ * simplest possible list iteration code, no prefetching is done.
+ * Use this for code that knows the list to be very short (empty
+ * or 1 entry) most of the time.
+ */
+#define __list_for_each(pos, head) \
+       for (pos = (head)->next; pos != (head); pos = pos->next)
+
+/**
+ * list_for_each_safe  -       iterate over a list safe against removal of list entry
+ * @pos:       the &struct list_head to use as a loop counter.
+ * @n:         another &struct list_head to use as temporary storage
+ * @head:      the head for your list.
+ */
+#define list_for_each_safe(pos, n, head) \
+       for (pos = (head)->next, n = pos->next; pos != (head); \
+               pos = n, n = pos->next)
+
+/**
+ * list_entry - get the struct for this entry
+ * @ptr:        the &struct list_head pointer.
+ * @type:       the type of the struct this is embedded in.
+ * @member:     the name of the list_struct within the struct.
+ */
+#define list_entry(ptr, type, member) \
+        container_of(ptr, type, member)
+
+static inline int list_len(struct list_head *head_p)
+{
+       struct list_head *p;
+       int n = 0;
+
+       __list_for_each(p, head_p) {
+               n++;
+       }
+
+       return n;
+}
+
+/**
+ * list_empty - tests whether a list is empty
+ * @head: the list to test.
+ */
+static inline int list_empty(const struct list_head *head)
+{
+       return head->next == head;
+}
+
+#endif
diff --git a/btt/misc.c b/btt/misc.c
new file mode 100644 (file)
index 0000000..8659db7
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * blktrace output analysis: generate a timeline & gather statistics
+ *
+ * Copyright (C) 2006 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
+ *
+ */
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#define INLINE_DECLARE
+#include "globals.h"
+
+int data_is_native = -1;
+
+struct blk_io_trace *convert_to_cpu(struct blk_io_trace *t)
+{
+       if (data_is_native == -1)
+               check_data_endianness(t->magic);
+
+       trace_to_cpu(t);
+
+       ASSERT(CHECK_MAGIC(t));
+       ASSERT((t->magic & 0xff) == SUPPORTED_VERSION);
+
+       return t;
+}
+
+int in_devices(struct blk_io_trace *t)
+{
+       int i;
+       unsigned int mjr, mnr;
+       char *p = devices;
+
+       if (p == NULL) return 1;        /* Allow anything */
+
+       for (;;) {
+               i = sscanf(p, "%u,%u;", &mjr, &mnr);
+               ASSERT(i == 2);
+
+               if ((mjr == MAJOR(t->device) && (mnr == MINOR(t->device))))
+                       return 1;
+
+               p = strchr(p, ';');
+               if (!p)
+                       break;
+               p++;
+       }
+
+       return 0;
+}
+
+unsigned int do_read(int ifd, void *buf, int len)
+{
+       int n;
+
+       n = read(ifd, buf, len);
+       if (n < 0) {
+               perror(input_name);
+               return 1;
+       }
+       else if (0 < n && n < len) {
+               fprintf(stderr,"Short read on %s\n", input_name);
+               return 1;
+       }
+       else if (n == 0) /* EOF */
+               return 1;
+
+       return 0;
+}
diff --git a/btt/output.c b/btt/output.c
new file mode 100644 (file)
index 0000000..aa0605c
--- /dev/null
@@ -0,0 +1,592 @@
+/*
+ * blktrace output analysis: generate a timeline & gather statistics
+ *
+ * Copyright (C) 2006 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
+ *
+ */
+#include <stdio.h>
+#include "globals.h"
+
+typedef struct avg_info *ai_dip_t;
+ai_dip_t dip_q2q_avg(struct d_info *dip) { return &dip->avgs.q2q; }
+ai_dip_t dip_q2c_avg(struct d_info *dip) { return &dip->avgs.q2c; }
+ai_dip_t dip_q2a_avg(struct d_info *dip) { return &dip->avgs.q2a; }
+ai_dip_t dip_q2i_avg(struct d_info *dip) { return &dip->avgs.q2i; }
+ai_dip_t dip_i2d_avg(struct d_info *dip) { return &dip->avgs.i2d; }
+ai_dip_t dip_d2c_avg(struct d_info *dip) { return &dip->avgs.d2c; }
+
+typedef struct avg_info *ai_pip_t;
+ai_pip_t pip_q2q_avg(struct p_info *pip) { return &pip->avgs.q2q; }
+ai_pip_t pip_q2c_avg(struct p_info *pip) { return &pip->avgs.q2c; }
+ai_pip_t pip_q2a_avg(struct p_info *pip) { return &pip->avgs.q2a; }
+ai_pip_t pip_q2i_avg(struct p_info *pip) { return &pip->avgs.q2i; }
+ai_pip_t pip_i2d_avg(struct p_info *pip) { return &pip->avgs.i2d; }
+ai_pip_t pip_d2c_avg(struct p_info *pip) { return &pip->avgs.d2c; }
+
+void output_section_hdr(FILE *ofp, char *hdr)
+{
+       fprintf(ofp, "==================== ");
+       fprintf(ofp, hdr);
+       fprintf(ofp, " ====================\n\n");
+}
+
+void output_hdr(FILE *ofp, char *hdr)
+{
+       fprintf(ofp, "%12s %13s %13s %13s %11s\n",
+               hdr, "MIN", "AVG", "MAX", "N" );
+       fprintf(ofp, "------------ ------------- ------------- ------------- -----------\n");
+}
+
+void __output_avg(FILE *ofp, char *hdr, struct avg_info *ap)
+{
+       if (ap->n > 0) {
+               ap->avg = BIT_TIME(ap->total) / (double)ap->n;
+               fprintf(ofp, "%-12s %13.9f %13.9f %13.9f %11d\n", hdr,
+                       BIT_TIME(ap->min), ap->avg, BIT_TIME(ap->max), ap->n);
+       }
+}
+
+void output_hdr2(FILE *ofp, char*hdr)
+{
+       fprintf(ofp, "%12s %13s %13s %13s %13s %13s %13s\n", hdr, "Q2Q", "Q2A", "Q2I", "I2D", "D2C", "Q2C");
+       fprintf(ofp, "------------ ------------- ------------- ------------- ------------- ------------- -------------\n");
+}
+
+static inline char *avg2string(struct avg_info *ap, char *string)
+{
+       if (ap->n > 0)
+               sprintf(string, "%13.9f", ap->avg);
+       else
+               sprintf(string, " ");
+       return string;
+}
+
+void __output_avg2(FILE *ofp, char *hdr, struct avgs_info *ap)
+{
+       char c1[16], c2[16], c3[16], c4[16], c5[16], c6[16];
+
+       if (ap->q2q.n > 0 || ap->q2a.n > 0 || ap->q2i.n > 0 ||
+                       ap->i2d.n > 0 || ap->d2c.n > 0 || ap->q2c.n > 0) {
+               fprintf(ofp, "%-12s %13s %13s %13s %13s %13s %13s\n", hdr,
+                       avg2string(&ap->q2q,c1), avg2string(&ap->q2a,c2),
+                       avg2string(&ap->q2i,c3), avg2string(&ap->i2d,c4),
+                       avg2string(&ap->d2c,c5), avg2string(&ap->q2c,c6));
+       }
+}
+
+char *make_dev_hdr(char *pad, size_t len, struct d_info *dip)
+{
+       if (dip->map == NULL) {
+               snprintf(pad, len, "(%3d,%3d)", 
+                        MAJOR(dip->device), MINOR(dip->device));
+       }
+       else {
+               snprintf(pad, len, "[%3d,%3d]",
+                       dip->map->host, dip->map->target);
+       }
+
+       return pad;
+}
+
+void __output_dip_avg(FILE *ofp, struct d_info *dip, struct avg_info *ap)
+{
+       if (ap->n > 0) {
+               char dev_info[12];
+               ap->avg = BIT_TIME(ap->total) / (double)ap->n;
+               __output_avg(ofp, make_dev_hdr(dev_info, 12, dip), ap);
+       }
+}
+
+void output_dip_avg(FILE *ofp, char *hdr, ai_dip_t (*func)(struct d_info *))
+{
+       struct d_info *dip;
+
+       output_hdr(ofp, hdr);
+       if (devices == NULL) {
+               struct list_head *p;
+
+               __list_for_each(p, &all_devs) {
+                       dip = list_entry(p, struct d_info, head);
+                       __output_dip_avg(ofp, dip, func(dip));
+               }
+       }
+       else {
+               int i;
+               unsigned int mjr, mnr;
+               char *p = devices;
+
+               while (p && ((i = sscanf(p, "%u,%u", &mjr, &mnr)) == 2)) {
+                       dip = __dip_find((__u32)((mjr << MINORBITS) | mnr));
+                       __output_dip_avg(ofp, dip, func(dip));
+
+                       p = strchr(p, ';');
+                       if (p) p++;
+               }
+       }
+
+       fprintf(ofp, "\n");
+}
+
+void __output_dip_merge_ratio(FILE *ofp, struct d_info *dip)
+{
+       double blks_avg;
+       char scratch[12];
+       double ratio, q2c_n = dip->avgs.q2c.n, d2c_n = dip->n_ds;
+
+       if (q2c_n > 0.0 && d2c_n > 0.0) {
+               ratio = q2c_n / d2c_n;
+               blks_avg = (double)dip->avgs.blks.total /
+                                                       (double)dip->avgs.d2c.n;
+               fprintf(ofp, "%10s | %8llu %8llu %7.1lf | %8llu %8llu %8llu %8llu\n",
+                       make_dev_hdr(scratch, 12, dip),
+                       (unsigned long long)dip->avgs.q2c.n,
+                       (unsigned long long)dip->n_ds,
+                       ratio,
+                       (unsigned long long)dip->avgs.blks.min,
+                       (unsigned long long)blks_avg,
+                       (unsigned long long)dip->avgs.blks.max,
+                       (unsigned long long)dip->avgs.blks.total);
+
+       }
+}
+
+void output_dip_merge_ratio(FILE *ofp)
+{
+       struct d_info *dip;
+
+       fprintf(ofp, "%10s | %8s %8s %7s | %8s %8s %8s %8s\n", "DEV", "#Q", "#D", "Ratio", "BLKmin", "BLKavg", "BLKmax", "Total");
+       fprintf(ofp, "---------- | -------- -------- ------- | -------- -------- -------- --------\n");
+       if (devices == NULL) {
+               struct list_head *p;
+
+               __list_for_each(p, &all_devs) {
+                       dip = list_entry(p, struct d_info, head);
+                       __output_dip_merge_ratio(ofp, dip);
+               }
+       }
+       else {
+               int i;
+               unsigned int mjr, mnr;
+               char *p = devices;
+
+               while (p && ((i = sscanf(p, "%u,%u", &mjr, &mnr)) == 2)) {
+                       dip = __dip_find((__u32)((mjr << MINORBITS) | mnr));
+                       __output_dip_merge_ratio(ofp, dip);
+
+                       p = strchr(p, ';');
+                       if (p) p++;
+               }
+       }
+
+       fprintf(ofp, "\n");
+}
+
+#define AVG(a,b) (100.0 * ((double)(a) / (double)(b)))
+#define CALC_AVG(ap) (ap)->avg = ((ap)->n == 0 ? 0.0 :                   \
+                                                 (BIT_TIME((ap)->total) / \
+                                                       (double)(ap)->n))
+char *q2i_v_q2C(struct d_info *dip, char *s)
+{
+       double q2c;
+
+       if (dip->avgs.q2i.n == 0) return " ";
+
+       q2c = dip->avgs.q2i.avg + dip->avgs.i2d.avg + dip->avgs.d2c.avg;
+       sprintf(s, "%5.1lf%%", AVG(dip->avgs.q2i.avg, q2c));
+
+       return s;
+}
+
+char *i2d_v_q2C(struct d_info *dip, char *s)
+{
+       double q2c;
+
+       if (dip->avgs.i2d.n == 0) return " ";
+
+       q2c = dip->avgs.q2i.avg + dip->avgs.i2d.avg + dip->avgs.d2c.avg;
+       sprintf(s, "%5.1lf%%", AVG(dip->avgs.i2d.avg, q2c));
+
+       return s;
+}
+
+char *d2c_v_q2C(struct d_info *dip, char *s)
+{
+       double q2c;
+
+       if (dip->avgs.i2d.n == 0) return " ";
+
+       q2c = dip->avgs.q2i.avg + dip->avgs.i2d.avg + dip->avgs.d2c.avg;
+       sprintf(s, "%5.1lf%%", AVG(dip->avgs.d2c.avg, q2c));
+
+       return s;
+}
+
+void __output_dip_prep_ohead(FILE *ofp, struct d_info *dip)
+{
+       char dev_info[12];
+       char s1[16], s2[16], s3[16];
+
+       if ((dip->avgs.q2i.n > 0 && dip->avgs.i2d.n > 0 &&
+                                               dip->avgs.d2c.n > 0)) {
+               CALC_AVG(&dip->avgs.q2i);
+               CALC_AVG(&dip->avgs.i2d);
+               CALC_AVG(&dip->avgs.d2c);
+
+               fprintf(ofp, "%10s | %6s %6s %6s\n",
+                       make_dev_hdr(dev_info, 12, dip),
+                       q2i_v_q2C(dip, s1), i2d_v_q2C(dip, s2),
+                       d2c_v_q2C(dip, s3));
+       }
+}
+
+void output_dip_prep_ohead(FILE *ofp)
+{
+       struct d_info *dip;
+
+       fprintf(ofp, "%10s | %6s %6s %6s\n", "DEV", "Q2I", "I2D", "D2C");
+       fprintf(ofp, "---------- | ------ ------ ------\n");
+
+       if (devices == NULL) {
+               struct list_head *p;
+
+               __list_for_each(p, &all_devs) {
+                       dip = list_entry(p, struct d_info, head);
+                       __output_dip_prep_ohead(ofp, dip);
+               }
+       }
+       else {
+               int i;
+               unsigned int mjr, mnr;
+               char *p = devices;
+
+               while (p && ((i = sscanf(p, "%u,%u", &mjr, &mnr)) == 2)) {
+                       dip = __dip_find((__u32)((mjr << MINORBITS) | mnr));
+                       __output_dip_prep_ohead(ofp, dip);
+
+                       p = strchr(p, ';');
+                       if (p) p++;
+               }
+       }
+
+       fprintf(ofp, "\n");
+}
+
+void __output_pip_avg(FILE *ofp, struct p_info *pip, struct avg_info *ap)
+{
+       if (ap->n > 0) {
+               char proc_name[12];
+               snprintf(proc_name, 12, pip->name);
+
+               ap->avg = BIT_TIME(ap->total) / (double)ap->n;
+               __output_avg(ofp, proc_name, ap);
+       }
+}
+
+void output_pip_avg(FILE *ofp, char *hdr, ai_pip_t (*func)(struct p_info *))
+{
+       struct p_info *pip;
+
+       output_hdr(ofp, hdr);
+       if (exes == NULL) {
+               struct list_head *p;
+
+               __list_for_each(p, &all_procs) {
+                       pip = list_entry(p, struct p_info, head);
+                       __output_pip_avg(ofp, pip, func(pip));
+               }
+       }
+       else {
+               char *exe, *p, *next, *exes_save = strdup(exes);
+
+               p = exes_save;
+               while (exes_save != NULL) {
+                       exe = exes_save;
+                       if ((next = strchr(exes_save, ',')) != NULL) {
+                               *next = '\0';
+                               exes_save = next+1;
+                       }
+                       else
+                               exes_save = NULL;
+
+                       pip = find_process((__u32)-1, exe);
+                       if (pip)
+                               __output_pip_avg(ofp, pip, func(pip));
+               }
+       }
+
+       fprintf(ofp, "\n");
+}
+
+void output_dip_avgs(FILE *ofp)
+{
+       char dev_info[12];
+       struct d_info *dip;
+
+       output_hdr2(ofp,"Dev");
+       if (devices == NULL) {
+               struct list_head *p;
+
+               __list_for_each(p, &all_devs) {
+                       dip = list_entry(p, struct d_info, head);
+                       __output_avg2(ofp, make_dev_hdr(dev_info, 12, dip),
+                                       &dip->avgs);
+               }
+       }
+       else {
+               int i;
+               unsigned int mjr, mnr;
+               char *p = devices;
+
+               while (p && ((i = sscanf(p, "%u,%u", &mjr, &mnr)) == 2)) {
+                       dip = __dip_find((__u32)((mjr << MINORBITS) | mnr));
+                       __output_avg2(ofp, make_dev_hdr(dev_info, 12, dip),
+                                       &dip->avgs);
+
+                       p = strchr(p, ';');
+                       if (p) p++;
+               }
+       }
+
+       fprintf(ofp, "\n");
+}
+
+void output_pip_avgs(FILE *ofp)
+{
+       char exe[16];
+       struct p_info *pip;
+
+       output_hdr2(ofp,"Exe");
+       if (exes == NULL) {
+               struct list_head *p;
+
+               __list_for_each(p, &all_procs) {
+                       pip = list_entry(p, struct p_info, head);
+                       snprintf(exe, 12, pip->name);
+                       __output_avg2(ofp, exe, &pip->avgs);
+               }
+       }
+       else {
+               char *exe, *p, *next, *exes_save = strdup(exes);
+
+               p = exes_save;
+               while (exes_save != NULL && *exes_save != '\0') {
+                       exe = exes_save;
+                       if ((next = strchr(exes_save, ',')) != NULL) {
+                               *next = '\0';
+                               exes_save = next+1;
+                       }
+                       else
+                               exes_save = NULL;
+
+                       pip = find_process((__u32)-1, exe);
+                       if (pip) {
+                               snprintf(exe, 12, pip->name);
+                               __output_avg2(ofp, exe, &pip->avgs);
+                       }
+               }
+       }
+
+       fprintf(ofp, "\n");
+}
+
+int output_avgs(FILE *ofp)
+{
+       if (exes == NULL || *exes != '\0') {
+               output_section_hdr(ofp, "Per Process");
+               output_pip_avg(ofp, "Q2Q", pip_q2q_avg);
+               output_pip_avg(ofp, "Q2A", pip_q2a_avg);
+               output_pip_avg(ofp, "Q2I", pip_q2i_avg);
+               output_pip_avg(ofp, "I2D", pip_i2d_avg);
+               output_pip_avg(ofp, "D2C", pip_d2c_avg);
+               output_pip_avg(ofp, "Q2C", pip_q2c_avg);
+       }
+
+       output_section_hdr(ofp, "Per Device");
+       output_dip_avg(ofp, "Q2Q", dip_q2q_avg);
+       output_dip_avg(ofp, "Q2A", dip_q2a_avg);
+       output_dip_avg(ofp, "Q2I", dip_q2i_avg);
+       output_dip_avg(ofp, "I2D", dip_i2d_avg);
+       output_dip_avg(ofp, "D2C", dip_d2c_avg);
+       output_dip_avg(ofp, "Q2C", dip_q2c_avg);
+
+       output_section_hdr(ofp, "All Devices");
+       output_hdr(ofp, "ALL");
+       __output_avg(ofp, "Q2Q", &all_avgs.q2q);
+       __output_avg(ofp, "Q2A", &all_avgs.q2a);
+       __output_avg(ofp, "Q2I", &all_avgs.q2i);
+       __output_avg(ofp, "I2D", &all_avgs.i2d);
+       __output_avg(ofp, "D2C", &all_avgs.d2c);
+       __output_avg(ofp, "Q2C", &all_avgs.q2c);
+
+       if (exes == NULL || *exes != '\0') {
+               output_section_hdr(ofp, "Per Process (avgs)");
+               output_pip_avgs(ofp);
+       }
+
+       output_section_hdr(ofp, "Per Device (avgs)");
+       output_dip_avgs(ofp);
+
+       output_section_hdr(ofp, "Device Merge Information");
+       output_dip_merge_ratio(ofp);
+
+       output_section_hdr(ofp, "Device Overhead");
+       output_dip_prep_ohead(ofp);
+
+       return 0;
+}
+
+void __output_ranges(FILE *ofp, struct list_head *head_p, float base)
+{
+       struct range_info *rip;
+       struct list_head *p;
+       float limit = base + 0.4;
+
+       __list_for_each(p, head_p) {
+               rip = list_entry(p, struct range_info, head);
+               fprintf(ofp, "%13.9lf %5.1f\n", BIT_TIME(rip->start), base);
+               fprintf(ofp, "%13.9lf %5.1f\n", BIT_TIME(rip->start), limit);
+               fprintf(ofp, "%13.9lf %5.1f\n", BIT_TIME(rip->end), limit);
+               fprintf(ofp, "%13.9lf %5.1f\n", BIT_TIME(rip->end), base);
+       }
+}
+
+int output_regions(FILE *ofp, char *header, struct region_info *reg, 
+                         float base)
+{
+       if (reg->qr_cur != NULL)
+               list_add_tail(&reg->qr_cur->head, &reg->qranges);
+       if (reg->cr_cur != NULL)
+               list_add_tail(&reg->cr_cur->head, &reg->cranges);
+
+       if (list_len(&reg->qranges) == 0 && list_len(&reg->cranges) == 0)
+               return 0;
+
+       fprintf(ofp, "# %16s : q activity\n", header);
+       __output_ranges(ofp, &reg->qranges, base);
+       fprintf(ofp, "\n");
+
+       fprintf(ofp, "# %16s : c activity\n", header);
+       __output_ranges(ofp, &reg->cranges, base + 0.5);
+       fprintf(ofp, "\n");
+
+       return 1;
+}
+
+float __output_dev(FILE *ofp, struct d_info *dip, float base)
+{
+       char header[128];
+       sprintf(header, "%d,%d", MAJOR(dip->device), MINOR(dip->device));
+       if (output_regions(ofp, header, &dip->regions, base))
+               base += 1.0;
+
+       return base;
+}
+
+float output_devs(FILE *ofp, float base)
+{
+       struct d_info *dip;
+
+       fprintf(ofp, "# Per device\n" );
+       if (devices == NULL) {
+               struct list_head *p;
+               __list_for_each(p, &all_devs) {
+                       dip = list_entry(p, struct d_info, head);
+                       base = __output_dev(ofp, dip, base);
+               }
+       }
+       else {
+               int i;
+               unsigned int mjr, mnr;
+               char *p = devices;
+
+               while (p && ((i = sscanf(p, "%u,%u", &mjr, &mnr)) == 2)) {
+                       dip = __dip_find((__u32)((mjr << MINORBITS) | mnr));
+                       ASSERT(dip);
+
+                       base = __output_dev(ofp, dip, base);
+
+                       p = strchr(p, ';');
+                       if (p) p++;
+               }
+       }
+
+       return base;
+}
+
+static inline int exe_match(char *exe, char *name)
+{
+       return (exe == NULL) || (strstr(name, exe) != NULL);
+}
+
+float __output_procs(FILE *ofp, float base, char *match)
+{
+       struct p_info *pip;
+       struct list_head *p;
+
+       __list_for_each(p, &all_procs) {
+               pip = list_entry(p, struct p_info, head);
+
+               if (exe_match(match, pip->name) &&
+                               output_regions(ofp, pip->name,
+                                              &pip->regions, base))
+                       base += 1.0;
+       }
+
+       return base;
+}
+
+float output_procs(FILE *ofp, float base)
+{
+       fprintf(ofp, "# Per process\n" );
+       if (exes == NULL)
+               base = __output_procs(ofp, base, NULL);
+       else {
+               char *exe, *next, *p, *exes_save = strdup(exes);
+
+               p = exes_save;
+               while (exes_save != NULL) {
+                       exe = exes_save;
+                       if ((next = strchr(exes_save, ',')) != NULL) {
+                               *next = '\0';
+                               exes_save = next+1;
+                       }
+                       else
+                               exes_save = NULL;
+
+                       base = __output_procs(ofp, base, exe);
+               }
+               free(p);
+       }
+
+       return base;
+}
+
+int output_ranges(FILE *ofp)
+{
+       float base = 0.0;
+
+       fprintf(ofp, "# %s\n", "Total System");
+       if (output_regions(ofp, "Total System", &all_regions, base))
+               base += 1.0;
+
+       if (n_devs > 1)
+               base = output_devs(ofp, base);
+
+       base = output_procs(ofp, base);
+
+       return 0;
+}
diff --git a/btt/proc.c b/btt/proc.c
new file mode 100644 (file)
index 0000000..42cd417
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * blktrace output analysis: generate a timeline & gather statistics
+ *
+ * Copyright (C) 2006 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
+ *
+ */
+#include <string.h>
+
+#include "globals.h"
+
+#define N_PID_HASH     128
+#define PID_HASH(pid)  ((pid) & (N_PID_HASH-1))
+struct list_head       *pid_heads = NULL;
+
+static void init_pid_heads(void)
+{
+       int i;
+
+       pid_heads = zmalloc(N_PID_HASH * sizeof(struct list_head));
+       for (i = 0; i < N_PID_HASH; i++)
+               INIT_LIST_HEAD(&pid_heads[i]);
+}
+
+static inline struct p_info *pid_to_proc(__u32 pid)
+{
+       struct p_pid *pp;
+       struct list_head *p, *head;
+
+       if (pid_heads == NULL) init_pid_heads();
+
+       head = &pid_heads[PID_HASH(pid)];
+       __list_for_each(p, head) {
+               pp = list_entry(p, struct p_pid, head);
+               if (pp->pid == pid)
+                       return pp->pip;
+       }
+
+       return NULL;
+}
+
+void insert_proc_hash(struct p_info *pip, __u32 pid)
+{
+       struct p_pid *pp = zmalloc(sizeof(*pp));
+
+       if (pid_heads == NULL) init_pid_heads();
+
+       pp->pip = pip;
+       pp->pid = pid;
+
+       list_add_tail(&pp->head, &pid_heads[PID_HASH(pid)]);
+}
+
+int __find_process_pid(__u32 pid)
+{
+       return pid_to_proc(pid) != NULL;
+}
+
+struct p_info *find_process(__u32 pid, char *name)
+{
+       struct p_info *pip;
+       struct list_head *p;
+
+       if (pid != ((__u32)-1) && (pip = pid_to_proc(pid)))
+               return pip;
+
+       if (name) {
+               __list_for_each(p, &all_procs) {
+                       pip = list_entry(p, struct p_info, head);
+                       if (name && !strcmp(pip->name, name)) {
+                               if (pid != ((__u32)-1))
+                                       insert_proc_hash(pip, pid);
+                               return pip;
+                       }
+               }
+       }
+
+       return NULL;
+}
+
+void add_process(__u32 pid, char *name)
+{
+       struct p_info *pip = find_process(pid, name);
+
+       if (pip == NULL) {
+               pip = zmalloc(sizeof(*pip));
+
+               list_add_tail(&pip->head, &all_procs);
+               insert_proc_hash(pip, pid);
+               pip->last_q = (__u64)-1;
+               pip->name = strdup(name);
+               init_region(&pip->regions);
+       }
+}
+
+void pip_update_q(struct io *iop)
+{
+       if (iop->pip) {
+               update_lq(&iop->pip->last_q, &iop->pip->avgs.q2q, iop->t.time);
+               update_qregion(&iop->pip->regions, iop->t.time);
+       }
+}
diff --git a/btt/trace.c b/btt/trace.c
new file mode 100644 (file)
index 0000000..00501ba
--- /dev/null
@@ -0,0 +1,412 @@
+/*
+ * blktrace output analysis: generate a timeline & gather statistics
+ *
+ * Copyright (C) 2006 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
+ *
+ */
+#include "globals.h"
+
+static inline void release_iop(struct io *iop)
+{
+       if (iop->pdu) free(iop->pdu);
+       IO_FREE(iop);
+}
+
+struct io *dip_find_exact(struct list_head *head, struct io *iop_in)
+{
+       struct io *iop;
+       struct list_head *p;
+
+       __list_for_each(p, head) {
+               iop = list_entry(p, struct io, dev_head);
+               if (is_bit(iop_in, iop))
+                       return iop;
+       }
+       return NULL;
+}
+
+struct io *dip_find_in(struct list_head *head, struct io *iop_in)
+{
+       struct io *iop;
+       struct list_head *p;
+
+       __list_for_each(p, head) {
+               iop = list_entry(p, struct io, dev_head);
+               if (in_bit(iop, iop_in))
+                       return iop;
+       }
+       return NULL;
+}
+
+struct io *dip_find_start(struct list_head *head, __u64 sector)
+{
+       struct io *iop;
+       struct list_head *p;
+
+       __list_for_each(p, head) {
+               iop = list_entry(p, struct io, dev_head);
+               if (BIT_START(iop) == sector)
+                       return iop;
+       }
+       return NULL;
+}
+
+struct io *dip_find_end(struct list_head *head, __u64 sector)
+{
+       struct io *iop;
+       struct list_head *p;
+
+       __list_for_each(p, head) {
+               iop = list_entry(p, struct io, dev_head);
+               if (BIT_END(iop) == sector)
+                       return iop;
+       }
+       return NULL;
+}
+
+struct io *dip_find_in_sec(struct list_head *head, __u64 sector)
+{
+       struct io *iop;
+       struct list_head *p;
+
+       __list_for_each(p, head) {
+               iop = list_entry(p, struct io, dev_head);
+               if (BIT_START(iop) <= sector && sector <= BIT_END(iop))
+                       return iop;
+       }
+       return NULL;
+}
+
+struct io *dip_find_qa(struct list_head *head, struct blk_io_trace *t)
+{
+       struct io *iop;
+       struct list_head *p;
+
+       __list_for_each(p, head) {
+               iop = list_entry(p, struct io, dev_head);
+               if (iop->t.cpu == t->cpu && iop->t.sequence == (t->sequence-1))
+                       return iop;
+       }
+       return NULL;
+}
+
+void dip_add_ms(struct list_head *head, struct io *d_iop)
+{
+       struct io *m_iop;
+       struct list_head *p;
+       struct io_list *iolp;
+
+       __list_for_each(p, head) {
+               m_iop = list_entry(p, struct io, dev_head);
+               if (in_bit(m_iop, d_iop)) {
+                       iolp = malloc(sizeof(*iolp));
+                       io_link(&iolp->iop, m_iop);
+                       list_add_tail(&iolp->head, &d_iop->u.d.d_im_head);
+               }
+       }
+}
+
+void dip_add_qs(struct list_head *head, struct io *i_iop)
+{
+       struct io *q_iop;
+       struct list_head *p;
+       struct io_list *iolp;
+
+       __list_for_each(p, head) {
+               q_iop = list_entry(p, struct io, dev_head);
+               if (in_bit(q_iop, i_iop)) {
+                       iolp = malloc(sizeof(*iolp));
+                       io_link(&iolp->iop, q_iop);
+                       list_add_tail(&iolp->head, &i_iop->u.i.i_qs_head);
+               }
+       }
+}
+
+void handle_queue(struct io *iop)
+{
+       struct io *tmp;
+
+       io_setup(iop, IOP_Q);
+
+       update_lq(&last_q, &all_avgs.q2q, iop->t.time);
+       update_qregion(&all_regions, iop->t.time);
+       dip_update_q(iop->dip, iop);
+       pip_update_q(iop);
+
+       tmp = dip_find_exact(dip_get_head(iop->dip, IOP_A), iop);
+       if (tmp) {
+               iop->u.q.qp_type = Q_A;
+               io_link(&iop->u.q.qp.q_a, tmp);
+       }
+       else
+               iop->u.q.qp_type = Q_NONE;
+
+#if defined(LVM_REMAP_WORKAROUND)
+       if (is_lvm) {
+               tmp = dip_find_qa(dip_get_head(iop->dip, IOP_A), &iop->t);
+               if (tmp) {
+                       iop->u.q.qp_type = Q_A;
+                       io_link(&iop->u.q.qp.q_a, tmp);
+               }
+       }
+#endif
+}
+
+void handle_merge(struct io *iop)
+{
+       struct io *q_iop;
+
+       io_setup(iop, IOP_M);
+
+       q_iop = dip_find_exact(dip_get_head(iop->dip, IOP_Q), iop);
+       if (q_iop)
+               io_link(&iop->u.m.m_q, q_iop);
+}
+
+void handle_insert(struct io *iop)
+{
+       struct io_list *iolp = malloc(sizeof(*iolp));
+
+       io_setup(iop, IOP_I);
+       INIT_LIST_HEAD(&iop->u.i.i_qs_head);
+       dip_add_qs(dip_get_head(iop->dip, IOP_Q), iop);
+}
+
+void handle_complete(struct io *iop)
+{
+       struct io *d_iop;
+
+       io_setup(iop, IOP_C);
+       update_blks(iop);
+       update_cregion(&all_regions, iop->t.time);
+       update_cregion(&iop->dip->regions, iop->t.time);
+       if (iop->pip)
+               update_cregion(&iop->pip->regions, iop->t.time);
+
+       d_iop = dip_find_exact(dip_get_head(iop->dip, IOP_D), iop);
+       if (d_iop) {
+               io_link(&iop->u.c.c_d, d_iop);
+
+               add_cy(iop);
+       }
+       else
+               io_free(iop);
+}
+
+void handle_issue(struct io *iop)
+{
+       struct io *i_iop;
+       struct io_list *iolp = malloc(sizeof(*iolp));
+
+       io_setup(iop, IOP_D);
+       iop->dip->n_ds++;
+
+       INIT_LIST_HEAD(&iop->u.d.d_im_head);
+       i_iop = dip_find_in(dip_get_head(iop->dip, IOP_I), iop);
+       if (i_iop) {
+               io_link(&iolp->iop, i_iop);
+               list_add_tail(&iolp->head, &iop->u.d.d_im_head);
+       }
+
+       dip_add_ms(dip_get_head(iop->dip, IOP_M), iop);
+}
+
+void handle_split(struct io *iop)
+{
+       struct io *q_iop;
+
+       pending_xs++;
+       io_setup(iop, IOP_X);
+
+       q_iop = dip_find_exact(dip_get_head(iop->dip, IOP_Q), iop);
+       if (q_iop)
+               io_link(&iop->u.x.x_q, q_iop);
+}
+
+#if 0
+void __x_add_c(struct io *y_iop, struct io *x_iop,
+              struct blk_io_trace_split_end *rp, int which)
+{
+       __u32 dev;
+       __u64 sector;
+       struct d_info *dip;
+       struct io **y_cp, *q_iop, *c_iop;
+
+       if (which == 1) {
+               y_cp = &y_iop->u.y.y_c1;
+               dev = be32_to_cpu(rp->dev1);
+               sector = be64_to_cpu(rp->sector1);
+       }
+       else {
+               y_cp = &y_iop->u.y.y_c2;
+               dev = be32_to_cpu(rp->dev2);
+               sector = be64_to_cpu(rp->sector2);
+       }
+
+       dip = __dip_find(dev);
+       ASSERT(dip != NULL);
+
+       q_iop = dip_find_end(dip_get_head(dip, IOP_Q), sector);
+       if (q_iop) {
+               q_iop->u.q.qp_type = Q_X;
+               io_link(&q_iop->u.q.qp.q_x, x_iop);
+       }
+
+       c_iop = dip_find_in_sec(dip_get_head(dip, IOP_C), sector);
+       if (c_iop)
+               io_link(y_cp, c_iop);
+}
+
+void handle_split_end(struct io *iop)
+{
+       struct io *x_iop;
+       struct blk_io_trace_split_end *rp = iop->pdu;
+
+       pending_xs--;
+       io_setup(iop, IOP_Y);
+
+       x_iop = dip_find_exact(dip_get_head(iop->dip, IOP_X), iop);
+       if (x_iop) {
+               __x_add_c(iop, x_iop, rp, 1);
+               __x_add_c(iop, x_iop, rp, 2);
+
+               rem_c(iop->u.y.y_c1);
+               rem_c(iop->u.y.y_c2);
+
+               add_cy(iop);
+       }
+       else
+               release_iop(iop);
+}
+#endif
+
+void handle_remap(struct io *iop)
+{
+       struct io *q_iop;
+       struct blk_io_trace_remap *rp = iop->pdu;
+       __u32 dev = be32_to_cpu(rp->device);
+       __u64 sector = be64_to_cpu(rp->sector);
+
+       q_iop = dip_find_in_sec(dip_get_head_dev(dev, IOP_Q), sector);
+       if (q_iop) {
+               io_setup(iop, IOP_A);
+
+#if defined(LVM_REMAP_WORKAROUND)
+               if (is_lvm) {
+                       sector = iop->t.sector;
+                       iop->t.sector = be64_to_cpu(rp->sector);
+               }
+#endif
+               io_link(&iop->u.a.a_q, q_iop);
+       }
+       else
+               release_iop(iop);
+}
+
+void extract_i(struct io *i_iop)
+{
+       struct io_list *iolp;
+       struct list_head *p, *q;
+
+       ASSERT(i_iop != NULL && i_iop->type == IOP_I);
+       list_for_each_safe(p, q, &i_iop->u.i.i_qs_head) {
+               iolp = list_entry(p, struct io_list, head);
+               LIST_DEL(&iolp->head);
+
+               ASSERT(iolp->iop->type == IOP_Q);
+               (void)__io_put(iolp->iop);
+
+               free(iolp);
+       }
+}
+
+/*
+ * Careful surgery
+ * (1) Need to remove D & its I & M's
+ * (2) Need to leave I's Q and M's Q's -- *no* io_put (__io_put instead)
+ */
+void handle_requeue(struct io *iop)
+{
+       struct io *d_iop;
+       struct io_list *iolp;
+       struct list_head *p, *q;
+
+       d_iop = dip_find_start(dip_get_head_dev(iop->t.device, IOP_D),
+                              iop->t.sector);
+       if (d_iop) {
+               list_for_each_safe(p, q, &d_iop->u.d.d_im_head) {
+                       iolp = list_entry(p, struct io_list, head);
+                       LIST_DEL(&iolp->head);
+
+                       if (iolp->iop->type == IOP_M)
+                               (void)__io_put(iolp->iop->u.m.m_q);
+                       else
+                               extract_i(iolp->iop);
+
+                       iolp->iop->users = 0;
+                       io_free(iolp->iop);
+                       free(iolp);
+               }
+
+               d_iop->users = 0;
+               io_free(d_iop);
+       }
+
+       release_iop(iop);
+
+}
+
+void __add_trace(struct io *iop)
+{
+       n_traces++;
+
+       if (verbose && (n_traces % 10000) == 0) {
+               printf("%10lu t, %10lu m %1lu f\r",
+                      n_traces, n_io_allocs, n_io_frees);
+               fflush(stdout);
+               if ((n_traces % 1000000) == 0) printf("\n");
+       }
+
+       switch (iop->t.action & 0xffff) {
+       case __BLK_TA_QUEUE:            handle_queue(iop);      break;
+       case __BLK_TA_BACKMERGE:        handle_merge(iop);      break;
+       case __BLK_TA_FRONTMERGE:       handle_merge(iop);      break;
+       case __BLK_TA_ISSUE:            handle_issue(iop);      break;
+       case __BLK_TA_COMPLETE:         handle_complete(iop);   break;
+       case __BLK_TA_INSERT:           handle_insert(iop);     break;
+       case __BLK_TA_SPLIT:            handle_split(iop);      break;
+#if 0
+       case __BLK_TA_SPLIT_END:        handle_split_end(iop);  break;
+#endif
+       case __BLK_TA_REMAP:            handle_remap(iop);      break;
+       case __BLK_TA_REQUEUE:          handle_requeue(iop);    break;
+       }
+}
+
+void add_trace(struct io *iop)
+{
+       if (iop->t.action & BLK_TC_ACT(BLK_TC_NOTIFY)) {
+               char *slash = strchr(iop->pdu, '/');
+
+               if (slash)
+                       *slash = '\0';
+               add_process(iop->t.pid, iop->pdu);
+               release_iop(iop);
+       }
+       else
+               __add_trace(iop);
+}
diff --git a/btt/traverse.c b/btt/traverse.c
new file mode 100644 (file)
index 0000000..0c76414
--- /dev/null
@@ -0,0 +1,150 @@
+/*
+ * blktrace output analysis: generate a timeline & gather statistics
+ *
+ * Copyright (C) 2006 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
+ *
+ */
+#include "globals.h"
+
+typedef struct io *iop_t;
+typedef iop_t (*iop_func_t)(__u64 *, struct io*);
+
+struct io *iop_q_func(__u64 *timeline, struct io *iop);
+struct io *iop_x_func(__u64 *timeline, struct io *iop);
+struct io *iop_a_func(__u64 *timeline, struct io *iop);
+struct io *iop_m_func(__u64 *timeline, struct io *iop);
+struct io *iop_i_func(__u64 *timeline, struct io *iop);
+struct io *iop_d_func(__u64 *timeline, struct io *iop);
+struct io *iop_c_func(__u64 *timeline, struct io *iop);
+struct io *iop_y_func(__u64 *timeline, struct io *iop);
+
+iop_func_t traverse_func[] = {
+       iop_q_func,
+       iop_x_func,
+       iop_a_func,
+       iop_m_func,
+       iop_i_func,
+       iop_d_func,
+       iop_c_func,
+       iop_y_func,
+};
+
+void __traverse(__u64 *timeline, struct io *iop)
+{
+       while (iop != NULL && !iop->traversed) {
+               iop->traversed++;
+               iop = traverse_func[iop->type](timeline, iop);
+       }
+}
+
+void traverse(struct io *iop)
+{
+       int i;
+       __u64 timeline[N_IOP_TYPES];
+
+       for (i = 0; i < N_IOP_TYPES; i++)
+               timeline[i] = 0.0;
+
+       __traverse(timeline, iop);
+}
+
+void iop_q_update(__u64 *timeline, struct io *iop, __u64 q_time)
+{
+       update_q2c(iop, timeline[IOP_C] - q_time);
+
+       if (timeline[IOP_A] > 0.0)      // IOP_X too
+               update_q2a(iop, timeline[IOP_A] - q_time);
+       else                            //IOP_M too
+               update_q2i(iop, timeline[IOP_I] - q_time);
+
+       update_i2d(iop, timeline[IOP_D] - timeline[IOP_I]);
+       update_d2c(iop, timeline[IOP_C] - timeline[IOP_D]);
+}
+
+struct io *iop_q_func(__u64 *timeline, struct io *iop)
+{
+       timeline[IOP_Q] = iop->t.time;
+       iop_q_update(timeline, iop, iop->t.time);
+
+       if (iop->u.q.qp_type == Q_A)
+               return iop->u.q.qp.q_a;
+       else if (iop->u.q.qp_type == Q_X)
+               return iop->u.q.qp.q_x;
+
+       /* Q_NONE */
+       return NULL;
+}
+
+struct io *iop_x_func(__u64 *timeline, struct io *iop)
+{
+       timeline[IOP_A] = iop->t.time;  // Cover X & A in one slice
+       return iop->u.x.x_q;
+}
+
+struct io *iop_a_func(__u64 *timeline, struct io *iop)
+{
+       timeline[IOP_A] = iop->t.time;
+       return iop->u.a.a_q;
+}
+
+struct io *iop_m_func(__u64 *timeline, struct io *iop)
+{
+       timeline[IOP_I] = iop->t.time;  // Cover M & I in one slice
+       return iop->u.m.m_q;
+}
+
+struct io *iop_i_func(__u64 *timeline, struct io *iop)
+{
+       struct list_head *p;
+       struct io_list *iolp;
+
+       timeline[IOP_I] = iop->t.time;
+       __list_for_each(p, &iop->u.i.i_qs_head) {
+               iolp = list_entry(p, struct io_list, head);
+               __traverse(timeline, iolp->iop);
+       }
+
+       return NULL;
+}
+
+struct io *iop_d_func(__u64 *timeline, struct io *iop)
+{
+       struct list_head *p;
+       struct io_list *iolp;
+
+       timeline[IOP_D] = iop->t.time;
+       __list_for_each(p, &iop->u.d.d_im_head) {
+               iolp = list_entry(p, struct io_list, head);
+               __traverse(timeline, iolp->iop);
+       }
+
+       return NULL;
+}
+
+struct io *iop_c_func(__u64 *timeline, struct io *iop)
+{
+       timeline[IOP_C] = iop->t.time;
+       return iop->u.c.c_d;
+}
+
+struct io *iop_y_func(__u64 *timeline, struct io *iop)
+{
+       timeline[IOP_Y] = iop->t.time;
+       __traverse(timeline, iop->u.y.y_c1);
+       __traverse(timeline, iop->u.y.y_c2);
+       return NULL;
+}