NVME trim clocking

Add the ability to set two goals for trims in the I/O scheduler. The
first goal is the number of BIO_DELETEs to accumulate
(kern.cam.XX.U.trim_goal). When non-zero, this many trims will be
accumulated before we start to transfer them to lower layers. This is
useful for devices that like to get lots of trims all at once in one
transaction (not all devices are like this, and some vary by workload).

The second is a number of ticks to defer trims. If you've set a trim
goal, then kern.cam.XX.U.trim_ticks controls how long the system will
defer those trims before timing out and sending them anyway. It has no
effect when trim_goal is 0.

In any event, a BIO_FLUSH will cause all the TRIMs to be released to
the periph drivers. This may be a minor overloading of what BIO_FLUSH
is supposed to mean, but it's useful to preserve other ordering
semantics that users of BIO_FLUSH reply on.

Sponsored by: Netflix, Inc
This commit is contained in:
Warner Losh 2018-11-27 00:36:35 +00:00
parent 4391a9c0e9
commit d900ade516
2 changed files with 85 additions and 7 deletions

View File

@ -277,6 +277,10 @@ struct cam_iosched_softc {
/* scheduler flags < 16, user flags >= 16 */
uint32_t flags;
int sort_io_queue;
int trim_goal; /* # of trims to queue before sending */
int trim_ticks; /* Max ticks to hold trims */
int last_trim_tick; /* Last 'tick' time ld a trim */
int queued_trims; /* Number of trims in the queue */
#ifdef CAM_IOSCHED_DYNAMIC
int read_bias; /* Read bias setting */
int current_read_bias; /* Current read bias state */
@ -751,6 +755,22 @@ cam_iosched_has_io(struct cam_iosched_softc *isc)
static inline bool
cam_iosched_has_more_trim(struct cam_iosched_softc *isc)
{
/*
* If we've set a trim_goal, then if we exceed that allow trims
* to be passed back to the driver. If we've also set a tick timeout
* allow trims back to the driver. Otherwise, don't allow trims yet.
*/
if (isc->trim_goal > 0) {
if (isc->queued_trims >= isc->trim_goal)
return true;
if (isc->queued_trims > 0 &&
isc->trim_ticks > 0 &&
ticks - isc->last_trim_tick > isc->trim_ticks)
return true;
return false;
}
return !(isc->flags & CAM_IOSCHED_FLAG_TRIM_ACTIVE) &&
bioq_first(&isc->trim_queue);
}
@ -1131,14 +1151,21 @@ cam_iosched_fini(struct cam_iosched_softc *isc)
void cam_iosched_sysctl_init(struct cam_iosched_softc *isc,
struct sysctl_ctx_list *ctx, struct sysctl_oid *node)
{
#ifdef CAM_IOSCHED_DYNAMIC
struct sysctl_oid_list *n;
#endif
SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(node),
n = SYSCTL_CHILDREN(node);
SYSCTL_ADD_INT(ctx, n,
OID_AUTO, "sort_io_queue", CTLFLAG_RW | CTLFLAG_MPSAFE,
&isc->sort_io_queue, 0,
"Sort IO queue to try and optimise disk access patterns");
SYSCTL_ADD_INT(ctx, n,
OID_AUTO, "trim_goal", CTLFLAG_RW,
&isc->trim_goal, 0,
"Number of trims to try to accumulate before sending to hardware");
SYSCTL_ADD_INT(ctx, n,
OID_AUTO, "trim_ticks", CTLFLAG_RW,
&isc->trim_goal, 0,
"IO Schedul qaunta to hold back trims for when accumulating");
#ifdef CAM_IOSCHED_DYNAMIC
if (!do_dynamic_iosched)
@ -1192,6 +1219,41 @@ cam_iosched_set_latfcn(struct cam_iosched_softc *isc,
#endif
}
/*
* Client drivers can set two parameters. "goal" is the number of BIO_DELETEs
* that will be queued up before iosched will "release" the trims to the client
* driver to wo with what they will (usually combine as many as possible). If we
* don't get this many, after trim_ticks we'll submit the I/O anyway with
* whatever we have. We do need an I/O of some kind of to clock the deferred
* trims out to disk. Since we will eventually get a write for the super block
* or something before we shutdown, the trims will complete. To be safe, when a
* BIO_FLUSH is presented to the iosched work queue, we set the ticks time far
* enough in the past so we'll present the BIO_DELETEs to the client driver.
* There might be a race if no BIO_DELETESs were queued, a BIO_FLUSH comes in
* and then a BIO_DELETE is sent down. No know client does this, and there's
* already a race between an ordered BIO_FLUSH and any BIO_DELETEs in flight,
* but no client depends on the ordering being honored.
*
* XXX I'm not sure what the interaction between UFS direct BIOs and the BUF
* flushing on shutdown. I think there's bufs that would be dependent on the BIO
* finishing to write out at least metadata, so we'll be fine. To be safe, keep
* the number of ticks low (less than maybe 10s) to avoid shutdown races.
*/
void
cam_iosched_set_trim_goal(struct cam_iosched_softc *isc, int goal)
{
isc->trim_goal = goal;
}
void
cam_iosched_set_trim_ticks(struct cam_iosched_softc *isc, int trim_ticks)
{
isc->trim_ticks = trim_ticks;
}
/*
* Flush outstanding I/O. Consumers of this library don't know all the
* queues we may keep, so this allows all I/O to be flushed in one
@ -1281,6 +1343,9 @@ void
cam_iosched_put_back_trim(struct cam_iosched_softc *isc, struct bio *bp)
{
bioq_insert_head(&isc->trim_queue, bp);
if (isc->queued_trims == 0)
isc->last_trim_tick = ticks;
isc->queued_trims++;
#ifdef CAM_IOSCHED_DYNAMIC
isc->trim_stats.queued++;
isc->trim_stats.total--; /* since we put it back, don't double count */
@ -1304,6 +1369,8 @@ cam_iosched_next_trim(struct cam_iosched_softc *isc)
if (bp == NULL)
return NULL;
bioq_remove(&isc->trim_queue, bp);
isc->queued_trims--;
isc->last_trim_tick = ticks; /* Reset the tick timer when we take trims */
#ifdef CAM_IOSCHED_DYNAMIC
isc->trim_stats.queued--;
isc->trim_stats.total++;
@ -1430,12 +1497,22 @@ cam_iosched_queue_work(struct cam_iosched_softc *isc, struct bio *bp)
{
/*
* Put all trims on the trim queue sorted, since we know
* that the collapsing code requires this. Otherwise put
* the work on the bio queue.
* If we get a BIO_FLUSH, and we're doing delayed BIO_DELETEs then we
* set the last tick time to one less than the current ticks minus the
* delay to force the BIO_DELETEs to be presented to the client driver.
*/
if (bp->bio_cmd == BIO_FLUSH && isc->trim_ticks > 0)
isc->last_trim_tick = ticks - isc->trim_ticks - 1;
/*
* Put all trims on the trim queue. Otherwise put the work on the bio
* queue.
*/
if (bp->bio_cmd == BIO_DELETE) {
bioq_insert_tail(&isc->trim_queue, bp);
if (isc->queued_trims == 0)
isc->last_trim_tick = ticks;
isc->queued_trims++;
#ifdef CAM_IOSCHED_DYNAMIC
isc->trim_stats.in++;
isc->trim_stats.queued++;

View File

@ -101,6 +101,7 @@ void cam_iosched_clr_work_flags(struct cam_iosched_softc *isc, uint32_t flags);
void cam_iosched_trim_done(struct cam_iosched_softc *isc);
int cam_iosched_bio_complete(struct cam_iosched_softc *isc, struct bio *bp, union ccb *done_ccb);
void cam_iosched_set_latfcn(struct cam_iosched_softc *isc, cam_iosched_latfcn_t, void *);
void cam_iosched_set_trim_goal(struct cam_iosched_softc *isc, int goal);
void cam_iosched_set_trim_ticks(struct cam_iosched_softc *isc, int ticks);
#endif
#endif