CC = gcc
-MYFLAGS = -DLVM_REMAP_WORKAROUND
CFLAGS = -Wall -O2 -W -g
#CFLAGS = -Wall -g -W -UDO_INLINE -DDEBUG
ALL_CFLAGS = $(CFLAGS) -I.. -D_GNU_SOURCE -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64
#ELIBS = -lefence
#PLIBS = -lpthread
LIBS = $(PLIBS) $(ELIBS)
-OBJS = bt_timeline.o args.o output.o proc.o trace.o misc.o devs.o \
- devmap.o seek.o iostat.o latency.o dip_rb.o rbtree.o
+OBJS = args.o bt_timeline.o devmap.o devs.o dip_rb.o iostat.o latency.o \
+ misc.o output.o proc.o seek.o trace.o trace_complete.o trace_im.o \
+ trace_issue.o trace_queue.o trace_remap.o trace_requeue.o rbtree.o
all: depend $(PROGS)
[ -l <output name> | --d2c-latencies=<output name> ]
[ -M <dev map> | --dev-maps=<dev map>
[ -o <output name> | --output-file=<output name> ]
+ [ -p <output name> | --per-io-dump=<output name> ]
[ -q <output name> | --q2c-latencies=<output name> ]
[ -s <output name> | --seeks=<output name> ]
[ -S <interval> | --iostat-interval=<interval> ]
The -M option takes in a file generated by the provided script
(gen_disk_info.py), and allows for better output of device names.
+The -p option will generate a file that contains a list of all IO
+"sequences" - showing the parts of each IO (Q, A, I/M, D, & C).
+
The -s option instructs btt to output seek data, the argument provided
is the basis for file names output. There are two files per device,
read seeks and write seeks.
#include <fcntl.h>
#include "globals.h"
-#define S_OPTS "d:D:e:hi:I:l:M:o:q:s:S:Vv"
+#define S_OPTS "d:D:e:hi:I:l:M:o:p:q:s:S:Vv"
static struct option l_opts[] = {
{
.name = "range-delta",
.flag = NULL,
.val = 'o'
},
+ {
+ .name = "per-io-dump",
+ .has_arg = required_argument,
+ .flag = NULL,
+ .val = 'p'
+ },
{
.name = "q2c-latencies",
.has_arg = required_argument,
"[ -l <output name> | --d2c-latencies=<output name> ]\n" \
"[ -M <dev map> | --dev-maps=<dev map>\n" \
"[ -o <output name> | --output-file=<output name> ]\n" \
+ "[ -p <output name> | --per-io-dump=<output name> ]\n" \
"[ -q <output name> | --q2c-latencies=<output name> ]\n" \
"[ -s <output name> | --seeks=<output name> ]\n" \
"[ -S <interval> | --iostat-interval=<interval> ]\n" \
case 'o':
output_name = strdup(optarg);
break;
+ case 'p':
+ per_io_name = strdup(optarg);
+ break;
case 'q':
q2c_name = strdup(optarg);
break;
exit(1);
}
}
+
+ if (per_io_name != NULL) {
+ per_io_ofp = fopen(per_io_name, "w");
+ if (per_io_ofp == NULL) {
+ perror(per_io_name);
+ exit(1);
+ }
+ }
}
char bt_timeline_version[] = "0.99";
char *devices, *exes, *input_name, *output_name, *seek_name;
-char *d2c_name, *q2c_name;
+char *d2c_name, *q2c_name, *per_io_name;
double range_delta = 0.1;
-FILE *ranges_ofp, *avgs_ofp;
+FILE *ranges_ofp, *avgs_ofp, *per_io_ofp;
int ifd, verbose = 0;
unsigned long n_traces;
struct avgs_info all_avgs;
return NULL;
}
-struct d_info *dip_add(__u32 device, struct io *iop, int link)
+struct d_info *dip_add(__u32 device, struct io *iop)
{
struct d_info *dip = __dip_find(device);
n_devs++;
}
- if (link)
- dip_rb_ins(dip, iop);
-
+ iop->linked = dip_rb_ins(dip, iop);
return dip;
}
dip_rb_fe(iop->dip, type, iop, fnc, NULL);
}
+void dip_foreach_list(struct io *iop, enum iop_type type, struct list_head *hd)
+{
+ dip_rb_fe(iop->dip, type, iop, NULL, hd);
+}
+
struct io *dip_find_sec(struct d_info *dip, enum iop_type type, __u64 sec)
{
return dip_rb_find_sec(dip, type, sec);
#include <stdio.h>
#include "globals.h"
-void rb_insert(struct rb_root *root, struct io *iop)
+int rb_insert(struct rb_root *root, struct io *iop)
{
struct io *__iop;
struct rb_node *parent = NULL;
else if (s > __s)
p = &(*p)->rb_right;
else
- p = &(*p)->rb_right;
+ return 0;
}
rb_link_node(&iop->rb_node, parent, p);
rb_insert_color(&iop->rb_node, root);
+ return 1;
}
struct io *rb_find_sec(struct rb_root *root, __u64 sec)
IOP_Q = 0,
IOP_X = 1,
IOP_A = 2,
- IOP_M = 3,
- IOP_I = 4,
- IOP_D = 5,
- IOP_C = 6,
- IOP_R = 7,
+ IOP_L = 3, // Betwen-device linkage
+ IOP_M = 4,
+ IOP_I = 5,
+ IOP_D = 6,
+ IOP_C = 7,
+ IOP_R = 8,
};
#define N_IOP_TYPES (IOP_R + 1)
struct blk_io_trace t;
void *pdu;
enum iop_type type;
- int linked;
+
+ struct list_head down_head, up_head, c_pending, retry;
+ struct list_head down_list, up_list;
+ __u64 bytes_left;
+ int run_ready, linked, self_remap, displayed;
};
/* bt_timeline.c */
extern char bt_timeline_version[], *devices, *exes, *input_name, *output_name;
-extern char *seek_name, *iostat_name, *d2c_name, *q2c_name;
+extern char *seek_name, *iostat_name, *d2c_name, *q2c_name, *per_io_name;
extern double range_delta;
-extern FILE *ranges_ofp, *avgs_ofp, *iostat_ofp;
-extern int verbose, ifd;
+extern FILE *ranges_ofp, *avgs_ofp, *iostat_ofp, *per_io_ofp;;
+extern int verbose, ifd, dump_level;
extern unsigned int n_devs;
extern unsigned long n_traces;
-extern struct list_head all_devs, all_procs;
+extern struct list_head all_devs, all_procs, retries;
extern struct avgs_info all_avgs;
extern __u64 last_q;
extern struct region_info all_regions;
/* devs.c */
void init_dev_heads(void);
-struct d_info *dip_add(__u32 device, struct io *iop, int link);
+struct d_info *dip_add(__u32 device, struct io *iop);
void dip_rem(struct io *iop);
struct d_info *__dip_find(__u32 device);
+void dip_foreach_list(struct io *iop, enum iop_type type, struct list_head *hd);
void dip_foreach(struct io *iop, enum iop_type type,
void (*fnc)(struct io *iop, struct io *this), int rm_after);
struct io *dip_find_sec(struct d_info *dip, enum iop_type type, __u64 sec);
void dip_foreach_out(void (*func)(struct d_info *, void *), void *arg);
/* dip_rb.c */
-void rb_insert(struct rb_root *root, struct io *iop);
+int rb_insert(struct rb_root *root, struct io *iop);
struct io *rb_find_sec(struct rb_root *root, __u64 sec);
void rb_foreach(struct rb_node *n, struct io *iop,
void (*fnc)(struct io *iop, struct io *this),
int seeki_mode(void *handle, struct mode *mp);
/* trace.c */
+void dump_iop(FILE *ofp, struct io *to_iop, struct io *from_iop, int indent);
+void release_iops(struct list_head *del_head);
void add_trace(struct io *iop);
+/* trace_complete.c */
+void trace_complete(struct io *c_iop);
+int retry_complete(struct io *c_iop);
+int ready_complete(struct io *c_iop, struct io *top);
+void run_complete(struct io *c_iop);
+
+/* trace_im.c */
+void trace_insert(struct io *i_iop);
+void trace_merge(struct io *m_iop);
+int ready_im(struct io *im_iop, struct io *top);
+void run_im(struct io *im_iop, struct io *top, struct list_head *del_head);
+void run_unim(struct io *im_iop, struct list_head *del_head);
+
+/* trace_issue.c */
+void trace_issue(struct io *d_iop);
+int ready_issue(struct io *d_iop, struct io *top);
+void run_issue(struct io *d_iop, struct io *top, struct list_head *del_head);
+void run_unissue(struct io *d_iop, struct list_head *del_head);
+
+/* trace_queue.c */
+void trace_queue(struct io *q_iop);
+int ready_queue(struct io *q_iop, struct io *top);
+void run_queue(struct io *q_iop, struct io *top, struct list_head *del_head);
+void run_unqueue(struct io *q_iop, struct list_head *del_head);
+
+/* trace_remap.c */
+void trace_remap(struct io *a_iop);
+int ready_remap(struct io *a_iop, struct io *top);
+void run_remap(struct io *a_iop, struct io *top, struct list_head *del_head);
+void run_unremap(struct io *a_iop, struct list_head *del_head);
+
+/* trace_requeue.c */
+void trace_requeue(struct io *r_iop);
+int retry_requeue(struct io *r_iop);
+int ready_requeue(struct io *r_iop, struct io *top);
+void run_requeue(struct io *r_iop);
+
#include "inlines.h"
if (*cur_p == NULL)
*cur_p = new_cur(time);
else {
- __u64 my_delta = time - (*cur_p)->end;
+ __u64 my_delta = (time > (*cur_p)->end) ? time - (*cur_p)->end : 1;
if (BIT_TIME(my_delta) >= range_delta) {
list_add_tail(&(*cur_p)->head, head_p);
*cur_p = new_cur(time);
static inline void update_lq(__u64 *last_q, struct avg_info *avg, __u64 time)
{
if (*last_q != ((__u64)-1))
- avg_update(avg, time - *last_q);
+ avg_update(avg, (time > *last_q) ? time - *last_q : 1);
*last_q = time;
}
else
iop = malloc(sizeof(struct io));
- return memset(iop, 0, sizeof(struct io));
+ memset(iop, 0, sizeof(struct io));
+
+ return iop;
}
static inline void io_free(struct io *iop)
{
+# if defined(DEBUG)
+ memset(iop, 0, sizeof(*iop));
+# endif
list_add_tail(&iop->f_head, &free_ios);
}
-static inline void io_setup(struct io *iop, enum iop_type type, int link)
+static inline int io_setup(struct io *iop, enum iop_type type)
{
iop->type = type;
- iop->dip = dip_add(iop->t.device, iop, link);
- iop->pip = find_process(iop->t.pid, NULL);
- iop->linked = link;
+ iop->dip = dip_add(iop->t.device, iop);
+ if (iop->linked) {
+ iop->pip = find_process(iop->t.pid, NULL);
+ INIT_LIST_HEAD(&iop->down_list);
+ INIT_LIST_HEAD(&iop->up_list);
+ iop->bytes_left = iop->t.bytes;
+ }
+
+ return iop->linked;
}
static inline void io_release(struct io *iop)
{
- if (iop->linked) {
+ if (iop->linked)
dip_rem(iop);
- iop->linked = 0;
- }
if (iop->pdu)
free(iop->pdu);
io_free(iop);
return memset(malloc(len), 0, len);
}
-static inline void dip_rb_ins(struct d_info *dip, struct io *iop)
+static inline int dip_rb_ins(struct d_info *dip, struct io *iop)
{
- rb_insert(__get_root(dip, iop->type), iop);
+ return rb_insert(__get_root(dip, iop->type), iop);
}
static inline void dip_rb_rem(struct io *iop)
{
return rb_find_sec(__get_root(dip, type), sec);
}
+
+static inline struct io *list_first_down(struct io *iop)
+{
+ struct list_head *p = list_first(&iop->down_list);
+ return p ? list_entry(p, struct io, up_head) : NULL;
+}
+
+static inline struct io *list_first_up(struct io *iop)
+{
+ struct list_head *p = list_first(&iop->up_list);
+ return p ? list_entry(p, struct io, down_head) : NULL;
+}
+
+static inline int list_empty_up(struct io *iop)
+{
+ return list_empty(&iop->up_list);
+}
+
+static inline void __link(struct io *down_iop, struct io *up_iop)
+{
+ list_add_tail(&down_iop->up_head, &up_iop->down_list);
+ list_add_tail(&up_iop->down_head, &down_iop->up_list);
+}
+
+static inline void __unlink(struct io *down_iop, struct io *up_iop)
+{
+ LIST_DEL(&down_iop->up_head);
+ LIST_DEL(&up_iop->down_head);
+}
+
+static inline void add_retry(struct io *iop)
+{
+ list_add_tail(&iop->retry, &retries);
+}
+
+static inline void del_retry(struct io *iop)
+{
+ LIST_DEL(&iop->retry);
+}
+
+static inline __u64 tdelta(struct io *iop1, struct io *iop2)
+{
+ __u64 t1 = iop1->t.time;
+ __u64 t2 = iop2->t.time;
+ return (t1 < t2) ? (t2 - t1) : 1;
+}
void im2d2c_func(struct io *c_iop, struct io *im_iop)
{
- ADD_STAT(c_iop->dip, wait, c_iop->t.time - im_iop->t.time);
+ ADD_STAT(c_iop->dip, wait, tdelta(im_iop, c_iop));
}
void iostat_init(void)
update_idle_time(dip, now, 0);
DEC_STAT(dip, cur_dev);
- ADD_STAT(dip, svctm, c_iop->t.time - d_iop->t.time);
+ ADD_STAT(dip, svctm, tdelta(d_iop, c_iop));
}
return head->next == head;
}
+/**
+ * list_first - Returns first entry on list, or NULL if empty
+ * @head: the list
+ */
+static inline struct list_head *list_first(const struct list_head *head)
+{
+ return list_empty(head) ? NULL : head->next;
+}
+
+/**
+ * list_move_tail - delete from one list and add as another's tail
+ * @list: the entry to move
+ * @head: the head that will follow our entry
+ */
+static inline void list_move_tail(struct list_head *list,
+ struct list_head *head)
+{
+ __list_del(list->prev, list->next);
+ list_add_tail(list, head);
+}
+
#endif
void output_hdr(FILE *ofp, char *hdr)
{
- fprintf(ofp, "%12s %13s %13s %13s %11s\n",
+ fprintf(ofp, "%15s %13s %13s %13s %11s\n",
hdr, "MIN", "AVG", "MAX", "N" );
- fprintf(ofp, "------------ ------------- ------------- ------------- -----------\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,
+ fprintf(ofp, "%-15s %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");
+ fprintf(ofp, "%15s %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->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,
+ fprintf(ofp, "%-15s %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));
void __dip_output_avg2(struct d_info *dip, void *arg)
{
- char dev_info[12];
- __output_avg2((FILE *)arg, make_dev_hdr(dev_info, 12, dip), &dip->avgs);
+ char dev_info[15];
+ __output_avg2((FILE *)arg, make_dev_hdr(dev_info, 15, dip), &dip->avgs);
}
char *make_dev_hdr(char *pad, size_t len, struct d_info *dip)
struct __oda *odap = arg;
ai_dip_t ap = odap->func(dip);
if (ap->n > 0) {
- char dev_info[12];
+ char dev_info[15];
ap->avg = BIT_TIME(ap->total) / (double)ap->n;
- __output_avg(odap->ofp, make_dev_hdr(dev_info, 12, dip), ap);
+ __output_avg(odap->ofp, make_dev_hdr(dev_info, 15, dip), ap);
}
}
void __output_dip_merge_ratio(struct d_info *dip, void *arg)
{
double blks_avg;
- char scratch[12];
+ char scratch[15];
double ratio, q2c_n = dip->avgs.q2c.n, d2c_n = dip->n_ds;
if (q2c_n > 0.0 && d2c_n > 0.0) {
blks_avg = (double)dip->avgs.blks.total / d2c_n;
fprintf((FILE *)arg,
"%10s | %8llu %8llu %7.1lf | %8llu %8llu %8llu %8llu\n",
- make_dev_hdr(scratch, 12, dip),
+ make_dev_hdr(scratch, 15, dip),
(unsigned long long)dip->avgs.q2c.n,
(unsigned long long)dip->n_ds,
ratio,
void __output_dip_prep_ohead(struct d_info *dip, void *arg)
{
- char dev_info[12];
+ char dev_info[15];
char s1[16], s2[16], s3[16];
if ((dip->avgs.q2i.n > 0 && dip->avgs.i2d.n > 0 &&
CALC_AVG(&dip->avgs.d2c);
fprintf((FILE *)arg, "%10s | %6s %6s %6s\n",
- make_dev_hdr(dev_info, 12, dip),
+ make_dev_hdr(dev_info, 15, dip),
q2i_v_q2C(dip, s1), i2d_v_q2C(dip, s2),
d2c_v_q2C(dip, s3));
}
double mean;
int i, nmodes;
long long nseeks;
- char dev_info[12];
+ char dev_info[15];
long long median;
struct mode m;
FILE *ofp = arg;
nmodes = seeki_mode(dip->seek_handle, &m);
fprintf(ofp, "%10s | %15lld %15.1lf %15lld | %lld(%d)",
- make_dev_hdr(dev_info, 12, dip), nseeks, mean, median,
+ make_dev_hdr(dev_info, 15, dip), nseeks, mean, median,
nmodes > 0 ? m.modes[0] : 0, m.most_seeks);
for (i = 1; i < nmodes; i++)
fprintf(ofp, " %lld", m.modes[i]);
ai_pip_t ap = opap->func(pip);
if (ap->n > 0) {
- char proc_name[12];
- snprintf(proc_name, 12, pip->name);
+ char proc_name[15];
+ snprintf(proc_name, 15, pip->name);
ap->avg = BIT_TIME(ap->total) / (double)ap->n;
__output_avg(opap->ofp, proc_name, ap);
*/
#include "globals.h"
-void im2d_func(struct io *d_iop, struct io *im_iop)
-{
- update_i2d(im_iop, d_iop->t.time - im_iop->t.time);
-}
+int dump_level;
+LIST_HEAD(retries);
-void q2c_func(struct io *c_iop, struct io *q_iop)
+static inline void dump_dev(FILE *ofp, __u32 dev)
{
- __u64 q2c = c_iop->t.time - q_iop->t.time;
-
- update_q2c(q_iop, q2c);
- latency_q2c(q_iop->dip, q_iop->t.time, q2c);
+ fprintf(ofp, "%3d,%-3d ", MAJOR(dev), MINOR(dev));
}
-static inline void handle_im(struct io *im_iop)
+static inline void dump_desc(FILE *ofp, struct io *iop)
{
- struct io *q_iop;
-
- q_iop = dip_find_sec(im_iop->dip, IOP_Q, BIT_START(im_iop));
- if (q_iop)
- update_q2i(q_iop, im_iop->t.time - q_iop->t.time);
+ fprintf(ofp, "%10llu+%-4u ", (unsigned long long)iop->t.sector,
+ t_sec(&iop->t));
}
-void handle_queue(struct io *q_iop)
+void dump_iop(FILE *ofp, struct io *to_iop, struct io *from_iop, int indent)
{
- io_setup(q_iop, IOP_Q, 1);
- update_lq(&last_q, &all_avgs.q2q, q_iop->t.time);
- update_qregion(&all_regions, q_iop->t.time);
- dip_update_q(q_iop->dip, q_iop);
- pip_update_q(q_iop);
-}
+ int i, c;
-void handle_remap(struct io *a_iop)
-{
- struct io *q_iop;
- struct blk_io_trace_remap *rp = a_iop->pdu;
- struct d_info *dip = __dip_find(be32_to_cpu(rp->device));
-
- io_setup(a_iop, IOP_A, 0);
- if (dip) {
- q_iop = dip_find_sec(dip, IOP_Q, be64_to_cpu(rp->sector));
- if (q_iop)
- update_q2a(q_iop, a_iop->t.time - q_iop->t.time);
- }
- io_release(a_iop);
-}
-
-void handle_insert(struct io *i_iop)
-{
- io_setup(i_iop, IOP_I, 1);
- iostat_insert(i_iop);
- handle_im(i_iop);
-}
+ if (!ofp) return;
+ if (to_iop->displayed) return;
-void handle_merge(struct io *m_iop)
-{
- io_setup(m_iop, IOP_M, 1);
- iostat_merge(m_iop);
- handle_im(m_iop);
-}
+ fprintf(ofp, "%5d.%09lu ", (int)SECONDS(to_iop->t.time),
+ (unsigned long)NANO_SECONDS(to_iop->t.time));
-void handle_issue(struct io *d_iop)
-{
- io_setup(d_iop, IOP_D, 1);
- d_iop->dip->n_ds++;
+ for (i = 0; i < ((dump_level * 4) + indent); i++)
+ fprintf(ofp, " ");
- dip_foreach(d_iop, IOP_I, im2d_func, 0);
- dip_foreach(d_iop, IOP_M, im2d_func, 0);
+ dump_dev(ofp, to_iop->t.device);
- if (seek_name)
- seeki_add(d_iop->dip->seek_handle, d_iop);
- iostat_issue(d_iop);
-}
+ switch (to_iop->type) {
+ case IOP_Q: c = 'Q'; break;
+ case IOP_L: c = 'L'; break;
+ case IOP_A: c = 'A'; break;
+ case IOP_I: c = 'I'; break;
+ case IOP_M: c = 'M'; break;
+ case IOP_D: c = 'D'; break;
+ case IOP_C: c = 'C'; break;
+ default : c = '?'; break;
+ }
-void handle_complete(struct io *c_iop)
-{
- struct io *d_iop;
-
- io_setup(c_iop, IOP_C, 0);
- update_blks(c_iop);
- update_cregion(&all_regions, c_iop->t.time);
- update_cregion(&c_iop->dip->regions, c_iop->t.time);
- if (c_iop->pip)
- update_cregion(&c_iop->pip->regions, c_iop->t.time);
-
- d_iop = dip_find_sec(c_iop->dip, IOP_D, BIT_START(c_iop));
- if (d_iop) {
- __u64 d2c = c_iop->t.time - d_iop->t.time;
- update_d2c(d_iop, d2c);
- latency_d2c(d_iop->dip, c_iop->t.time, d2c);
- iostat_complete(d_iop, c_iop);
- dip_foreach(d_iop, IOP_I, NULL, 1);
- dip_foreach(d_iop, IOP_M, NULL, 1);
- io_release(d_iop);
+ fprintf(ofp, "%c ", c);
+ dump_desc(ofp, to_iop);
+ if (from_iop) {
+ fprintf(ofp, "<- ");
+ dump_dev(ofp, from_iop->t.device);
+ dump_desc(ofp, from_iop);
}
+
+ fprintf(ofp, "\n");
- dip_foreach(c_iop, IOP_Q, q2c_func, 1);
- io_release(c_iop);
+ to_iop->displayed = 1;
}
-void rq_im2d_func(struct io *d_iop, struct io *im_iop)
+void release_iops(struct list_head *del_head)
{
- unupdate_i2d(im_iop, d_iop->t.time - im_iop->t.time);
+ struct io *x_iop;
+ struct list_head *p, *q;
+
+ list_for_each_safe(p, q, del_head) {
+ x_iop = list_entry(p, struct io, f_head);
+ LIST_DEL(&x_iop->f_head);
+ io_release(x_iop);
+ }
}
-/*
- * Careful surgery
- * (1) Need to remove D & its I & M's
- * (2) Need to leave I's Q and M's Q's
- * (3) XXX: Need to downward adjust stats, but we don't carry PREVIOUS
- * XXX: min/maxes?! We'll just adjust what we can, and hope that
- * XXX: the min/maxes are "pretty close". (REQUEUEs are rare, right?)
- */
-void handle_requeue(struct io *r_iop)
+static void do_retries(void)
{
- struct io *d_iop;
-
- io_setup(r_iop, IOP_R, 0);
- d_iop = dip_find_sec(r_iop->dip, IOP_D, BIT_START(r_iop));
- if (d_iop) {
- dip_foreach(d_iop, IOP_I, rq_im2d_func, 1);
- dip_foreach(d_iop, IOP_M, rq_im2d_func, 1);
- iostat_unissue(d_iop);
- io_release(d_iop);
+ struct io *iop;
+ struct list_head *p, *q;
+
+ list_for_each_safe(p, q, &retries) {
+ iop = list_entry(p, struct io, retry);
+ // iop could be gone after call...
+ if (iop->type == IOP_C)
+ retry_complete(iop);
+ else
+ retry_requeue(iop);
}
- io_release(r_iop);
}
-void __add_trace(struct io *iop)
+static void __add_trace(struct io *iop)
{
time_t now = time(NULL);
}
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_REMAP: handle_remap(iop); break;
- case __BLK_TA_REQUEUE: handle_requeue(iop); break;
- default: io_release(iop); break;
+ case __BLK_TA_QUEUE: trace_queue(iop); break;
+ case __BLK_TA_REMAP: trace_remap(iop); break;
+ case __BLK_TA_INSERT: trace_insert(iop); break;
+ case __BLK_TA_BACKMERGE: trace_merge(iop); break;
+ case __BLK_TA_FRONTMERGE: trace_merge(iop); break;
+ case __BLK_TA_REQUEUE: trace_requeue(iop); break;
+ case __BLK_TA_ISSUE: trace_issue(iop); break;
+ case __BLK_TA_COMPLETE: trace_complete(iop); break;
+ default:
+ io_release(iop);
+ return;
}
+
+ if (((iop->t.action & 0xffff) != __BLK_TA_REQUEUE) &&
+ !list_empty(&retries))
+ do_retries();
}
void add_trace(struct io *iop)
{
+ if (iop->t.time == 15717167961) dbg_ping();
if (iop->t.action & BLK_TC_ACT(BLK_TC_NOTIFY)) {
char *slash = strchr(iop->pdu, '/');
--- /dev/null
+/*
+ * 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"
+
+LIST_HEAD(pending_cs);
+
+static void gen_c_list(struct io *c_iop, struct list_head *c_head)
+{
+ struct io *iop;
+ struct list_head *p;
+
+ __list_for_each(p, &pending_cs) {
+ iop = list_entry(p, struct io, c_pending);
+ if (iop->t.device == c_iop->t.device)
+ continue;
+ if (dip_find_sec(iop->dip, IOP_D, BIT_START(iop)) == NULL)
+ continue;
+
+ __link(iop, c_iop);
+ if (ready_complete(iop, c_iop))
+ list_add_tail(&iop->f_head, c_head);
+ __unlink(iop, c_iop);
+ }
+}
+
+static void run_comp(struct io *c_iop, struct io *top, struct list_head *rmhd)
+{
+ struct io *d_iop = dip_find_sec(c_iop->dip, IOP_D, BIT_START(c_iop));
+
+ update_blks(c_iop);
+ if (d_iop) {
+ __u64 d2c = tdelta(d_iop, c_iop);
+
+ update_d2c(d_iop, d2c);
+ latency_d2c(d_iop->dip, c_iop->t.time, d2c);
+ iostat_complete(d_iop, c_iop);
+
+ __link(d_iop, c_iop);
+ run_issue(d_iop, top, rmhd);
+ __unlink(d_iop, c_iop);
+ }
+ else {
+ LIST_HEAD(head);
+ struct io *iop;
+ struct list_head *p, *q;
+
+ gen_c_list(c_iop, &head);
+ list_for_each_safe(p, q, &head) {
+ iop = list_entry(p, struct io, f_head);
+ LIST_DEL(&iop->f_head);
+
+ dump_level++;
+ __link(iop, c_iop);
+ run_comp(iop, top, rmhd);
+ __unlink(iop, c_iop);
+ dump_level--;
+ }
+ }
+ dump_iop(per_io_ofp, c_iop, NULL, 0);
+ LIST_DEL(&c_iop->c_pending);
+ list_add_tail(&c_iop->f_head, rmhd);
+}
+
+static int ready_comp(struct io *c_iop,
+ __attribute__((__unused__)) struct io *top)
+{
+ LIST_HEAD(head);
+ struct io *iop;
+ struct list_head *p, *q;
+ __u64 bl = c_iop->bytes_left;
+
+ gen_c_list(c_iop, &head);
+ list_for_each_safe(p, q, &head) {
+ iop = list_entry(p, struct io, f_head);
+ LIST_DEL(&iop->f_head);
+
+ __link(iop, c_iop);
+ if (ready_complete(iop, c_iop))
+ bl -= iop->bytes_left;
+ __unlink(iop, c_iop);
+ }
+
+ return bl == 0;
+}
+
+void trace_complete(struct io *c_iop)
+{
+ if (!io_setup(c_iop, IOP_C)) {
+ io_release(c_iop);
+ return;
+ }
+
+ list_add_tail(&c_iop->c_pending, &pending_cs);
+ if (ready_complete(c_iop, c_iop)) {
+ dump_level = 0;
+ run_complete(c_iop);
+ }
+ else
+ add_retry(c_iop);
+}
+
+int retry_complete(struct io *c_iop)
+{
+ if (!ready_complete(c_iop, c_iop))
+ return 0;
+
+ del_retry(c_iop);
+ run_complete(c_iop);
+ return 1;
+}
+
+int ready_complete(struct io *c_iop, struct io *top)
+{
+ struct io *d_iop = dip_find_sec(c_iop->dip, IOP_D, BIT_START(c_iop));
+
+ if (d_iop) {
+ ASSERT(d_iop->t.bytes == c_iop->bytes_left);
+ return ready_issue(d_iop, top);
+ }
+ else
+ return ready_comp(c_iop, top);
+}
+
+void run_complete(struct io *c_iop)
+{
+ LIST_HEAD(rmhd);
+
+ update_cregion(&all_regions, c_iop->t.time);
+ update_cregion(&c_iop->dip->regions, c_iop->t.time);
+ if (c_iop->pip)
+ update_cregion(&c_iop->pip->regions, c_iop->t.time);
+
+ run_comp(c_iop, c_iop, &rmhd);
+ if (per_io_ofp) fprintf(per_io_ofp, "\n");
+ release_iops(&rmhd);
+}
--- /dev/null
+/*
+ * 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"
+
+void trace_insert(struct io *i_iop)
+{
+ if (!io_setup(i_iop, IOP_I)) {
+ io_release(i_iop);
+ return;
+ }
+ iostat_insert(i_iop);
+}
+
+void trace_merge(struct io *m_iop)
+{
+ if (!io_setup(m_iop, IOP_M)) {
+ io_release(m_iop);
+ return;
+ }
+ iostat_merge(m_iop);
+}
+
+int ready_im(struct io *im_iop, struct io *top)
+{
+ struct io *q_iop = dip_find_sec(im_iop->dip, IOP_Q, BIT_START(im_iop));
+
+ if (q_iop) {
+ ASSERT(q_iop->bytes_left >= im_iop->bytes_left);
+ return ready_queue(q_iop, top);
+ }
+
+ return 0;
+}
+
+void run_im(struct io *im_iop, struct io *top, struct list_head *del_head)
+{
+ struct io *q_iop = dip_find_sec(im_iop->dip, IOP_Q, BIT_START(im_iop));
+
+ ASSERT(q_iop);
+ update_q2i(q_iop, tdelta(q_iop, im_iop));
+
+ __link(q_iop, im_iop);
+ run_queue(q_iop, top, del_head);
+ __unlink(q_iop, im_iop);
+
+ dump_iop(per_io_ofp, im_iop, NULL, 0);
+ list_add_tail(&im_iop->f_head, del_head);
+}
+
+void run_unim(struct io *im_iop, struct list_head *del_head)
+{
+ struct io *q_iop = dip_find_sec(im_iop->dip, IOP_Q, BIT_START(im_iop));
+
+ __link(q_iop, im_iop);
+ run_unqueue(q_iop, del_head);
+ __unlink(q_iop, im_iop);
+
+ list_add_tail(&im_iop->f_head, del_head);
+}
--- /dev/null
+/*
+ * 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"
+
+void trace_issue(struct io *d_iop)
+{
+ if (!io_setup(d_iop, IOP_D)) {
+ io_release(d_iop);
+ return;
+ }
+
+ if (seek_name)
+ seeki_add(d_iop->dip->seek_handle, d_iop);
+ iostat_issue(d_iop);
+ d_iop->dip->n_ds++;
+}
+
+int ready_issue(struct io *d_iop, struct io *top)
+{
+ LIST_HEAD(head);
+ struct io *im_iop;
+ struct list_head *p, *q;
+ __u64 bl = d_iop->bytes_left;
+
+ dip_foreach_list(d_iop, IOP_I, &head);
+ dip_foreach_list(d_iop, IOP_M, &head);
+ list_for_each_safe(p, q, &head) {
+ im_iop = list_entry(p, struct io, f_head);
+ LIST_DEL(&im_iop->f_head);
+
+ if (!ready_im(im_iop, top))
+ return 0;
+
+ bl -= im_iop->bytes_left;
+ }
+
+ return bl == 0;
+}
+
+void run_issue(struct io *d_iop, struct io *top, struct list_head *del_head)
+{
+ LIST_HEAD(head);
+ struct list_head *p, *q;
+ struct io *im_iop;
+
+ dip_foreach_list(d_iop, IOP_I, &head);
+ dip_foreach_list(d_iop, IOP_M, &head);
+ list_for_each_safe(p, q, &head) {
+ im_iop = list_entry(p, struct io, f_head);
+ LIST_DEL(&im_iop->f_head);
+
+ update_i2d(im_iop, tdelta(im_iop, d_iop));
+
+ __link(im_iop, d_iop);
+ run_im(im_iop, top, del_head);
+ __unlink(im_iop, d_iop);
+ }
+
+ dump_iop(per_io_ofp, d_iop, NULL, 0);
+ list_add_tail(&d_iop->f_head, del_head);
+}
+
+void run_unissue(struct io *d_iop, struct list_head *del_head)
+{
+ LIST_HEAD(head);
+ struct io *im_iop;
+ struct list_head *p, *q;
+
+ iostat_unissue(d_iop);
+
+ dip_foreach_list(d_iop, IOP_I, &head);
+ dip_foreach_list(d_iop, IOP_M, &head);
+ list_for_each_safe(p, q, &head) {
+ im_iop = list_entry(p, struct io, f_head);
+ LIST_DEL(&im_iop->f_head);
+
+ __link(im_iop, d_iop);
+ unupdate_i2d(im_iop, tdelta(im_iop, d_iop));
+ run_unim(im_iop, del_head);
+ __unlink(im_iop, d_iop);
+ }
+
+ list_add_tail(&d_iop->f_head, del_head);
+}
--- /dev/null
+/*
+ * 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"
+
+void trace_queue(struct io *q_iop)
+{
+ if (!io_setup(q_iop, IOP_Q)) {
+ io_release(q_iop);
+ return;
+ }
+
+ update_lq(&last_q, &all_avgs.q2q, q_iop->t.time);
+ update_qregion(&all_regions, q_iop->t.time);
+ dip_update_q(q_iop->dip, q_iop);
+ pip_update_q(q_iop);
+}
+
+int ready_queue(struct io *q_iop, struct io *top)
+{
+ struct io *a_iop = dip_find_sec(q_iop->dip, IOP_A, BIT_START(q_iop));
+
+ if (a_iop) {
+ ASSERT(a_iop->bytes_left == q_iop->bytes_left);
+ return ready_remap(a_iop, top);
+ }
+
+ return q_iop->t.device == top->t.device &&
+ BIT_START(top) <= BIT_START(q_iop) &&
+ BIT_END(q_iop) <= BIT_END(top);
+}
+
+void run_queue(struct io *q_iop, struct io *top, struct list_head *del_head)
+{
+ struct io *iop;
+ struct io *a_iop = dip_find_sec(q_iop->dip, IOP_A, BIT_START(q_iop));
+
+ if (a_iop) {
+ __link(a_iop, q_iop);
+ run_remap(a_iop, top, del_head);
+ __unlink(a_iop, q_iop);
+ }
+
+ for (iop = q_iop; iop != NULL; iop = list_first_up(iop)) {
+ if (iop->type == IOP_C && iop->t.device == q_iop->t.device) {
+ __u64 q2c = tdelta(q_iop, iop);
+
+ update_q2c(q_iop, q2c);
+ latency_q2c(q_iop->dip, q_iop->t.time, q2c);
+
+ dump_iop(per_io_ofp, q_iop, NULL,
+ (q_iop->t.device == top->t.device) ? -4 : 0);
+
+ break;
+ }
+ }
+
+ iop = list_first_up(q_iop);
+ q_iop->bytes_left -= iop->bytes_left;
+ if (q_iop->bytes_left == 0)
+ list_add_tail(&q_iop->f_head, del_head);
+}
+
+void run_unqueue(struct io *q_iop, struct list_head *del_head)
+{
+ struct io *a_iop = dip_find_sec(q_iop->dip, IOP_A, BIT_START(q_iop));
+
+ if (a_iop) {
+ __link(a_iop, q_iop);
+ run_unremap(a_iop, del_head);
+ __unlink(a_iop, q_iop);
+ }
+
+ list_add_tail(&q_iop->f_head, del_head);
+}
--- /dev/null
+/*
+ * 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"
+
+void trace_remap(struct io *a_iop)
+{
+ struct io *l_iop;
+ struct blk_io_trace_remap *rp = a_iop->pdu;
+ __u32 remap_dev = be32_to_cpu(rp->device);
+ __u64 remap_sec = be64_to_cpu(rp->sector);
+
+ if (!io_setup(a_iop, IOP_A)) {
+ io_release(a_iop);
+ return;
+ }
+
+ /*
+ * Create a fake LINK trace
+ */
+ l_iop = io_alloc();
+ memcpy(&l_iop->t, &a_iop->t, sizeof(a_iop->t));
+ l_iop->t.device = remap_dev;
+ l_iop->t.sector = remap_sec;
+
+ if (!io_setup(l_iop, IOP_L)) {
+ io_release(l_iop);
+ io_release(a_iop);
+ return;
+ }
+
+ __link(l_iop, a_iop);
+ l_iop->self_remap = (MAJOR(a_iop->t.device) == MAJOR(remap_dev));
+}
+
+
+int ready_remap(struct io *a_iop, struct io *top)
+{
+ struct io *l_iop = list_first_down(a_iop);
+ struct blk_io_trace_remap *rp = a_iop->pdu;
+ __u64 remap_sec = be64_to_cpu(rp->sector);
+
+ if (l_iop->self_remap) {
+ struct io *a_iop = dip_find_sec(l_iop->dip, IOP_A, remap_sec);
+ if (a_iop)
+ return ready_remap(a_iop, top);
+ }
+ else {
+ struct io *q_iop = dip_find_sec(l_iop->dip, IOP_Q, remap_sec);
+ if (q_iop)
+ return ready_queue(q_iop, top);
+ }
+
+ return 0;
+}
+
+void run_remap(struct io *a_iop, struct io *top, struct list_head *del_head)
+{
+ struct io *l_iop = list_first_down(a_iop);
+ struct blk_io_trace_remap *rp = a_iop->pdu;
+ __u64 remap_sec = be64_to_cpu(rp->sector);
+
+ if (l_iop->self_remap) {
+ struct io *a2_iop = dip_find_sec(l_iop->dip, IOP_A, remap_sec);
+ ASSERT(a2_iop);
+ __link(a2_iop, l_iop);
+ run_remap(a2_iop, top, del_head);
+ __unlink(a2_iop, l_iop);
+ }
+ else {
+ struct io *q_iop = dip_find_sec(l_iop->dip, IOP_Q, remap_sec);
+ ASSERT(q_iop);
+ __link(q_iop, l_iop);
+ update_q2a(q_iop, tdelta(q_iop, a_iop));
+ run_queue(q_iop, top, del_head);
+ __unlink(q_iop, l_iop);
+ }
+
+ dump_iop(per_io_ofp, a_iop, l_iop, 0);
+
+ __unlink(l_iop, a_iop);
+ list_add_tail(&l_iop->f_head, del_head);
+ list_add_tail(&a_iop->f_head, del_head);
+}
+
+void run_unremap(struct io *a_iop, struct list_head *del_head)
+{
+ struct io *l_iop = list_first_down(a_iop);
+ struct blk_io_trace_remap *rp = a_iop->pdu;
+ __u64 remap_sec = be64_to_cpu(rp->sector);
+
+ if (l_iop->self_remap) {
+ struct io *a_iop = dip_find_sec(l_iop->dip, IOP_A, remap_sec);
+ __link(a_iop, l_iop);
+ run_unremap(a_iop, del_head);
+ __unlink(a_iop, l_iop);
+ }
+ else {
+ struct io *q_iop = dip_find_sec(l_iop->dip, IOP_Q, remap_sec);
+ __link(q_iop, l_iop);
+ run_unqueue(q_iop, del_head);
+ __unlink(q_iop, l_iop);
+ }
+
+ __unlink(l_iop, a_iop);
+ list_add_tail(&l_iop->f_head, del_head);
+ list_add_tail(&a_iop->f_head, del_head);
+}
--- /dev/null
+/*
+ * 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"
+
+void trace_requeue(struct io *r_iop)
+{
+ if (!io_setup(r_iop, IOP_R)) {
+ io_release(r_iop);
+ return;
+ }
+
+ if (ready_requeue(r_iop, r_iop))
+ run_requeue(r_iop);
+ else
+ add_retry(r_iop);
+}
+
+int retry_requeue(struct io *r_iop)
+{
+ if (!ready_requeue(r_iop, r_iop))
+ return 0;
+
+ del_retry(r_iop);
+ run_requeue(r_iop);
+ return 1;
+}
+
+int ready_requeue(struct io *r_iop, struct io *top)
+{
+ struct io *d_iop = dip_find_sec(r_iop->dip, IOP_D, BIT_START(r_iop));
+ if (d_iop)
+ return ready_issue(d_iop, top);
+ return 0;
+}
+
+void run_requeue(struct io *r_iop)
+{
+ LIST_HEAD(del_head);
+ struct io *d_iop = dip_find_sec(r_iop->dip, IOP_D, BIT_START(r_iop));
+
+ __link(d_iop, r_iop);
+ run_unissue(d_iop, &del_head);
+ __unlink(d_iop, r_iop);
+
+ list_add_tail(&r_iop->f_head, &del_head);
+ release_iops(&del_head);
+}