2016-11-08 12:32:37 +08:00
|
|
|
#ifndef BLK_STAT_H
|
|
|
|
#define BLK_STAT_H
|
|
|
|
|
blk-stat: convert to callback-based statistics reporting
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>
2017-03-21 23:56:08 +08:00
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/blkdev.h>
|
|
|
|
#include <linux/ktime.h>
|
|
|
|
#include <linux/rcupdate.h>
|
|
|
|
#include <linux/timer.h>
|
2016-11-08 12:32:37 +08:00
|
|
|
|
|
|
|
/*
|
2017-03-28 06:19:41 +08:00
|
|
|
* from upper:
|
|
|
|
* 3 bits: reserved for other usage
|
|
|
|
* 12 bits: size
|
|
|
|
* 49 bits: time
|
2016-11-08 12:32:37 +08:00
|
|
|
*/
|
|
|
|
#define BLK_STAT_RES_BITS 3
|
2017-03-28 06:19:41 +08:00
|
|
|
#define BLK_STAT_SIZE_BITS 12
|
|
|
|
#define BLK_STAT_RES_SHIFT (64 - BLK_STAT_RES_BITS)
|
|
|
|
#define BLK_STAT_SIZE_SHIFT (BLK_STAT_RES_SHIFT - BLK_STAT_SIZE_BITS)
|
|
|
|
#define BLK_STAT_TIME_MASK ((1ULL << BLK_STAT_SIZE_SHIFT) - 1)
|
|
|
|
#define BLK_STAT_SIZE_MASK \
|
|
|
|
(((1ULL << BLK_STAT_SIZE_BITS) - 1) << BLK_STAT_SIZE_SHIFT)
|
|
|
|
#define BLK_STAT_RES_MASK (~((1ULL << BLK_STAT_RES_SHIFT) - 1))
|
2016-11-08 12:32:37 +08:00
|
|
|
|
blk-stat: convert to callback-based statistics reporting
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>
2017-03-21 23:56:08 +08:00
|
|
|
/**
|
|
|
|
* struct blk_stat_callback - Block statistics callback.
|
|
|
|
*
|
|
|
|
* A &struct blk_stat_callback is associated with a &struct request_queue. While
|
|
|
|
* @timer is active, that queue's request completion latencies are sorted into
|
|
|
|
* buckets by @bucket_fn and added to a per-cpu buffer, @cpu_stat. When the
|
|
|
|
* timer fires, @cpu_stat is flushed to @stat and @timer_fn is invoked.
|
|
|
|
*/
|
|
|
|
struct blk_stat_callback {
|
|
|
|
/*
|
|
|
|
* @list: RCU list of callbacks for a &struct request_queue.
|
|
|
|
*/
|
|
|
|
struct list_head list;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @timer: Timer for the next callback invocation.
|
|
|
|
*/
|
|
|
|
struct timer_list timer;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @cpu_stat: Per-cpu statistics buckets.
|
|
|
|
*/
|
|
|
|
struct blk_rq_stat __percpu *cpu_stat;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @bucket_fn: Given a request, returns which statistics bucket it
|
|
|
|
* should be accounted under.
|
|
|
|
*/
|
|
|
|
unsigned int (*bucket_fn)(const struct request *);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @buckets: Number of statistics buckets.
|
|
|
|
*/
|
|
|
|
unsigned int buckets;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @stat: Array of statistics buckets.
|
|
|
|
*/
|
|
|
|
struct blk_rq_stat *stat;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @fn: Callback function.
|
|
|
|
*/
|
|
|
|
void (*timer_fn)(struct blk_stat_callback *);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @data: Private pointer for the user.
|
|
|
|
*/
|
|
|
|
void *data;
|
|
|
|
|
|
|
|
struct rcu_head rcu;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct blk_queue_stats *blk_alloc_queue_stats(void);
|
|
|
|
void blk_free_queue_stats(struct blk_queue_stats *);
|
|
|
|
|
|
|
|
void blk_stat_add(struct request *);
|
|
|
|
|
2016-11-08 12:32:37 +08:00
|
|
|
static inline u64 __blk_stat_time(u64 time)
|
|
|
|
{
|
|
|
|
return time & BLK_STAT_TIME_MASK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline u64 blk_stat_time(struct blk_issue_stat *stat)
|
|
|
|
{
|
2017-03-28 06:19:41 +08:00
|
|
|
return __blk_stat_time(stat->stat);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline sector_t blk_capped_size(sector_t size)
|
|
|
|
{
|
|
|
|
return size & ((1ULL << BLK_STAT_SIZE_BITS) - 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline sector_t blk_stat_size(struct blk_issue_stat *stat)
|
|
|
|
{
|
|
|
|
return (stat->stat & BLK_STAT_SIZE_MASK) >> BLK_STAT_SIZE_SHIFT;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void blk_stat_set_issue(struct blk_issue_stat *stat,
|
|
|
|
sector_t size)
|
|
|
|
{
|
|
|
|
stat->stat = (stat->stat & BLK_STAT_RES_MASK) |
|
|
|
|
(ktime_to_ns(ktime_get()) & BLK_STAT_TIME_MASK) |
|
|
|
|
(((u64)blk_capped_size(size)) << BLK_STAT_SIZE_SHIFT);
|
2016-11-08 12:32:37 +08:00
|
|
|
}
|
|
|
|
|
blk-throttle: add a mechanism to estimate IO latency
User configures latency target, but the latency threshold for each
request size isn't fixed. For a SSD, the IO latency highly depends on
request size. To calculate latency threshold, we sample some data, eg,
average latency for request size 4k, 8k, 16k, 32k .. 1M. The latency
threshold of each request size will be the sample latency (I'll call it
base latency) plus latency target. For example, the base latency for
request size 4k is 80us and user configures latency target 60us. The 4k
latency threshold will be 80 + 60 = 140us.
To sample data, we calculate the order base 2 of rounded up IO sectors.
If the IO size is bigger than 1M, it will be accounted as 1M. Since the
calculation does round up, the base latency will be slightly smaller
than actual value. Also if there isn't any IO dispatched for a specific
IO size, we will use the base latency of smaller IO size for this IO
size.
But we shouldn't sample data at any time. The base latency is supposed
to be latency where disk isn't congested, because we use latency
threshold to schedule IOs between cgroups. If disk is congested, the
latency is higher, using it for scheduling is meaningless. Hence we only
do the sampling when block throttling is in the LOW limit, with
assumption disk isn't congested in such state. If the assumption isn't
true, eg, low limit is too high, calculated latency threshold will be
higher.
Hard disk is completely different. Latency depends on spindle seek
instead of request size. Currently this feature is SSD only, we probably
can use a fixed threshold like 4ms for hard disk though.
Signed-off-by: Shaohua Li <shli@fb.com>
Signed-off-by: Jens Axboe <axboe@fb.com>
2017-03-28 06:19:42 +08:00
|
|
|
/* record time/size info in request but not add a callback */
|
|
|
|
void blk_stat_enable_accounting(struct request_queue *q);
|
|
|
|
|
blk-stat: convert to callback-based statistics reporting
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>
2017-03-21 23:56:08 +08:00
|
|
|
/*
|
|
|
|
* blk_stat_rq_ddir() - Bucket callback function for the request data direction.
|
|
|
|
* @rq: Request.
|
|
|
|
*
|
|
|
|
* This is the same as rq_data_dir() but as a function so it can be used as
|
|
|
|
* @bucket_fn for blk_stat_alloc_callback().
|
|
|
|
*
|
|
|
|
* Return: Data direction of the request, either READ or WRITE.
|
|
|
|
*/
|
|
|
|
unsigned int blk_stat_rq_ddir(const struct request *rq);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* blk_stat_alloc_callback() - Allocate a block statistics callback.
|
|
|
|
* @timer_fn: Timer callback function.
|
|
|
|
* @bucket_fn: Bucket callback function.
|
|
|
|
* @buckets: Number of statistics buckets.
|
|
|
|
* @data: Value for the @data field of the &struct blk_stat_callback.
|
|
|
|
*
|
|
|
|
* See &struct blk_stat_callback for details on the callback functions.
|
|
|
|
*
|
|
|
|
* Return: &struct blk_stat_callback on success or NULL on ENOMEM.
|
|
|
|
*/
|
|
|
|
struct blk_stat_callback *
|
|
|
|
blk_stat_alloc_callback(void (*timer_fn)(struct blk_stat_callback *),
|
|
|
|
unsigned int (*bucket_fn)(const struct request *),
|
|
|
|
unsigned int buckets, void *data);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* blk_stat_add_callback() - Add a block statistics callback to be run on a
|
|
|
|
* request queue.
|
|
|
|
* @q: The request queue.
|
|
|
|
* @cb: The callback.
|
|
|
|
*
|
|
|
|
* Note that a single &struct blk_stat_callback can only be added to a single
|
|
|
|
* &struct request_queue.
|
|
|
|
*/
|
|
|
|
void blk_stat_add_callback(struct request_queue *q,
|
|
|
|
struct blk_stat_callback *cb);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* blk_stat_remove_callback() - Remove a block statistics callback from a
|
|
|
|
* request queue.
|
|
|
|
* @q: The request queue.
|
|
|
|
* @cb: The callback.
|
|
|
|
*
|
|
|
|
* When this returns, the callback is not running on any CPUs and will not be
|
|
|
|
* called again unless readded.
|
|
|
|
*/
|
|
|
|
void blk_stat_remove_callback(struct request_queue *q,
|
|
|
|
struct blk_stat_callback *cb);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* blk_stat_free_callback() - Free a block statistics callback.
|
|
|
|
* @cb: The callback.
|
|
|
|
*
|
|
|
|
* @cb may be NULL, in which case this does nothing. If it is not NULL, @cb must
|
|
|
|
* not be associated with a request queue. I.e., if it was previously added with
|
|
|
|
* blk_stat_add_callback(), it must also have been removed since then with
|
|
|
|
* blk_stat_remove_callback().
|
|
|
|
*/
|
|
|
|
void blk_stat_free_callback(struct blk_stat_callback *cb);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* blk_stat_is_active() - Check if a block statistics callback is currently
|
|
|
|
* gathering statistics.
|
|
|
|
* @cb: The callback.
|
|
|
|
*/
|
|
|
|
static inline bool blk_stat_is_active(struct blk_stat_callback *cb)
|
|
|
|
{
|
|
|
|
return timer_pending(&cb->timer);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* blk_stat_activate_nsecs() - Gather block statistics during a time window in
|
|
|
|
* nanoseconds.
|
|
|
|
* @cb: The callback.
|
|
|
|
* @nsecs: Number of nanoseconds to gather statistics for.
|
|
|
|
*
|
|
|
|
* The timer callback will be called when the window expires.
|
|
|
|
*/
|
|
|
|
static inline void blk_stat_activate_nsecs(struct blk_stat_callback *cb,
|
|
|
|
u64 nsecs)
|
|
|
|
{
|
|
|
|
mod_timer(&cb->timer, jiffies + nsecs_to_jiffies(nsecs));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* blk_stat_activate_msecs() - Gather block statistics during a time window in
|
|
|
|
* milliseconds.
|
|
|
|
* @cb: The callback.
|
|
|
|
* @msecs: Number of milliseconds to gather statistics for.
|
|
|
|
*
|
|
|
|
* The timer callback will be called when the window expires.
|
|
|
|
*/
|
|
|
|
static inline void blk_stat_activate_msecs(struct blk_stat_callback *cb,
|
|
|
|
unsigned int msecs)
|
|
|
|
{
|
|
|
|
mod_timer(&cb->timer, jiffies + msecs_to_jiffies(msecs));
|
|
|
|
}
|
|
|
|
|
2016-11-08 12:32:37 +08:00
|
|
|
#endif
|