2 * I/O monitor based on block queue trace data
4 * Copyright IBM Corp. 2008
6 * Author(s): Martin Peschke <mp3@de.ibm.com>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 #include <sys/types.h>
45 struct blk_io_trace bit;
52 struct rb_node **node_ptr;
53 struct rb_node *parent;
58 struct blkiomon_stat stat;
74 static char blkiomon_version[] = "0.2";
77 static int interval = -1;
79 static struct trace *vacant_traces_list = NULL;
80 static int vacant_traces = 0;
82 #define TRACE_HASH_SIZE 128
83 struct trace *thash[TRACE_HASH_SIZE] = {};
85 static struct dstat *vacant_dstats_list = NULL;
86 static struct rb_root dstat_tree[2] = { RB_ROOT, RB_ROOT };
87 static struct dstat *dstat_list[2] = {};
88 static int dstat_curr = 0;
90 static struct output drvdata, human, binary, debug;
92 static char *msg_q_name = NULL;
93 static int msg_q_id = -1, msg_q = -1;
94 static long msg_id = -1;
96 static pthread_t interval_thread;
97 static pthread_mutex_t dstat_mutex = PTHREAD_MUTEX_INITIALIZER;
99 int data_is_native = -1;
104 static long leftover = 0, driverdata = 0, match = 0, mismatch = 0, sequence = 0;
106 static void dump_bit(struct trace *t, const char *descr)
108 struct blk_io_trace *bit = &t->bit;
113 fprintf(debug.fp, "--- %s ---\n", descr);
114 fprintf(debug.fp, "magic %16d\n", bit->magic);
115 fprintf(debug.fp, "sequence %16d\n", bit->sequence);
116 fprintf(debug.fp, "time %16ld\n", (unsigned long)bit->time);
117 fprintf(debug.fp, "sector %16ld\n", (unsigned long)bit->sector);
118 fprintf(debug.fp, "bytes %16d\n", bit->bytes);
119 fprintf(debug.fp, "action %16x\n", bit->action);
120 fprintf(debug.fp, "pid %16d\n", bit->pid);
121 fprintf(debug.fp, "device %16d\n", bit->device);
122 fprintf(debug.fp, "cpu %16d\n", bit->cpu);
123 fprintf(debug.fp, "error %16d\n", bit->error);
124 fprintf(debug.fp, "pdu_len %16d\n", bit->pdu_len);
126 fprintf(debug.fp, "order %16ld\n", t->sequence);
129 static void dump_bits(struct trace *t1, struct trace *t2, const char *descr)
131 struct blk_io_trace *bit1 = &t1->bit;
132 struct blk_io_trace *bit2 = &t2->bit;
137 fprintf(debug.fp, "--- %s ---\n", descr);
138 fprintf(debug.fp, "magic %16d %16d\n", bit1->magic, bit2->magic);
139 fprintf(debug.fp, "sequence %16d %16d\n",
140 bit1->sequence, bit2->sequence);
141 fprintf(debug.fp, "time %16ld %16ld\n",
142 (unsigned long)bit1->time, (unsigned long)bit2->time);
143 fprintf(debug.fp, "sector %16ld %16ld\n",
144 (unsigned long)bit1->sector, (unsigned long)bit2->sector);
145 fprintf(debug.fp, "bytes %16d %16d\n", bit1->bytes, bit2->bytes);
146 fprintf(debug.fp, "action %16x %16x\n", bit1->action, bit2->action);
147 fprintf(debug.fp, "pid %16d %16d\n", bit1->pid, bit2->pid);
148 fprintf(debug.fp, "device %16d %16d\n", bit1->device, bit2->device);
149 fprintf(debug.fp, "cpu %16d %16d\n", bit1->cpu, bit2->cpu);
150 fprintf(debug.fp, "error %16d %16d\n", bit1->error, bit2->error);
151 fprintf(debug.fp, "pdu_len %16d %16d\n", bit1->pdu_len, bit2->pdu_len);
153 fprintf(debug.fp, "order %16ld %16ld\n", t1->sequence, t2->sequence);
156 static struct dstat *blkiomon_alloc_dstat(void)
160 if (vacant_dstats_list) {
161 dstat = vacant_dstats_list;
162 vacant_dstats_list = dstat->next;
164 dstat = malloc(sizeof(*dstat));
166 perror("blkiomon: could not allocate device statistic");
170 memset(dstat, 0, sizeof(*dstat));
174 static struct dstat *blkiomon_find_dstat(struct rb_search *search, __u32 device)
176 struct rb_node **p = &(dstat_tree[dstat_curr].rb_node);
177 struct rb_node *parent = NULL;
183 dstat = rb_entry(parent, struct dstat, node);
185 if (dstat->msg.stat.device < device)
187 else if (dstat->msg.stat.device > device)
192 search->node_ptr = p;
193 search->parent = parent;
197 static struct dstat *blkiomon_get_dstat(__u32 device)
200 struct rb_search search;
202 pthread_mutex_lock(&dstat_mutex);
204 dstat = blkiomon_find_dstat(&search, device);
208 dstat = blkiomon_alloc_dstat();
212 dstat->msg.stat.device = device;
213 dstat->msg.stat.size_mm.min = -1ULL;
214 dstat->msg.stat.d2c_mm.min = -1ULL;
216 rb_link_node(&dstat->node, search.parent, search.node_ptr);
217 rb_insert_color(&dstat->node, &dstat_tree[dstat_curr]);
219 dstat->next = dstat_list[dstat_curr];
220 dstat_list[dstat_curr] = dstat;
223 pthread_mutex_unlock(&dstat_mutex);
227 static int blkiomon_output_msg_q(struct dstat *dstat)
232 dstat->msg.mtype = msg_id;
233 return msgsnd(msg_q, &dstat->msg, sizeof(struct blkiomon_stat), 0);
236 static int blkiomon_output_binary(struct dstat *dstat)
238 struct blkiomon_stat *p = &dstat->msg.stat;
243 if (fwrite(p, sizeof(*p), 1, binary.fp) != 1)
245 if (binary.pipe && fflush(binary.fp))
250 fprintf(stderr, "blkiomon: could not write to %s\n", binary.fn);
256 static struct dstat *blkiomon_output(struct dstat *head, struct timespec *ts)
258 struct dstat *dstat, *tail = NULL;
260 for (dstat = head; dstat; dstat = dstat->next) {
261 dstat->msg.stat.time = ts->tv_sec;
262 blkiomon_stat_print(human.fp, &dstat->msg.stat);
263 blkiomon_stat_to_be(&dstat->msg.stat);
264 blkiomon_output_binary(dstat);
265 blkiomon_output_msg_q(dstat);
271 static void *blkiomon_interval(void *data)
273 struct timespec wake, r;
274 struct dstat *head, *tail;
277 clock_gettime(CLOCK_REALTIME, &wake);
280 wake.tv_sec += interval;
281 if (clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &wake, &r)) {
282 perror("blkiomon: interrupted sleep");
286 /* grab tree and make data gatherer build up another tree */
287 pthread_mutex_lock(&dstat_mutex);
288 finished = dstat_curr;
289 dstat_curr = dstat_curr ? 0 : 1;
290 pthread_mutex_unlock(&dstat_mutex);
292 head = dstat_list[finished];
295 dstat_list[finished] = NULL;
296 dstat_tree[finished] = RB_ROOT;
297 tail = blkiomon_output(head, &wake);
299 pthread_mutex_lock(&dstat_mutex);
300 tail->next = vacant_dstats_list;
301 vacant_dstats_list = head;
302 pthread_mutex_unlock(&dstat_mutex);
307 #define BLK_DATADIR(a) (((a) >> BLK_TC_SHIFT) & (BLK_TC_READ | BLK_TC_WRITE))
309 static int blkiomon_account(struct blk_io_trace *bit_d,
310 struct blk_io_trace *bit_c)
313 struct blkiomon_stat *p;
314 __u64 d2c = (bit_c->time - bit_d->time) / 1000; /* ns -> us */
315 __u32 size = bit_d->bytes;
317 dstat = blkiomon_get_dstat(bit_d->device);
320 p = &dstat->msg.stat;
322 if (BLK_DATADIR(bit_c->action) & BLK_TC_READ)
324 else if (BLK_DATADIR(bit_c->action) & BLK_TC_WRITE)
329 histlog2_account(p->size_hist, size, &size_hist);
330 histlog2_account(p->d2c_hist, d2c, &d2c_hist);
331 minmax_account(&p->size_mm, size);
332 minmax_account(&p->d2c_mm, d2c);
336 static struct trace *blkiomon_alloc_trace(void)
338 struct trace *t = vacant_traces_list;
340 vacant_traces_list = t->next;
343 t = malloc(sizeof(*t));
344 memset(t, 0, sizeof(*t));
348 static void blkiomon_free_trace(struct trace *t)
350 if (vacant_traces < 256) {
351 t->next = vacant_traces_list;
352 vacant_traces_list = t;
358 static int action(int a)
360 int bits = BLK_TC_WRITE | BLK_TC_READ | BLK_TC_FS | BLK_TC_PC;
361 return a & (BLK_TC_ACT(bits));
364 static void blkiomon_store_trace(struct trace *t)
366 int i = t->bit.sector % TRACE_HASH_SIZE;
372 static struct trace *blkiomon_fetch_trace(struct blk_io_trace *bit)
374 int i = bit->sector % TRACE_HASH_SIZE;
375 struct trace *t, *prev = NULL;
377 for (t = thash[i]; t; t = t->next) {
378 if (t->bit.device == bit->device &&
379 t->bit.sector == bit->sector &&
380 action(t->bit.action) == action(bit->action)) {
382 prev->next = t->next;
392 static struct trace *blkiomon_do_trace(struct trace *t)
394 struct trace *t_stored, *t_old, *t_young;
396 /* store trace if there is no match yet */
397 t_stored = blkiomon_fetch_trace(&t->bit);
399 blkiomon_store_trace(t);
400 return blkiomon_alloc_trace();
403 /* figure out older trace and younger trace */
404 if (t_stored->bit.time < t->bit.time) {
412 /* we need an older D trace and a younger C trace */
413 if (t_old->bit.action & BLK_TC_ACT(BLK_TC_ISSUE) &&
414 t_young->bit.action & BLK_TC_ACT(BLK_TC_COMPLETE)) {
415 /* matching D and C traces - update statistics */
417 blkiomon_account(&t_old->bit, &t_young->bit);
418 blkiomon_free_trace(t_stored);
422 /* no matching D and C traces - keep more recent trace */
423 dump_bits(t_old, t_young, "mismatch");
425 blkiomon_store_trace(t_young);
429 static int blkiomon_dump_drvdata(struct blk_io_trace *bit, void *pdu_buf)
434 if (fwrite(bit, sizeof(*bit), 1, drvdata.fp) != 1)
436 if (fwrite(pdu_buf, bit->pdu_len, 1, drvdata.fp) != 1)
438 if (drvdata.pipe && fflush(drvdata.fp))
443 fprintf(stderr, "blkiomon: could not write to %s\n", drvdata.fn);
449 static int blkiomon_do_fifo(void)
452 struct blk_io_trace *bit;
453 void *pdu_buf = NULL;
455 t = blkiomon_alloc_trace();
461 if (fread(bit, sizeof(*bit), 1, ifp) != 1) {
464 "blkiomon: could not read trace");
469 perror("blkiomon: error while reading trace");
473 if (data_is_native == -1 && check_data_endianness(bit->magic))
478 if (verify_trace(bit)) {
479 perror("blkiomon: bad trace");
483 /* read additional trace payload */
485 pdu_buf = realloc(pdu_buf, bit->pdu_len);
486 if (fread(pdu_buf, bit->pdu_len, 1, ifp) != 1) {
488 perror("blkiomon: could not read payload");
493 t->sequence = sequence++;
495 /* forward low-level device driver trace to other tool */
496 if (bit->action & BLK_TC_ACT(BLK_TC_DRV_DATA)) {
498 if (blkiomon_dump_drvdata(bit, pdu_buf))
503 if (!(bit->action & BLK_TC_ACT(BLK_TC_ISSUE | BLK_TC_COMPLETE)))
506 /* try to find matching trace and update statistics */
507 t = blkiomon_do_trace(t);
511 /* t and bit will be recycled for next incoming trace */
513 blkiomon_free_trace(t);
518 static int blkiomon_open_output(struct output *out)
525 if (!strcmp(out->fn, "-")) {
526 out->fp = fdopen(STDOUT_FILENO, "w");
531 out->fp = fopen(out->fn, "w");
533 vbuf_size = 128 * 1024;
538 out->buf = malloc(128 * 1024);
539 if (setvbuf(out->fp, out->buf, mode, vbuf_size))
544 fprintf(stderr, "blkiomon: could not write to %s\n", out->fn);
550 static int blkiomon_open_msg_q(void)
556 if (!msg_q_id || msg_id <= 0)
558 key = ftok(msg_q_name, msg_q_id);
562 msg_q = msgget(key, S_IRWXU);
566 return (msg_q >= 0 ? 0 : -1);
569 static void blkiomon_debug(void)
577 for (i = 0; i < TRACE_HASH_SIZE; i++)
578 for (t = thash[i]; t; t = t->next) {
579 dump_bit(t, "leftover");
583 fprintf(debug.fp, "%ld leftover, %ld match, %ld mismatch, "
584 "%ld driverdata, %ld overall\n",
585 leftover, match, mismatch, driverdata, sequence);
588 #define S_OPTS "b:d:D:h:I:Q:q:m:V"
590 static char usage_str[] = "\n\nblkiomon " \
591 "-I <interval> | --interval=<interval>\n" \
592 "[ -h <file> | --human-readable=<file> ]\n" \
593 "[ -b <file> | --binary=<file> ]\n" \
594 "[ -D <file> | --debug=<file> ]\n" \
595 "[ -Q <path name> | --msg-queue-name=<path name>]\n" \
596 "[ -q <msg queue id> | --msg-queue-id=<msg queue id>]\n" \
597 "[ -m <msg id> | --msg-id=<msg id>]\n" \
598 "[ -V | --version ]\n\n" \
599 "\t-I Sample interval.\n" \
600 "\t-h Human-readable output file.\n" \
601 "\t-b Binary output file.\n" \
602 "\t-d Output file for data emitted by low level device driver.\n" \
603 "\t-D Output file for debugging data.\n" \
604 "\t-Qqm Output to message queue using given ID for messages.\n" \
605 "\t-V Print program version.\n\n";
607 static struct option l_opts[] = {
609 .name = "human-readable",
610 .has_arg = required_argument,
616 .has_arg = required_argument,
622 .has_arg = required_argument,
628 .has_arg = required_argument,
634 .has_arg = required_argument,
640 .has_arg = required_argument,
645 .name = "msg-queue-id",
646 .has_arg = required_argument,
652 .has_arg = required_argument,
658 .has_arg = no_argument,
667 static void blkiomon_signal(int signal)
669 fprintf(stderr, "blkiomon: terminated by signal\n");
673 int main(int argc, char *argv[])
677 signal(SIGALRM, blkiomon_signal);
678 signal(SIGINT, blkiomon_signal);
679 signal(SIGTERM, blkiomon_signal);
680 signal(SIGQUIT, blkiomon_signal);
682 while ((c = getopt_long(argc, argv, S_OPTS, l_opts, NULL)) != -1) {
697 interval = atoi(optarg);
703 msg_q_id = atoi(optarg);
706 msg_id = atoi(optarg);
709 printf("%s version %s\n", argv[0], blkiomon_version);
712 fprintf(stderr, "Usage: %s", usage_str);
718 fprintf(stderr, "Usage: %s", usage_str);
722 ifp = fdopen(STDIN_FILENO, "r");
724 perror("blkiomon: could not open stdin for reading");
728 if (blkiomon_open_output(&human))
730 if (blkiomon_open_output(&binary))
732 if (blkiomon_open_output(&drvdata))
734 if (blkiomon_open_output(&debug))
736 if (blkiomon_open_msg_q())
739 if (pthread_create(&interval_thread, NULL, blkiomon_interval, NULL)) {
740 perror("blkiomon: could not create thread");