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.
This commit is contained in:
David van Moolenbroek 2011-11-24 14:50:13 +01:00
parent a9f89a7290
commit fd4c2b74f3
20 changed files with 682 additions and 30 deletions

View file

@ -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 \

4
commands/btrace/Makefile Normal file
View file

@ -0,0 +1,4 @@
PROG= btrace
MAN=
.include <bsd.prog.mk>

218
commands/btrace/btrace.c Normal file
View file

@ -0,0 +1,218 @@
/* Block trace command line tool */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <minix/types.h>
#include <minix/btrace.h>
#include <sys/ioc_block.h>
static void usage(char *name)
{
printf("usage:\n"
"%s start <device> <nr_entries>\n"
"%s stop <device> <file>\n"
"%s reset <device>\n"
"%s dump <file>\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;
}

View file

@ -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 \

View file

@ -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 */

View file

@ -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 <minix/blockdriver.h>
/* 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) );

View file

@ -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 */

View file

@ -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

View file

@ -0,0 +1,15 @@
/* sys/ioc_block.h - Block ioctl() command codes.
*
*/
#ifndef _S_I_BLOCK_H
#define _S_I_BLOCK_H
#include <minix/ioctl.h>
#include <minix/btrace.h>
#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 */

View file

@ -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

View file

@ -40,10 +40,12 @@
#include <minix/drivers.h>
#include <minix/blockdriver.h>
#include <minix/ds.h>
#include <sys/ioc_block.h>
#include <sys/ioc_disk.h>
#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;
}

View file

@ -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) );

View file

@ -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];

View file

@ -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);
}

View file

@ -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;

View file

@ -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) );

283
lib/libblockdriver/trace.c Normal file
View file

@ -0,0 +1,283 @@
/* This file implements block level tracing support. */
#include <minix/drivers.h>
#include <minix/blockdriver_mt.h>
#include <minix/btrace.h>
#include <sys/ioc_block.h>
#include <minix/minlib.h>
#include <assert.h>
#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;
}

View file

@ -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 */

View file

@ -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 \

59
man/man8/btrace.8 Normal file
View file

@ -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 <david@minix3.org>