Skip to content

Commit

Permalink
blk-stat: convert to callback-based statistics reporting
Browse files Browse the repository at this point in the history
Currently, statistics are gathered in ~0.13s windows, and users grab the
statistics whenever they need them. This is not ideal for both in-tree
users:

1. Writeback throttling wants its own dynamically sized window of
   statistics. Since the blk-stats statistics are reset after every
   window and the wbt windows don't line up with the blk-stats windows,
   wbt doesn't see every I/O.
2. Polling currently grabs the statistics on every I/O. Again, depending
   on how the window lines up, we may miss some I/Os. It's also
   unnecessary overhead to get the statistics on every I/O; the hybrid
   polling heuristic would be just as happy with the statistics from the
   previous full window.

This reworks the blk-stats infrastructure to be callback-based: users
register a callback that they want called at a given time with all of
the statistics from the window during which the callback was active.
Users can dynamically bucketize the statistics. wbt and polling both
currently use read vs. write, but polling can be extended to further
subdivide based on request size.

The callbacks are kept on an RCU list, and each callback has percpu
stats buffers. There will only be a few users, so the overhead on the
I/O completion side is low. The stats flushing is also simplified
considerably: since the timer function is responsible for clearing the
statistics, we don't have to worry about stale statistics.

wbt is a trivial conversion. After the conversion, the windowing problem
mentioned above is fixed.

For polling, we register an extra callback that caches the previous
window's statistics in the struct request_queue for the hybrid polling
heuristic to use.

Since we no longer have a single stats buffer for the request queue,
this also removes the sysfs and debugfs stats entries. To replace those,
we add a debugfs entry for the poll statistics.

Signed-off-by: Omar Sandoval <osandov@fb.com>
Signed-off-by: Jens Axboe <axboe@fb.com>
  • Loading branch information
Omar Sandoval authored and Jens Axboe committed Mar 21, 2017
1 parent 4875253 commit 34dbad5
Show file tree
Hide file tree
Showing 11 changed files with 449 additions and 320 deletions.
6 changes: 5 additions & 1 deletion block/blk-core.c
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,10 @@ static blk_qc_t blk_queue_bio(struct request_queue *q, struct bio *bio);

int blk_init_allocated_queue(struct request_queue *q)
{
q->stats = blk_alloc_queue_stats();
if (!q->stats)
return -ENOMEM;

q->fq = blk_alloc_flush_queue(q, NUMA_NO_NODE, q->cmd_size);
if (!q->fq)
return -ENOMEM;
Expand Down Expand Up @@ -2698,7 +2702,7 @@ void blk_finish_request(struct request *req, int error)
struct request_queue *q = req->q;

if (req->rq_flags & RQF_STATS)
blk_stat_add(&q->rq_stats[rq_data_dir(req)], req);
blk_stat_add(req);

if (req->rq_flags & RQF_QUEUED)
blk_queue_end_tag(q, req);
Expand Down
99 changes: 44 additions & 55 deletions block/blk-mq-debugfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,42 @@ static int blk_mq_debugfs_seq_open(struct inode *inode, struct file *file,
return ret;
}

static void print_stat(struct seq_file *m, struct blk_rq_stat *stat)
{
if (stat->nr_samples) {
seq_printf(m, "samples=%d, mean=%lld, min=%llu, max=%llu",
stat->nr_samples, stat->mean, stat->min, stat->max);
} else {
seq_puts(m, "samples=0");
}
}

static int queue_poll_stat_show(struct seq_file *m, void *v)
{
struct request_queue *q = m->private;

seq_puts(m, "read: ");
print_stat(m, &q->poll_stat[READ]);
seq_puts(m, "\n");

seq_puts(m, "write: ");
print_stat(m, &q->poll_stat[WRITE]);
seq_puts(m, "\n");
return 0;
}

static int queue_poll_stat_open(struct inode *inode, struct file *file)
{
return single_open(file, queue_poll_stat_show, inode->i_private);
}

static const struct file_operations queue_poll_stat_fops = {
.open = queue_poll_stat_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};

static int hctx_state_show(struct seq_file *m, void *v)
{
struct blk_mq_hw_ctx *hctx = m->private;
Expand Down Expand Up @@ -322,60 +358,6 @@ static const struct file_operations hctx_io_poll_fops = {
.release = single_release,
};

static void print_stat(struct seq_file *m, struct blk_rq_stat *stat)
{
seq_printf(m, "samples=%d, mean=%lld, min=%llu, max=%llu",
stat->nr_samples, stat->mean, stat->min, stat->max);
}

static int hctx_stats_show(struct seq_file *m, void *v)
{
struct blk_mq_hw_ctx *hctx = m->private;
struct blk_rq_stat stat[2];

blk_stat_init(&stat[READ]);
blk_stat_init(&stat[WRITE]);

blk_hctx_stat_get(hctx, stat);

seq_puts(m, "read: ");
print_stat(m, &stat[READ]);
seq_puts(m, "\n");

seq_puts(m, "write: ");
print_stat(m, &stat[WRITE]);
seq_puts(m, "\n");
return 0;
}

static int hctx_stats_open(struct inode *inode, struct file *file)
{
return single_open(file, hctx_stats_show, inode->i_private);
}

static ssize_t hctx_stats_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
struct seq_file *m = file->private_data;
struct blk_mq_hw_ctx *hctx = m->private;
struct blk_mq_ctx *ctx;
int i;

hctx_for_each_ctx(hctx, ctx, i) {
blk_stat_init(&ctx->stat[READ]);
blk_stat_init(&ctx->stat[WRITE]);
}
return count;
}

static const struct file_operations hctx_stats_fops = {
.open = hctx_stats_open,
.read = seq_read,
.write = hctx_stats_write,
.llseek = seq_lseek,
.release = single_release,
};

static int hctx_dispatched_show(struct seq_file *m, void *v)
{
struct blk_mq_hw_ctx *hctx = m->private;
Expand Down Expand Up @@ -636,6 +618,11 @@ static const struct file_operations ctx_completed_fops = {
.release = single_release,
};

static const struct blk_mq_debugfs_attr blk_mq_debugfs_queue_attrs[] = {
{"poll_stat", 0400, &queue_poll_stat_fops},
{},
};

static const struct blk_mq_debugfs_attr blk_mq_debugfs_hctx_attrs[] = {
{"state", 0400, &hctx_state_fops},
{"flags", 0400, &hctx_flags_fops},
Expand All @@ -646,7 +633,6 @@ static const struct blk_mq_debugfs_attr blk_mq_debugfs_hctx_attrs[] = {
{"sched_tags", 0400, &hctx_sched_tags_fops},
{"sched_tags_bitmap", 0400, &hctx_sched_tags_bitmap_fops},
{"io_poll", 0600, &hctx_io_poll_fops},
{"stats", 0600, &hctx_stats_fops},
{"dispatched", 0600, &hctx_dispatched_fops},
{"queued", 0600, &hctx_queued_fops},
{"run", 0600, &hctx_run_fops},
Expand Down Expand Up @@ -753,6 +739,9 @@ int blk_mq_debugfs_register_hctxs(struct request_queue *q)
if (!q->mq_debugfs_dir)
goto err;

if (!debugfs_create_files(q->mq_debugfs_dir, q, blk_mq_debugfs_queue_attrs))
goto err;

queue_for_each_hw_ctx(q, hctx, i) {
if (blk_mq_debugfs_register_hctx(q, hctx))
goto err;
Expand Down
76 changes: 52 additions & 24 deletions block/blk-mq.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
static DEFINE_MUTEX(all_q_mutex);
static LIST_HEAD(all_q_list);

static void blk_mq_poll_stats_start(struct request_queue *q);
static void blk_mq_poll_stats_fn(struct blk_stat_callback *cb);

/*
* Check if any of the ctx's have pending work in this hardware queue
*/
Expand Down Expand Up @@ -432,15 +435,8 @@ static void blk_mq_ipi_complete_request(struct request *rq)
static void blk_mq_stat_add(struct request *rq)
{
if (rq->rq_flags & RQF_STATS) {
/*
* We could rq->mq_ctx here, but there's less of a risk
* of races if we have the completion event add the stats
* to the local software queue.
*/
struct blk_mq_ctx *ctx;

ctx = __blk_mq_get_ctx(rq->q, raw_smp_processor_id());
blk_stat_add(&ctx->stat[rq_data_dir(rq)], rq);
blk_mq_poll_stats_start(rq->q);
blk_stat_add(rq);
}
}

Expand Down Expand Up @@ -2040,8 +2036,6 @@ static void blk_mq_init_cpu_queues(struct request_queue *q,
spin_lock_init(&__ctx->lock);
INIT_LIST_HEAD(&__ctx->rq_list);
__ctx->queue = q;
blk_stat_init(&__ctx->stat[READ]);
blk_stat_init(&__ctx->stat[WRITE]);

/* If the cpu isn't online, the cpu is mapped to first hctx */
if (!cpu_online(i))
Expand Down Expand Up @@ -2339,6 +2333,15 @@ struct request_queue *blk_mq_init_allocated_queue(struct blk_mq_tag_set *set,
/* mark the queue as mq asap */
q->mq_ops = set->ops;

q->stats = blk_alloc_queue_stats();
if (!q->stats)
goto err_exit;

q->poll_cb = blk_stat_alloc_callback(blk_mq_poll_stats_fn,
blk_stat_rq_ddir, 2, q);
if (!q->poll_cb)
goto err_exit;

q->queue_ctx = alloc_percpu(struct blk_mq_ctx);
if (!q->queue_ctx)
goto err_exit;
Expand Down Expand Up @@ -2740,27 +2743,52 @@ void blk_mq_update_nr_hw_queues(struct blk_mq_tag_set *set, int nr_hw_queues)
}
EXPORT_SYMBOL_GPL(blk_mq_update_nr_hw_queues);

/* Enable polling stats and return whether they were already enabled. */
static bool blk_poll_stats_enable(struct request_queue *q)
{
if (test_bit(QUEUE_FLAG_POLL_STATS, &q->queue_flags) ||
test_and_set_bit(QUEUE_FLAG_POLL_STATS, &q->queue_flags))
return true;
blk_stat_add_callback(q, q->poll_cb);
return false;
}

static void blk_mq_poll_stats_start(struct request_queue *q)
{
/*
* We don't arm the callback if polling stats are not enabled or the
* callback is already active.
*/
if (!test_bit(QUEUE_FLAG_POLL_STATS, &q->queue_flags) ||
blk_stat_is_active(q->poll_cb))
return;

blk_stat_activate_msecs(q->poll_cb, 100);
}

static void blk_mq_poll_stats_fn(struct blk_stat_callback *cb)
{
struct request_queue *q = cb->data;

if (cb->stat[READ].nr_samples)
q->poll_stat[READ] = cb->stat[READ];
if (cb->stat[WRITE].nr_samples)
q->poll_stat[WRITE] = cb->stat[WRITE];
}

static unsigned long blk_mq_poll_nsecs(struct request_queue *q,
struct blk_mq_hw_ctx *hctx,
struct request *rq)
{
struct blk_rq_stat stat[2];
unsigned long ret = 0;

/*
* If stats collection isn't on, don't sleep but turn it on for
* future users
*/
if (!blk_stat_enable(q))
if (!blk_poll_stats_enable(q))
return 0;

/*
* We don't have to do this once per IO, should optimize this
* to just use the current window of stats until it changes
*/
memset(&stat, 0, sizeof(stat));
blk_hctx_stat_get(hctx, stat);

/*
* As an optimistic guess, use half of the mean service time
* for this type of request. We can (and should) make this smarter.
Expand All @@ -2769,10 +2797,10 @@ static unsigned long blk_mq_poll_nsecs(struct request_queue *q,
* important on devices where the completion latencies are longer
* than ~10 usec.
*/
if (req_op(rq) == REQ_OP_READ && stat[READ].nr_samples)
ret = (stat[READ].mean + 1) / 2;
else if (req_op(rq) == REQ_OP_WRITE && stat[WRITE].nr_samples)
ret = (stat[WRITE].mean + 1) / 2;
if (req_op(rq) == REQ_OP_READ && q->poll_stat[READ].nr_samples)
ret = (q->poll_stat[READ].mean + 1) / 2;
else if (req_op(rq) == REQ_OP_WRITE && q->poll_stat[WRITE].nr_samples)
ret = (q->poll_stat[WRITE].mean + 1) / 2;

return ret;
}
Expand Down
1 change: 0 additions & 1 deletion block/blk-mq.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ struct blk_mq_ctx {

/* incremented at completion time */
unsigned long ____cacheline_aligned_in_smp rq_completed[2];
struct blk_rq_stat stat[2];

struct request_queue *queue;
struct kobject kobj;
Expand Down
Loading

0 comments on commit 34dbad5

Please sign in to comment.