biolatency
diff --git a/tools/biolatency b/tools/biolatency
new file mode 100755
index 0000000..9a6b38a
--- /dev/null
+++ b/tools/biolatency
@@ -0,0 +1,120 @@
+#!/usr/bin/python
+#
+# biolatency	Summarize block device I/O latency as a histogram.
+#		For Linux, uses BCC, eBPF.
+#
+# USAGE: biolatency [-h] [-T] [-Q] [-m] [interval] [count]
+#
+# Copyright (c) 2015 Brendan Gregg.
+# Licensed under the Apache License, Version 2.0 (the "License")
+#
+# 20-Sep-2015	Brendan Gregg	Created this.
+
+from __future__ import print_function
+from bcc import BPF
+from time import sleep, strftime
+import argparse
+
+# arguments
+examples = """examples:
+    ./biolatency            # summarize block I/O latency as a histogram
+    ./biolatency 1 10       # print 1 second summaries, 10 times
+    ./biolatency -mT 1      # 1s summaries, milliseconds, and timestamps
+    ./biolatency -Q         # include OS queued time in I/O time
+"""
+parser = argparse.ArgumentParser(
+	description="Summarize block device I/O latency as a histogram",
+	formatter_class=argparse.RawDescriptionHelpFormatter,
+	epilog=examples)
+parser.add_argument("-T", "--timestamp", action="store_true",
+	help="include timestamp on output")
+parser.add_argument("-Q", "--queued", action="store_true",
+	help="include OS queued time in I/O time")
+parser.add_argument("-m", "--milliseconds", action="store_true",
+	help="millisecond histogram")
+parser.add_argument("interval", nargs="?", default=99999999,
+	help="output interval, in seconds")
+parser.add_argument("count", nargs="?", default=99999999,
+	help="number of outputs")
+args = parser.parse_args()
+countdown = int(args.count)
+debug = 0
+
+# load BPF program
+bpf_text = """
+#include <uapi/linux/ptrace.h>
+#include <linux/blkdev.h>
+
+BPF_TABLE(\"array\", int, u64, dist, 64);
+BPF_HASH(start, struct request *);
+
+// time block I/O
+int trace_req_start(struct pt_regs *ctx, struct request *req)
+{
+	u64 ts = bpf_ktime_get_ns();
+	start.update(&req, &ts);
+	return 0;
+}
+
+// output
+int trace_req_completion(struct pt_regs *ctx, struct request *req)
+{
+	u64 *tsp, delta;
+
+	// fetch timestamp and calculate delta
+	tsp = start.lookup(&req);
+	if (tsp == 0) {
+		return 0;	// missed issue
+	}
+	delta = bpf_ktime_get_ns() - *tsp;
+	FACTOR
+
+	// store as histogram
+	int index = bpf_log2l(delta);
+	u64 *leaf = dist.lookup(&index);
+	if (leaf) (*leaf)++;
+
+	start.delete(&req);
+	return 0;
+}
+"""
+if args.milliseconds:
+	bpf_text = bpf_text.replace('FACTOR', 'delta /= 1000000;')
+	label = "msecs"
+else:
+	bpf_text = bpf_text.replace('FACTOR', 'delta /= 1000;')
+	label = "usecs"
+if debug:
+	print(bpf_text)
+
+# load BPF program
+b = BPF(text=bpf_text)
+if args.queued:
+	b.attach_kprobe(event="blk_account_io_start", fn_name="trace_req_start")
+else:
+	b.attach_kprobe(event="blk_start_request", fn_name="trace_req_start")
+	b.attach_kprobe(event="blk_mq_start_request", fn_name="trace_req_start")
+b.attach_kprobe(event="blk_account_io_completion",
+    fn_name="trace_req_completion")
+
+print("Tracing block device I/O... Hit Ctrl-C to end.")
+
+# output
+exiting = 0 if args.interval else 1
+dist = b.get_table("dist")
+while (1):
+	try:
+		sleep(int(args.interval))
+	except KeyboardInterrupt:
+		exiting=1
+
+	print()
+	if args.timestamp:
+		print("%-8s\n" % strftime("%H:%M:%S"), end="")
+
+	dist.print_log2_hist(label)
+	dist.clear()
+
+	countdown -= 1
+	if exiting or countdown == 0:
+		exit()