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

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