From fd4c2b74f3ef3caf03bf7bde78d230dad54774a2 Mon Sep 17 00:00:00 2001 From: David van Moolenbroek Date: Thu, 24 Nov 2011 14:50:13 +0100 Subject: [PATCH] Add block device tracing facility The implementation is in libblockdriver, and works transparently for all block drivers. The new btrace(8) tool can be used to control block tracing; see ``man btrace'' for details. --- commands/Makefile | 2 +- commands/btrace/Makefile | 4 + commands/btrace/btrace.c | 218 ++++++++++++++++++++ common/include/Makefile.inc | 1 + common/include/minix/blockdriver.h | 4 +- common/include/minix/blockdriver_mt.h | 4 +- common/include/minix/btrace.h | 40 ++++ common/include/sys/Makefile.inc | 2 +- common/include/sys/ioc_block.h | 15 ++ lib/libblockdriver/Makefile | 2 +- lib/libblockdriver/driver.c | 30 ++- lib/libblockdriver/driver.h | 5 +- lib/libblockdriver/driver_mt.c | 14 +- lib/libblockdriver/driver_st.c | 6 +- lib/libblockdriver/mq.c | 8 +- lib/libblockdriver/mq.h | 2 - lib/libblockdriver/trace.c | 283 ++++++++++++++++++++++++++ lib/libblockdriver/trace.h | 11 + man/man8/Makefile | 2 +- man/man8/btrace.8 | 59 ++++++ 20 files changed, 682 insertions(+), 30 deletions(-) create mode 100644 commands/btrace/Makefile create mode 100644 commands/btrace/btrace.c create mode 100644 common/include/minix/btrace.h create mode 100644 common/include/sys/ioc_block.h create mode 100644 lib/libblockdriver/trace.c create mode 100644 lib/libblockdriver/trace.h create mode 100644 man/man8/btrace.8 diff --git a/commands/Makefile b/commands/Makefile index 99c1586f7..f3e377ac5 100644 --- a/commands/Makefile +++ b/commands/Makefile @@ -4,7 +4,7 @@ SUBDIR= aal add_route arp ash at autil awk \ backup badblocks banner basename binpackage \ - binpackages cal calendar \ + binpackages btrace cal calendar \ cat cawf cd cdprobe checkhier chmem \ chmod chown chroot ci cksum cleantmp clear cmp co \ comm compress cp crc cron crontab cut date \ diff --git a/commands/btrace/Makefile b/commands/btrace/Makefile new file mode 100644 index 000000000..7daeefeab --- /dev/null +++ b/commands/btrace/Makefile @@ -0,0 +1,4 @@ +PROG= btrace +MAN= + +.include diff --git a/commands/btrace/btrace.c b/commands/btrace/btrace.c new file mode 100644 index 000000000..716313ca8 --- /dev/null +++ b/commands/btrace/btrace.c @@ -0,0 +1,218 @@ +/* Block trace command line tool */ +#include +#include +#include +#include +#include +#include +#include +#include + +static void usage(char *name) +{ + printf("usage:\n" + "%s start \n" + "%s stop \n" + "%s reset \n" + "%s dump \n", + name, name, name, name); + + exit(EXIT_FAILURE); +} + +static void btrace_start(char *device, int nr_entries) +{ + int r, ctl, devfd; + size_t size; + + if ((devfd = open(device, O_RDONLY)) < 0) { + perror("device open"); + exit(EXIT_FAILURE); + } + + size = nr_entries; + if ((r = ioctl(devfd, BIOCTRACEBUF, &size)) < 0) { + perror("ioctl(BIOCTRACEBUF)"); + exit(EXIT_FAILURE); + } + + ctl = BTCTL_START; + if ((r = ioctl(devfd, BIOCTRACECTL, &ctl)) < 0) { + perror("ioctl(BIOCTRACECTL)"); + + size = 0; + ioctl(devfd, BIOCTRACEBUF, &size); + + exit(EXIT_FAILURE); + } + + close(devfd); +} + +static void btrace_stop(char *device, char *file) +{ + btrace_entry buf[BTBUF_SIZE]; + int r, ctl, devfd, outfd; + size_t size; + + if ((devfd = open(device, O_RDONLY)) < 0) { + perror("device open"); + exit(EXIT_FAILURE); + } + + if ((outfd = open(file, O_CREAT|O_TRUNC|O_WRONLY, 0600)) < 0) { + perror("file open"); + exit(EXIT_FAILURE); + } + + ctl = BTCTL_STOP; + if ((r = ioctl(devfd, BIOCTRACECTL, &ctl)) < 0) { + perror("ioctl(BIOCTRACECTL)"); + exit(EXIT_FAILURE); + } + + for (;;) { + if ((r = ioctl(devfd, BIOCTRACEGET, buf)) < 0) { + perror("ioctl(BIOCTRACEGET)"); + break; + } + + if (r == 0) break; + + size = r * sizeof(buf[0]); + if ((r = write(outfd, (char *) buf, size)) != size) { + if (r < 0) perror("write"); + else fputs("short write\n", stderr); + } + } + + close(outfd); + + size = 0; + if ((r = ioctl(devfd, BIOCTRACEBUF, &size)) < 0) { + perror("ioctl(BIOCTRACEBUF)"); + exit(EXIT_FAILURE); + } + + close(devfd); +} + +static void btrace_reset(char *device) +{ + size_t size; + int r, ctl, devfd; + + if ((devfd = open(device, O_RDONLY)) < 0) { + perror("device open"); + exit(EXIT_FAILURE); + } + + ctl = BTCTL_STOP; + (void) ioctl(devfd, BIOCTRACECTL, &ctl); + + size = 0; + if ((r = ioctl(devfd, BIOCTRACEBUF, &size)) < 0) { + perror("ioctl(BIOCTRACEBUF)"); + exit(EXIT_FAILURE); + } + + close(devfd); +} + +static void dump_entry(btrace_entry *entry) +{ + switch (entry->request) { + case BTREQ_OPEN: printf("OPEN"); break; + case BTREQ_CLOSE: printf("CLOSE"); break; + case BTREQ_READ: printf("READ"); break; + case BTREQ_WRITE: printf("WRITE"); break; + case BTREQ_GATHER: printf("GATHER"); break; + case BTREQ_SCATTER: printf("SCATTER"); break; + case BTREQ_IOCTL: printf("IOCTL"); break; + } + + printf(" request\n"); + + switch (entry->request) { + case BTREQ_OPEN: + printf("- access:\t%lx\n", entry->size); + break; + case BTREQ_READ: + case BTREQ_WRITE: + case BTREQ_GATHER: + case BTREQ_SCATTER: + printf("- position:\t%08lx%08lx\n", + ex64hi(entry->position), ex64lo(entry->position)); + printf("- size:\t\t%u\n", entry->size); + printf("- flags:\t%x\n", entry->flags); + break; + case BTREQ_IOCTL: + printf("- request:\t%08x\n", entry->size); + break; + } + + printf("- start:\t%u us\n", entry->start_time); + printf("- finish:\t%u us\n", entry->finish_time); + if (entry->result == BTRES_INPROGRESS) + printf("- result:\t(in progress)\n"); + else + printf("- result:\t%d\n", entry->result); + printf("\n"); +} + +static void btrace_dump(char *file) +{ + btrace_entry buf[BTBUF_SIZE]; + int i, r, infd; + + if ((infd = open(file, O_RDONLY)) < 0) { + perror("open"); + exit(EXIT_FAILURE); + } + + for (;;) { + if ((r = read(infd, (char *) buf, sizeof(buf))) <= 0) + break; + + r /= sizeof(buf[0]); + + for (i = 0; i < r; i++) + dump_entry(&buf[i]); + } + + if (r < 0) perror("read"); + + close(infd); +} + +int main(int argc, char **argv) +{ + int num; + char *name = argv[0]; + + if (argc < 3) usage(name); + + if (!strcmp(argv[1], "start")) { + if (argc < 4) usage(name); + + num = atoi(argv[3]); + + if (num <= 0) usage(name); + + btrace_start(argv[2], num); + } + else if (!strcmp(argv[1], "stop")) { + if (argc < 4) usage(name); + + btrace_stop(argv[2], argv[3]); + } + else if (!strcmp(argv[1], "reset")) { + btrace_reset(argv[2]); + } + else if (!strcmp(argv[1], "dump")) { + btrace_dump(argv[2]); + } + else usage(name); + + return EXIT_SUCCESS; +} diff --git a/common/include/Makefile.inc b/common/include/Makefile.inc index 81be73c6a..1b38a2783 100644 --- a/common/include/Makefile.inc +++ b/common/include/Makefile.inc @@ -6,6 +6,7 @@ INCS+= env.h fetch.h hgfs.h lib.h libutil.h timers.h INCS+= minix/acpi.h minix/ansi.h minix/audio_fw.h minix/bitmap.h \ minix/bdev.h minix/blockdriver.h minix/blockdriver_mt.h \ + minix/btrace.h \ minix/callnr.h minix/chardriver.h minix/com.h minix/compiler.h \ minix/config.h minix/const.h minix/cpufeature.h minix/crtso.h \ minix/debug.h minix/devio.h minix/devman.h minix/dmap.h \ diff --git a/common/include/minix/blockdriver.h b/common/include/minix/blockdriver.h index 2d212ec08..efd5261a5 100644 --- a/common/include/minix/blockdriver.h +++ b/common/include/minix/blockdriver.h @@ -27,7 +27,7 @@ struct blockdriver { */ _PROTOTYPE( void blockdriver_announce, (void) ); -#ifndef _DRIVER_MT_API +#ifndef _BLOCKDRIVER_MT_API /* Additional functions for the singlethreaded version. These allow the driver * to either use the stock driver_task(), or implement its own message loop. * To avoid accidents, these functions are not exposed when minix/driver_mt.h @@ -39,7 +39,7 @@ _PROTOTYPE( void blockdriver_process, (struct blockdriver *dp, message *m_ptr, _PROTOTYPE( void blockdriver_terminate, (void) ); _PROTOTYPE( void blockdriver_task, (struct blockdriver *bdp) ); _PROTOTYPE( int blockdriver_mq_queue, (message *m_ptr, int status) ); -#endif /* !_DRIVER_MT_API */ +#endif /* !_BLOCKDRIVER_MT_API */ /* Parameters for the disk drive. */ #define SECTOR_SIZE 512 /* physical sector size in bytes */ diff --git a/common/include/minix/blockdriver_mt.h b/common/include/minix/blockdriver_mt.h index 143660f46..05b523d5c 100644 --- a/common/include/minix/blockdriver_mt.h +++ b/common/include/minix/blockdriver_mt.h @@ -1,11 +1,11 @@ #ifndef _MINIX_BLOCKDRIVER_MT_H #define _MINIX_BLOCKDRIVER_MT_H -#define DRIVER_MT_API 1 /* do not expose the singlethreaded API */ +#define BLOCKDRIVER_MT_API 1 /* do not expose the singlethreaded API */ #include /* The maximum number of worker threads. */ -#define DRIVER_MT_MAX_WORKERS 32 +#define BLOCKDRIVER_MT_MAX_WORKERS 32 _PROTOTYPE( void blockdriver_mt_task, (struct blockdriver *driver_tab) ); _PROTOTYPE( void blockdriver_mt_sleep, (void) ); diff --git a/common/include/minix/btrace.h b/common/include/minix/btrace.h new file mode 100644 index 000000000..d97b7d952 --- /dev/null +++ b/common/include/minix/btrace.h @@ -0,0 +1,40 @@ +#ifndef _MINIX_BTRACE_H +#define _MINIX_BTRACE_H + +/* Control directives. */ +enum { + BTCTL_START, + BTCTL_STOP +}; + +/* Request codes. */ +enum { + BTREQ_OPEN, + BTREQ_CLOSE, + BTREQ_READ, + BTREQ_WRITE, + BTREQ_GATHER, + BTREQ_SCATTER, + BTREQ_IOCTL +}; + +/* Special result codes. */ +#define BTRES_INPROGRESS (-997) + +/* Block trace entry. */ +typedef struct { + u32_t request; /* request code; one of BTR_xxx */ + u32_t size; /* request size, ioctl request, or access */ + u64_t position; /* starting disk position */ + u32_t flags; /* transfer flags */ + i32_t result; /* request result; OK, bytes, or error */ + u32_t start_time; /* request service start time (us) */ + u32_t finish_time; /* request service completion time (us) */ +} btrace_entry; /* (32 bytes) */ + +/* This is the number of btrace_entry structures copied out at once using the + * BIOCTRACEGET ioctl call. + */ +#define BTBUF_SIZE 1024 + +#endif /* _MINIX_BTRACE_H */ diff --git a/common/include/sys/Makefile.inc b/common/include/sys/Makefile.inc index f8e4c932b..2f62740f1 100644 --- a/common/include/sys/Makefile.inc +++ b/common/include/sys/Makefile.inc @@ -3,7 +3,7 @@ .PATH: ${MINIXSRCDIR}/common/include/sys INCS+= elf32.h elf64.h elf_common.h elf_generic.h \ - ioc_file.h ioc_tape.h ioc_disk.h \ + ioc_block.h ioc_file.h ioc_tape.h ioc_disk.h \ ioc_memory.h ioc_sound.h ioc_tty.h \ kbdio.h mtio.h svrctl.h video.h vm.h procfs.h elf_core.h exec_elf.h diff --git a/common/include/sys/ioc_block.h b/common/include/sys/ioc_block.h new file mode 100644 index 000000000..663a30646 --- /dev/null +++ b/common/include/sys/ioc_block.h @@ -0,0 +1,15 @@ +/* sys/ioc_block.h - Block ioctl() command codes. + * + */ + +#ifndef _S_I_BLOCK_H +#define _S_I_BLOCK_H + +#include +#include + +#define BIOCTRACEBUF _IOW('b', 1, size_t) +#define BIOCTRACECTL _IOW('b', 2, int) +#define BIOCTRACEGET _IOR_BIG(3, btrace_entry[BTBUF_SIZE]) + +#endif /* _S_I_BLOCK_H */ diff --git a/lib/libblockdriver/Makefile b/lib/libblockdriver/Makefile index 0c153d218..abd061095 100644 --- a/lib/libblockdriver/Makefile +++ b/lib/libblockdriver/Makefile @@ -3,7 +3,7 @@ LIB= blockdriver -SRCS= driver.c drvlib.c driver_st.c driver_mt.c mq.c event.c +SRCS= driver.c drvlib.c driver_st.c driver_mt.c mq.c event.c trace.c .if ${USE_STATECTL} != "no" CPPFLAGS+= -DUSE_STATECTL diff --git a/lib/libblockdriver/driver.c b/lib/libblockdriver/driver.c index f63e9cbc1..e7940cc42 100644 --- a/lib/libblockdriver/driver.c +++ b/lib/libblockdriver/driver.c @@ -40,10 +40,12 @@ #include #include #include +#include #include #include "driver.h" #include "mq.h" +#include "trace.h" /* Management data for opened devices. */ PRIVATE int open_devs[MAX_NR_OPEN_DEVICES]; @@ -207,7 +209,7 @@ PRIVATE int do_rdwt(struct blockdriver *bdp, message *mp) /*===========================================================================* * do_vrdwt * *===========================================================================*/ -PRIVATE int do_vrdwt(struct blockdriver *bdp, message *mp) +PRIVATE int do_vrdwt(struct blockdriver *bdp, message *mp, thread_id_t id) { /* Carry out an device read or write to/from a vector of buffers. */ iovec_t iovec[NR_IOREQS]; @@ -226,12 +228,14 @@ PRIVATE int do_vrdwt(struct blockdriver *bdp, message *mp) return EINVAL; } - /* Check for overflow condition. */ + /* Check for overflow condition, and update the size for block tracing. */ for (i = size = 0; i < nr_req; i++) { if ((ssize_t) (size + iovec[i].iov_size) < size) return EINVAL; size += iovec[i].iov_size; } + trace_setsize(id, size); + /* Transfer bytes from/to the device. */ do_write = (mp->m_type == BDEV_SCATTER); position = make64(mp->BDEV_POS_LO, mp->BDEV_POS_HI); @@ -249,7 +253,10 @@ PRIVATE int do_vrdwt(struct blockdriver *bdp, message *mp) PRIVATE int do_ioctl(struct blockdriver *bdp, message *mp) { /* Carry out an I/O control request. For now, we handle setting/getting - * partitions here, and let the driver handle any other requests. + * partitions here, forward block trace control requests to the tracing module, + * and let the driver handle any other requests. In the future, a stricter + * separation between block and disk I/O controls may become desirable, but we + * do not have any non-"disk" block drivers at this time. */ struct device *dv; struct partition entry; @@ -263,6 +270,14 @@ PRIVATE int do_ioctl(struct blockdriver *bdp, message *mp) grant = mp->BDEV_GRANT; switch (request) { + case BIOCTRACEBUF: + case BIOCTRACECTL: + case BIOCTRACEGET: + /* Block trace control. */ + r = trace_ctl(minor, request, mp->m_source, grant); + + break; + case DIOCSETP: /* Copy just this one partition table entry. */ r = sys_safecopyfrom(mp->m_source, grant, 0, (vir_bytes) &entry, @@ -337,7 +352,8 @@ PUBLIC void blockdriver_handle_notify(struct blockdriver *bdp, message *m_ptr) /*===========================================================================* * blockdriver_handle_request * *===========================================================================*/ -PUBLIC int blockdriver_handle_request(struct blockdriver *bdp, message *m_ptr) +PUBLIC int blockdriver_handle_request(struct blockdriver *bdp, message *m_ptr, + thread_id_t id) { /* Call the appropiate driver function, based on the type of request. Return * the result code that is to be sent back to the caller, or EDONTREPLY if no @@ -358,6 +374,8 @@ PUBLIC int blockdriver_handle_request(struct blockdriver *bdp, message *m_ptr) set_open_dev(m_ptr->BDEV_MINOR); } + trace_start(id, m_ptr); + /* Call the appropriate function(s) for this request. */ switch (m_ptr->m_type) { case BDEV_OPEN: r = do_open(bdp, m_ptr); break; @@ -365,7 +383,7 @@ PUBLIC int blockdriver_handle_request(struct blockdriver *bdp, message *m_ptr) case BDEV_READ: case BDEV_WRITE: r = do_rdwt(bdp, m_ptr); break; case BDEV_GATHER: - case BDEV_SCATTER: r = do_vrdwt(bdp, m_ptr); break; + case BDEV_SCATTER: r = do_vrdwt(bdp, m_ptr, id); break; case BDEV_IOCTL: r = do_ioctl(bdp, m_ptr); break; default: if (bdp->bdr_other) @@ -378,5 +396,7 @@ PUBLIC int blockdriver_handle_request(struct blockdriver *bdp, message *m_ptr) if (bdp->bdr_cleanup != NULL) (*bdp->bdr_cleanup)(); + trace_finish(id, r); + return r; } diff --git a/lib/libblockdriver/driver.h b/lib/libblockdriver/driver.h index 4e1299e3d..5e51933ad 100644 --- a/lib/libblockdriver/driver.h +++ b/lib/libblockdriver/driver.h @@ -1,10 +1,13 @@ #ifndef _BLOCKDRIVER_DRIVER_H #define _BLOCKDRIVER_DRIVER_H +#define SINGLE_THREAD (0) /* single-thread ID */ +#define MAIN_THREAD (BLOCKDRIVER_MT_MAX_WORKERS) /* main thread ID */ + _PROTOTYPE( void blockdriver_handle_notify, (struct blockdriver *bdp, message *m_ptr) ); _PROTOTYPE( int blockdriver_handle_request, (struct blockdriver *bdp, - message *m_ptr) ); + message *m_ptr, thread_id_t thread) ); _PROTOTYPE( void blockdriver_reply, (message *m_ptr, int ipc_status, int reply) ); diff --git a/lib/libblockdriver/driver_mt.c b/lib/libblockdriver/driver_mt.c index fd7cc0215..7780c192f 100644 --- a/lib/libblockdriver/driver_mt.c +++ b/lib/libblockdriver/driver_mt.c @@ -40,9 +40,9 @@ PRIVATE int running = FALSE; PRIVATE mthread_key_t worker_key; -PRIVATE worker_t worker[DRIVER_MT_MAX_WORKERS]; +PRIVATE worker_t worker[BLOCKDRIVER_MT_MAX_WORKERS]; -PRIVATE worker_t *exited[DRIVER_MT_MAX_WORKERS]; +PRIVATE worker_t *exited[BLOCKDRIVER_MT_MAX_WORKERS]; PRIVATE int num_exited = 0; /*===========================================================================* @@ -126,7 +126,7 @@ PRIVATE void *worker_thread(void *param) wp->state = STATE_RUNNING; /* Handle the request, and send a reply. */ - r = blockdriver_handle_request(bdtab, &m); + r = blockdriver_handle_request(bdtab, &m, wp->id); blockdriver_reply(&m, ipc_status, r); } @@ -223,7 +223,7 @@ PRIVATE void master_handle_request(message *m_ptr, int ipc_status) */ if (!IS_BDEV_RQ(m_ptr->m_type)) { /* Process as 'other' message. */ - r = blockdriver_handle_request(bdtab, m_ptr); + r = blockdriver_handle_request(bdtab, m_ptr, MAIN_THREAD); blockdriver_reply(m_ptr, ipc_status, r); @@ -240,7 +240,7 @@ PRIVATE void master_handle_request(message *m_ptr, int ipc_status) } /* Start the thread if it is not already running. */ - assert(thread_id >= 0 && thread_id < DRIVER_MT_MAX_WORKERS); + assert(thread_id >= 0 && thread_id < BLOCKDRIVER_MT_MAX_WORKERS); wp = &worker[thread_id]; @@ -269,7 +269,7 @@ PRIVATE void master_init(struct blockdriver *bdp) bdtab = bdp; - for (i = 0; i < DRIVER_MT_MAX_WORKERS; i++) + for (i = 0; i < BLOCKDRIVER_MT_MAX_WORKERS; i++) worker[i].state = STATE_DEAD; /* Initialize a per-thread key, where each worker thread stores its own @@ -368,7 +368,7 @@ PUBLIC void blockdriver_mt_wakeup(thread_id_t id) */ worker_t *wp; - assert(id >= 0 && id < DRIVER_MT_MAX_WORKERS); + assert(id >= 0 && id < BLOCKDRIVER_MT_MAX_WORKERS); wp = &worker[id]; diff --git a/lib/libblockdriver/driver_st.c b/lib/libblockdriver/driver_st.c index b3b225b66..e703e7131 100644 --- a/lib/libblockdriver/driver_st.c +++ b/lib/libblockdriver/driver_st.c @@ -27,7 +27,7 @@ PUBLIC int blockdriver_receive_mq(message *m_ptr, int *status_ptr) /* receive() interface for drivers with message queueing. */ /* Any queued messages? */ - if (mq_dequeue(MQ_SINGLE, m_ptr, status_ptr)) + if (mq_dequeue(SINGLE_THREAD, m_ptr, status_ptr)) return OK; /* Fall back to standard receive() interface otherwise. */ @@ -81,7 +81,7 @@ PUBLIC void blockdriver_process(struct blockdriver *bdp, message *m_ptr, /* Do not reply to notifications. */ } else { - r = blockdriver_handle_request(bdp, m_ptr); + r = blockdriver_handle_request(bdp, m_ptr, SINGLE_THREAD); blockdriver_reply(m_ptr, ipc_status, r); } @@ -94,5 +94,5 @@ PUBLIC int blockdriver_mq_queue(message *m, int status) { /* Queue a message for later processing. */ - return mq_enqueue(MQ_SINGLE, m, status); + return mq_enqueue(SINGLE_THREAD, m, status); } diff --git a/lib/libblockdriver/mq.c b/lib/libblockdriver/mq.c index 602fa3b10..226356358 100644 --- a/lib/libblockdriver/mq.c +++ b/lib/libblockdriver/mq.c @@ -22,7 +22,7 @@ struct mq_cell { PRIVATE struct mq_cell pool[MQ_SIZE]; -PRIVATE STAILQ_HEAD(queue, mq_cell) queue[DRIVER_MT_MAX_WORKERS]; +PRIVATE STAILQ_HEAD(queue, mq_cell) queue[BLOCKDRIVER_MT_MAX_WORKERS]; PRIVATE STAILQ_HEAD(free_list, mq_cell) free_list; /*===========================================================================* @@ -36,7 +36,7 @@ PUBLIC void mq_init(void) STAILQ_INIT(&free_list); - for (i = 0; i < DRIVER_MT_MAX_WORKERS; i++) + for (i = 0; i < BLOCKDRIVER_MT_MAX_WORKERS; i++) STAILQ_INIT(&queue[i]); for (i = 0; i < MQ_SIZE; i++) @@ -54,7 +54,7 @@ PUBLIC int mq_enqueue(thread_id_t thread_id, const message *mess, */ struct mq_cell *cell; - assert(thread_id >= 0 && thread_id < DRIVER_MT_MAX_WORKERS); + assert(thread_id >= 0 && thread_id < BLOCKDRIVER_MT_MAX_WORKERS); if (STAILQ_EMPTY(&free_list)) return FALSE; @@ -80,7 +80,7 @@ PUBLIC int mq_dequeue(thread_id_t thread_id, message *mess, int *ipc_status) */ struct mq_cell *cell; - assert(thread_id >= 0 && thread_id < DRIVER_MT_MAX_WORKERS); + assert(thread_id >= 0 && thread_id < BLOCKDRIVER_MT_MAX_WORKERS); if (STAILQ_EMPTY(&queue[thread_id])) return FALSE; diff --git a/lib/libblockdriver/mq.h b/lib/libblockdriver/mq.h index 7d772e49e..427d3df05 100644 --- a/lib/libblockdriver/mq.h +++ b/lib/libblockdriver/mq.h @@ -1,8 +1,6 @@ #ifndef _BLOCKDRIVER_MQ_H #define _BLOCKDRIVER_MQ_H -#define MQ_SINGLE 0 /* thread ID for single-threading */ - _PROTOTYPE( void mq_init, (void) ); _PROTOTYPE( int mq_enqueue, (thread_id_t thread_id, const message *mess, int ipc_status) ); diff --git a/lib/libblockdriver/trace.c b/lib/libblockdriver/trace.c new file mode 100644 index 000000000..61ed3fefb --- /dev/null +++ b/lib/libblockdriver/trace.c @@ -0,0 +1,283 @@ +/* This file implements block level tracing support. */ + +#include +#include +#include +#include +#include +#include + +#include "trace.h" + +#define NO_TRACEDEV ((dev_t) -1) +#define NO_TIME ((u32_t) -1) + +PRIVATE int trace_enabled = FALSE; +PRIVATE dev_t trace_dev = NO_TRACEDEV; +PRIVATE btrace_entry *trace_buf = NULL; +PRIVATE size_t trace_size = 0; +PRIVATE size_t trace_pos; +PRIVATE size_t trace_next; +PRIVATE u64_t trace_tsc; + +/* Pointers to in-progress trace entries for each thread (all worker threads, + * plus one for the main thread). Each pointer is set to NULL whenever no + * operation is currently being traced for that thread, for whatever reason. + */ +PRIVATE btrace_entry *trace_ptr[BLOCKDRIVER_MT_MAX_WORKERS + 1] = { NULL }; + +/*===========================================================================* + * trace_gettime * + *===========================================================================*/ +PRIVATE u32_t trace_gettime(void) +{ +/* Return the current time, in microseconds since the start of the trace. + */ + u64_t tsc; + + assert(trace_enabled); + + read_tsc_64(&tsc); + + tsc = sub64(tsc, trace_tsc); + + return tsc_64_to_micros(tsc); +} + +/*===========================================================================* + * trace_ctl * + *===========================================================================*/ +PUBLIC int trace_ctl(dev_t minor, unsigned int request, endpoint_t endpt, + cp_grant_id_t grant) +{ +/* Process a block trace control request. + */ + size_t size; + int r, ctl, entries; + + switch (request) { + case BIOCTRACEBUF: + /* The size cannot be changed when tracing is enabled. */ + if (trace_enabled) return EBUSY; + + /* Copy in the requested size. */ + if ((r = sys_safecopyfrom(endpt, grant, 0, (vir_bytes) &size, + sizeof(size), D)) != OK) + return r; + + if (size >= INT_MAX / sizeof(btrace_entry)) return EINVAL; + + /* The size can only be set or reset, not changed. */ + if (size > 0 && trace_size > 0) return EBUSY; + + /* Allocate or free a buffer for tracing data. For future multi-device + * tracing support, the buffer is associated with a minor device. + */ + if (size == 0) { + if (trace_dev == NO_TRACEDEV) return OK; + + if (trace_dev != minor) return EINVAL; + + free(trace_buf); + + trace_dev = NO_TRACEDEV; + } else { + if ((trace_buf = malloc(size * sizeof(btrace_entry))) == NULL) + return errno; + + trace_dev = minor; + } + + trace_size = size; + trace_pos = 0; + trace_next = 0; + + return OK; + + case BIOCTRACECTL: + /* We can only start/stop tracing if the given device has a trace + * buffer associated with it. + */ + if (trace_dev != minor) return EINVAL; + + /* Copy in the request code. */ + if ((r = sys_safecopyfrom(endpt, grant, 0, (vir_bytes) &ctl, + sizeof(ctl), D)) != OK) + return r; + + /* Start or stop tracing. */ + switch (ctl) { + case BTCTL_START: + if (trace_enabled) return EBUSY; + + read_tsc_64(&trace_tsc); + + trace_enabled = TRUE; + + break; + + case BTCTL_STOP: + if (!trace_enabled) return EINVAL; + + trace_enabled = FALSE; + + /* Cancel all ongoing trace operations. */ + memset(trace_ptr, 0, sizeof(trace_ptr)); + + break; + + default: + return EINVAL; + } + + return OK; + + case BIOCTRACEGET: + /* We can only retrieve tracing entries if the given device has a trace + * buffer associated with it. + */ + if (trace_dev != minor) return EINVAL; + + if (trace_enabled) return EBUSY; + + /* How much can we copy out? */ + entries = MIN(trace_pos - trace_next, + _MINIX_IOCTL_SIZE_BIG(request) / sizeof(btrace_entry)); + + if (entries == 0) + return 0; + + if ((r = sys_safecopyto(endpt, grant, 0, + (vir_bytes) &trace_buf[trace_next], + entries * sizeof(btrace_entry), D)) != OK) + return r; + + trace_next += entries; + + return entries; + } + + return EINVAL; +} + +/*===========================================================================* + * trace_start * + *===========================================================================*/ +PUBLIC void trace_start(thread_id_t id, message *m_ptr) +{ +/* Start creating a trace entry. + */ + btrace_entry *entry; + int req; + u64_t pos; + size_t size; + int flags; + + if (!trace_enabled || trace_dev != m_ptr->BDEV_MINOR) return; + + assert(id >= 0 && id < BLOCKDRIVER_MT_MAX_WORKERS + 1); + + if (trace_pos == trace_size) + return; + + switch (m_ptr->m_type) { + case BDEV_OPEN: req = BTREQ_OPEN; break; + case BDEV_CLOSE: req = BTREQ_CLOSE; break; + case BDEV_READ: req = BTREQ_READ; break; + case BDEV_WRITE: req = BTREQ_WRITE; break; + case BDEV_GATHER: req = BTREQ_GATHER; break; + case BDEV_SCATTER: req = BTREQ_SCATTER; break; + case BDEV_IOCTL: req = BTREQ_IOCTL; break; + default: return; + } + + switch (m_ptr->m_type) { + case BDEV_OPEN: + case BDEV_CLOSE: + pos = cvu64(0); + size = m_ptr->BDEV_ACCESS; + flags = 0; + + break; + + case BDEV_READ: + case BDEV_WRITE: + case BDEV_GATHER: + case BDEV_SCATTER: + pos = make64(m_ptr->BDEV_POS_LO, m_ptr->BDEV_POS_HI); + size = m_ptr->BDEV_COUNT; + flags = m_ptr->BDEV_FLAGS; + + break; + + case BDEV_IOCTL: + pos = cvu64(0); + size = m_ptr->BDEV_REQUEST; + flags = 0; + + /* Do not log trace control requests. */ + switch (size) { + case BIOCTRACEBUF: + case BIOCTRACECTL: + case BIOCTRACEGET: + return; + } + + break; + + default: + /* Do not log any other messages. */ + return; + } + + entry = &trace_buf[trace_pos]; + entry->request = req; + entry->size = size; + entry->position = pos; + entry->flags = flags; + entry->result = BTRES_INPROGRESS; + entry->start_time = trace_gettime(); + entry->finish_time = NO_TIME; + + trace_ptr[id] = entry; + trace_pos++; +} + +/*===========================================================================* + * trace_setsize * + *===========================================================================*/ +PUBLIC void trace_setsize(thread_id_t id, size_t size) +{ +/* Set the current trace entry's actual (byte) size, for vector requests. + */ + btrace_entry *entry; + + if (!trace_enabled) return; + + assert(id >= 0 && id < BLOCKDRIVER_MT_MAX_WORKERS + 1); + + if ((entry = trace_ptr[id]) == NULL) return; + + entry->size = size; +} + +/*===========================================================================* + * trace_finish * + *===========================================================================*/ +PUBLIC void trace_finish(thread_id_t id, int result) +{ +/* Finish a trace entry. + */ + btrace_entry *entry; + + if (!trace_enabled) return; + + assert(id >= 0 && id < BLOCKDRIVER_MT_MAX_WORKERS + 1); + + if ((entry = trace_ptr[id]) == NULL) return; + + entry->result = result; + entry->finish_time = trace_gettime(); + + trace_ptr[id] = NULL; +} diff --git a/lib/libblockdriver/trace.h b/lib/libblockdriver/trace.h new file mode 100644 index 000000000..041a9543a --- /dev/null +++ b/lib/libblockdriver/trace.h @@ -0,0 +1,11 @@ +#ifndef _BLOCKDRIVER_TRACE_H +#define _BLOCKDRIVER_TRACE_H + +_PROTOTYPE( int trace_ctl, (dev_t minor, unsigned int request, + endpoint_t endpt, cp_grant_id_t grant)); + +_PROTOTYPE( void trace_start, (thread_id_t thread_id, message *m_ptr)); +_PROTOTYPE( void trace_setsize, (thread_id_t thread_id, size_t size)); +_PROTOTYPE( void trace_finish, (thread_id_t thread_id, int r)); + +#endif /* _BLOCKDRIVER_TRACE_H */ diff --git a/man/man8/Makefile b/man/man8/Makefile index 8dde810a4..1a26413a3 100644 --- a/man/man8/Makefile +++ b/man/man8/Makefile @@ -1,4 +1,4 @@ -MAN= add_route.8 backup.8 badblocks.8 boot.8 \ +MAN= add_route.8 backup.8 badblocks.8 boot.8 btrace.8 \ cdprobe.8 checkhier.8 chown.8 cleantmp.8 config.8 cron.8 \ dhcpd.8 diskctl.8 dosminix.8 elvprsv.8 fdisk.8 fingerd.8 ftpd.8 \ getty.8 halt.8 hgfs.8 httpd.8 ifconfig.8 inet.8 init.8 \ diff --git a/man/man8/btrace.8 b/man/man8/btrace.8 new file mode 100644 index 000000000..77c405245 --- /dev/null +++ b/man/man8/btrace.8 @@ -0,0 +1,59 @@ +.TH BTRACE 8 +.SH NAME +btrace \- block-level tracing interface +.SH SYNOPSIS +\fBbtrace\fR \fBstart\fR \fIdevice\fR \fIentries\fR +.PP +\fBbtrace\fR \fBstop\fR \fIdevice\fR \fIfile\fR +.PP +\fBbtrace\fR \fBreset\fR \fIdevice\fR +.PP +\fBbtrace\fR \fBdump\fR \fIfile\fR +.SH DESCRIPTION +The \fBbtrace\fR tool is the user interface to MINIX3's block-level tracing +facility. It allows one to start, stop, and reset tracing, and dump a trace +output file in a somewhat human-readable format. +.SH COMMANDS +.TP 10 +\fBstart\fR +This command starts tracing all block requests on the given \fIdevice\fR. Each +block request takes up one entry in the log. The \fIentries\fR parameter +specifies the allocation side of the log in the driver process, in number of +(32-byte) entries. Once the log is full, no more entries will be added. +.TP 10 +\fBstop\fR +This command stops tracing on the given \fIdevice\fR, and dumps the resulting +log to the given output \fIfile\fR. +.TP 10 +\fBreset\fR +This command stops tracing on the given \fIdevice\fR and resets the state of +the block tracer. This should be useful only in emergency situations. +.TP 10 +\fBdump\fR +Dump the contents of a log file generated earlier with \fBbtrace stop\fR, in +human-readable format. Heavy users of the block tracing facility will probably +want to write their own tools for parsing and visualizing dump files. +.SH LIMITATIONS +Only one block device can be traced per driver at once. It is therefore also +not possible to trace a device and all its partitions at the same time. The +tracing facility has been designed for tracing activity of a single file +system, in which case these limitations are not important. +.PP +The log will always start with a \fIclose\fR operation on the device, since +\fBbtrace\fR closes the file descriptor used to instruct the driver to start +tracing. Similarly, for logs that have not already filled up during tracing, +the last entry will be a \fBbtrace\fR-triggered \fIopen\fR operation. +.SH EXAMPLES +.TP 35 +.B btrace start /dev/c2d0 10240 +# Start a block trace on c2d0. +.TP 35 +.B btrace stop /dev/c2d0 outfile +# Stop the block trace on c2d0. +.TP 35 +.B btrace dump outfile +# View the output of the trace. +.SH "SEE ALSO" +.BR ioctl (2). +.SH AUTHOR +David van Moolenbroek -- 2.44.0