|
@@ -708,3 +708,195 @@ static void throtl_charge_bio(struct throtl_grp *tg, struct bio *bio)
|
|
|
tg->bytes_disp[rw] += bio->bi_size;
|
|
|
tg->io_disp[rw]++;
|
|
|
|
|
|
+ throtl_update_dispatch_stats(tg_to_blkg(tg), bio->bi_size, bio->bi_rw);
|
|
|
+}
|
|
|
+
|
|
|
+static void throtl_add_bio_tg(struct throtl_data *td, struct throtl_grp *tg,
|
|
|
+ struct bio *bio)
|
|
|
+{
|
|
|
+ bool rw = bio_data_dir(bio);
|
|
|
+
|
|
|
+ bio_list_add(&tg->bio_lists[rw], bio);
|
|
|
+ /* Take a bio reference on tg */
|
|
|
+ blkg_get(tg_to_blkg(tg));
|
|
|
+ tg->nr_queued[rw]++;
|
|
|
+ td->nr_queued[rw]++;
|
|
|
+ throtl_enqueue_tg(td, tg);
|
|
|
+}
|
|
|
+
|
|
|
+static void tg_update_disptime(struct throtl_data *td, struct throtl_grp *tg)
|
|
|
+{
|
|
|
+ unsigned long read_wait = -1, write_wait = -1, min_wait = -1, disptime;
|
|
|
+ struct bio *bio;
|
|
|
+
|
|
|
+ if ((bio = bio_list_peek(&tg->bio_lists[READ])))
|
|
|
+ tg_may_dispatch(td, tg, bio, &read_wait);
|
|
|
+
|
|
|
+ if ((bio = bio_list_peek(&tg->bio_lists[WRITE])))
|
|
|
+ tg_may_dispatch(td, tg, bio, &write_wait);
|
|
|
+
|
|
|
+ min_wait = min(read_wait, write_wait);
|
|
|
+ disptime = jiffies + min_wait;
|
|
|
+
|
|
|
+ /* Update dispatch time */
|
|
|
+ throtl_dequeue_tg(td, tg);
|
|
|
+ tg->disptime = disptime;
|
|
|
+ throtl_enqueue_tg(td, tg);
|
|
|
+}
|
|
|
+
|
|
|
+static void tg_dispatch_one_bio(struct throtl_data *td, struct throtl_grp *tg,
|
|
|
+ bool rw, struct bio_list *bl)
|
|
|
+{
|
|
|
+ struct bio *bio;
|
|
|
+
|
|
|
+ bio = bio_list_pop(&tg->bio_lists[rw]);
|
|
|
+ tg->nr_queued[rw]--;
|
|
|
+ /* Drop bio reference on blkg */
|
|
|
+ blkg_put(tg_to_blkg(tg));
|
|
|
+
|
|
|
+ BUG_ON(td->nr_queued[rw] <= 0);
|
|
|
+ td->nr_queued[rw]--;
|
|
|
+
|
|
|
+ throtl_charge_bio(tg, bio);
|
|
|
+ bio_list_add(bl, bio);
|
|
|
+ bio->bi_rw |= REQ_THROTTLED;
|
|
|
+
|
|
|
+ throtl_trim_slice(td, tg, rw);
|
|
|
+}
|
|
|
+
|
|
|
+static int throtl_dispatch_tg(struct throtl_data *td, struct throtl_grp *tg,
|
|
|
+ struct bio_list *bl)
|
|
|
+{
|
|
|
+ unsigned int nr_reads = 0, nr_writes = 0;
|
|
|
+ unsigned int max_nr_reads = throtl_grp_quantum*3/4;
|
|
|
+ unsigned int max_nr_writes = throtl_grp_quantum - max_nr_reads;
|
|
|
+ struct bio *bio;
|
|
|
+
|
|
|
+ /* Try to dispatch 75% READS and 25% WRITES */
|
|
|
+
|
|
|
+ while ((bio = bio_list_peek(&tg->bio_lists[READ]))
|
|
|
+ && tg_may_dispatch(td, tg, bio, NULL)) {
|
|
|
+
|
|
|
+ tg_dispatch_one_bio(td, tg, bio_data_dir(bio), bl);
|
|
|
+ nr_reads++;
|
|
|
+
|
|
|
+ if (nr_reads >= max_nr_reads)
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ while ((bio = bio_list_peek(&tg->bio_lists[WRITE]))
|
|
|
+ && tg_may_dispatch(td, tg, bio, NULL)) {
|
|
|
+
|
|
|
+ tg_dispatch_one_bio(td, tg, bio_data_dir(bio), bl);
|
|
|
+ nr_writes++;
|
|
|
+
|
|
|
+ if (nr_writes >= max_nr_writes)
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ return nr_reads + nr_writes;
|
|
|
+}
|
|
|
+
|
|
|
+static int throtl_select_dispatch(struct throtl_data *td, struct bio_list *bl)
|
|
|
+{
|
|
|
+ unsigned int nr_disp = 0;
|
|
|
+ struct throtl_grp *tg;
|
|
|
+ struct throtl_rb_root *st = &td->tg_service_tree;
|
|
|
+
|
|
|
+ while (1) {
|
|
|
+ tg = throtl_rb_first(st);
|
|
|
+
|
|
|
+ if (!tg)
|
|
|
+ break;
|
|
|
+
|
|
|
+ if (time_before(jiffies, tg->disptime))
|
|
|
+ break;
|
|
|
+
|
|
|
+ throtl_dequeue_tg(td, tg);
|
|
|
+
|
|
|
+ nr_disp += throtl_dispatch_tg(td, tg, bl);
|
|
|
+
|
|
|
+ if (tg->nr_queued[0] || tg->nr_queued[1]) {
|
|
|
+ tg_update_disptime(td, tg);
|
|
|
+ throtl_enqueue_tg(td, tg);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (nr_disp >= throtl_quantum)
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ return nr_disp;
|
|
|
+}
|
|
|
+
|
|
|
+static void throtl_process_limit_change(struct throtl_data *td)
|
|
|
+{
|
|
|
+ struct request_queue *q = td->queue;
|
|
|
+ struct blkcg_gq *blkg, *n;
|
|
|
+
|
|
|
+ if (!td->limits_changed)
|
|
|
+ return;
|
|
|
+
|
|
|
+ xchg(&td->limits_changed, false);
|
|
|
+
|
|
|
+ throtl_log(td, "limits changed");
|
|
|
+
|
|
|
+ list_for_each_entry_safe(blkg, n, &q->blkg_list, q_node) {
|
|
|
+ struct throtl_grp *tg = blkg_to_tg(blkg);
|
|
|
+
|
|
|
+ if (!tg->limits_changed)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ if (!xchg(&tg->limits_changed, false))
|
|
|
+ continue;
|
|
|
+
|
|
|
+ throtl_log_tg(td, tg, "limit change rbps=%llu wbps=%llu"
|
|
|
+ " riops=%u wiops=%u", tg->bps[READ], tg->bps[WRITE],
|
|
|
+ tg->iops[READ], tg->iops[WRITE]);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Restart the slices for both READ and WRITES. It
|
|
|
+ * might happen that a group's limit are dropped
|
|
|
+ * suddenly and we don't want to account recently
|
|
|
+ * dispatched IO with new low rate
|
|
|
+ */
|
|
|
+ throtl_start_new_slice(td, tg, 0);
|
|
|
+ throtl_start_new_slice(td, tg, 1);
|
|
|
+
|
|
|
+ if (throtl_tg_on_rr(tg))
|
|
|
+ tg_update_disptime(td, tg);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* Dispatch throttled bios. Should be called without queue lock held. */
|
|
|
+static int throtl_dispatch(struct request_queue *q)
|
|
|
+{
|
|
|
+ struct throtl_data *td = q->td;
|
|
|
+ unsigned int nr_disp = 0;
|
|
|
+ struct bio_list bio_list_on_stack;
|
|
|
+ struct bio *bio;
|
|
|
+ struct blk_plug plug;
|
|
|
+
|
|
|
+ spin_lock_irq(q->queue_lock);
|
|
|
+
|
|
|
+ throtl_process_limit_change(td);
|
|
|
+
|
|
|
+ if (!total_nr_queued(td))
|
|
|
+ goto out;
|
|
|
+
|
|
|
+ bio_list_init(&bio_list_on_stack);
|
|
|
+
|
|
|
+ throtl_log(td, "dispatch nr_queued=%u read=%u write=%u",
|
|
|
+ total_nr_queued(td), td->nr_queued[READ],
|
|
|
+ td->nr_queued[WRITE]);
|
|
|
+
|
|
|
+ nr_disp = throtl_select_dispatch(td, &bio_list_on_stack);
|
|
|
+
|
|
|
+ if (nr_disp)
|
|
|
+ throtl_log(td, "bios disp=%u", nr_disp);
|
|
|
+
|
|
|
+ throtl_schedule_next_dispatch(td);
|
|
|
+out:
|
|
|
+ spin_unlock_irq(q->queue_lock);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * If we dispatched some requests, unplug the queue to make sure
|