-
Notifications
You must be signed in to change notification settings - Fork 1
/
blk-iocost.c
2494 lines (2130 loc) · 67.7 KB
/
blk-iocost.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* SPDX-License-Identifier: GPL-2.0
*
* IO cost model based controller.
*
* Copyright (C) 2019 Tejun Heo <tj@kernel.org>
* Copyright (C) 2019 Andy Newell <newella@fb.com>
* Copyright (C) 2019 Facebook
*
* One challenge of controlling IO resources is the lack of trivially
* observable cost metric. This is distinguished from CPU and memory where
* wallclock time and the number of bytes can serve as accurate enough
* approximations.
*
* Bandwidth and iops are the most commonly used metrics for IO devices but
* depending on the type and specifics of the device, different IO patterns
* easily lead to multiple orders of magnitude variations rendering them
* useless for the purpose of IO capacity distribution. While on-device
* time, with a lot of clutches, could serve as a useful approximation for
* non-queued rotational devices, this is no longer viable with modern
* devices, even the rotational ones.
*
* While there is no cost metric we can trivially observe, it isn't a
* complete mystery. For example, on a rotational device, seek cost
* dominates while a contiguous transfer contributes a smaller amount
* proportional to the size. If we can characterize at least the relative
* costs of these different types of IOs, it should be possible to
* implement a reasonable work-conserving proportional IO resource
* distribution.
*
* 1. IO Cost Model
*
* IO cost model estimates the cost of an IO given its basic parameters and
* history (e.g. the end sector of the last IO). The cost is measured in
* device time. If a given IO is estimated to cost 10ms, the device should
* be able to process ~100 of those IOs in a second.
*
* Currently, there's only one builtin cost model - linear. Each IO is
* classified as sequential or random and given a base cost accordingly.
* On top of that, a size cost proportional to the length of the IO is
* added. While simple, this model captures the operational
* characteristics of a wide varienty of devices well enough. Default
* paramters for several different classes of devices are provided and the
* parameters can be configured from userspace via
* /sys/fs/cgroup/io.cost.model.
*
* If needed, tools/cgroup/iocost_coef_gen.py can be used to generate
* device-specific coefficients.
*
* 2. Control Strategy
*
* The device virtual time (vtime) is used as the primary control metric.
* The control strategy is composed of the following three parts.
*
* 2-1. Vtime Distribution
*
* When a cgroup becomes active in terms of IOs, its hierarchical share is
* calculated. Please consider the following hierarchy where the numbers
* inside parentheses denote the configured weights.
*
* root
* / \
* A (w:100) B (w:300)
* / \
* A0 (w:100) A1 (w:100)
*
* If B is idle and only A0 and A1 are actively issuing IOs, as the two are
* of equal weight, each gets 50% share. If then B starts issuing IOs, B
* gets 300/(100+300) or 75% share, and A0 and A1 equally splits the rest,
* 12.5% each. The distribution mechanism only cares about these flattened
* shares. They're called hweights (hierarchical weights) and always add
* upto 1 (HWEIGHT_WHOLE).
*
* A given cgroup's vtime runs slower in inverse proportion to its hweight.
* For example, with 12.5% weight, A0's time runs 8 times slower (100/12.5)
* against the device vtime - an IO which takes 10ms on the underlying
* device is considered to take 80ms on A0.
*
* This constitutes the basis of IO capacity distribution. Each cgroup's
* vtime is running at a rate determined by its hweight. A cgroup tracks
* the vtime consumed by past IOs and can issue a new IO iff doing so
* wouldn't outrun the current device vtime. Otherwise, the IO is
* suspended until the vtime has progressed enough to cover it.
*
* 2-2. Vrate Adjustment
*
* It's unrealistic to expect the cost model to be perfect. There are too
* many devices and even on the same device the overall performance
* fluctuates depending on numerous factors such as IO mixture and device
* internal garbage collection. The controller needs to adapt dynamically.
*
* This is achieved by adjusting the overall IO rate according to how busy
* the device is. If the device becomes overloaded, we're sending down too
* many IOs and should generally slow down. If there are waiting issuers
* but the device isn't saturated, we're issuing too few and should
* generally speed up.
*
* To slow down, we lower the vrate - the rate at which the device vtime
* passes compared to the wall clock. For example, if the vtime is running
* at the vrate of 75%, all cgroups added up would only be able to issue
* 750ms worth of IOs per second, and vice-versa for speeding up.
*
* Device business is determined using two criteria - rq wait and
* completion latencies.
*
* When a device gets saturated, the on-device and then the request queues
* fill up and a bio which is ready to be issued has to wait for a request
* to become available. When this delay becomes noticeable, it's a clear
* indication that the device is saturated and we lower the vrate. This
* saturation signal is fairly conservative as it only triggers when both
* hardware and software queues are filled up, and is used as the default
* busy signal.
*
* As devices can have deep queues and be unfair in how the queued commands
* are executed, soley depending on rq wait may not result in satisfactory
* control quality. For a better control quality, completion latency QoS
* parameters can be configured so that the device is considered saturated
* if N'th percentile completion latency rises above the set point.
*
* The completion latency requirements are a function of both the
* underlying device characteristics and the desired IO latency quality of
* service. There is an inherent trade-off - the tighter the latency QoS,
* the higher the bandwidth lossage. Latency QoS is disabled by default
* and can be set through /sys/fs/cgroup/io.cost.qos.
*
* 2-3. Work Conservation
*
* Imagine two cgroups A and B with equal weights. A is issuing a small IO
* periodically while B is sending out enough parallel IOs to saturate the
* device on its own. Let's say A's usage amounts to 100ms worth of IO
* cost per second, i.e., 10% of the device capacity. The naive
* distribution of half and half would lead to 60% utilization of the
* device, a significant reduction in the total amount of work done
* compared to free-for-all competition. This is too high a cost to pay
* for IO control.
*
* To conserve the total amount of work done, we keep track of how much
* each active cgroup is actually using and yield part of its weight if
* there are other cgroups which can make use of it. In the above case,
* A's weight will be lowered so that it hovers above the actual usage and
* B would be able to use the rest.
*
* As we don't want to penalize a cgroup for donating its weight, the
* surplus weight adjustment factors in a margin and has an immediate
* snapback mechanism in case the cgroup needs more IO vtime for itself.
*
* Note that adjusting down surplus weights has the same effects as
* accelerating vtime for other cgroups and work conservation can also be
* implemented by adjusting vrate dynamically. However, squaring who can
* donate and should take back how much requires hweight propagations
* anyway making it easier to implement and understand as a separate
* mechanism.
*
* 3. Monitoring
*
* Instead of debugfs or other clumsy monitoring mechanisms, this
* controller uses a drgn based monitoring script -
* tools/cgroup/iocost_monitor.py. For details on drgn, please see
* https://github.com/osandov/drgn. The ouput looks like the following.
*
* sdb RUN per=300ms cur_per=234.218:v203.695 busy= +1 vrate= 62.12%
* active weight hweight% inflt% dbt delay usages%
* test/a * 50/ 50 33.33/ 33.33 27.65 2 0*041 033:033:033
* test/b * 100/ 100 66.67/ 66.67 17.56 0 0*000 066:079:077
*
* - per : Timer period
* - cur_per : Internal wall and device vtime clock
* - vrate : Device virtual time rate against wall clock
* - weight : Surplus-adjusted and configured weights
* - hweight : Surplus-adjusted and configured hierarchical weights
* - inflt : The percentage of in-flight IO cost at the end of last period
* - del_ms : Deferred issuer delay induction level and duration
* - usages : Usage history
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/timer.h>
#include <linux/time64.h>
#include <linux/parser.h>
#include <linux/sched/signal.h>
#include <linux/blk-cgroup.h>
#include "blk-rq-qos.h"
#include "blk-stat.h"
#include "blk-wbt.h"
#ifdef CONFIG_TRACEPOINTS
/* copied from TRACE_CGROUP_PATH, see cgroup-internal.h */
#define TRACE_IOCG_PATH_LEN 1024
static DEFINE_SPINLOCK(trace_iocg_path_lock);
static char trace_iocg_path[TRACE_IOCG_PATH_LEN];
#define TRACE_IOCG_PATH(type, iocg, ...) \
do { \
unsigned long flags; \
if (trace_iocost_##type##_enabled()) { \
spin_lock_irqsave(&trace_iocg_path_lock, flags); \
cgroup_path(iocg_to_blkg(iocg)->blkcg->css.cgroup, \
trace_iocg_path, TRACE_IOCG_PATH_LEN); \
trace_iocost_##type(iocg, trace_iocg_path, \
##__VA_ARGS__); \
spin_unlock_irqrestore(&trace_iocg_path_lock, flags); \
} \
} while (0)
#else /* CONFIG_TRACE_POINTS */
#define TRACE_IOCG_PATH(type, iocg, ...) do { } while (0)
#endif /* CONFIG_TRACE_POINTS */
enum {
MILLION = 1000000,
/* timer period is calculated from latency requirements, bound it */
MIN_PERIOD = USEC_PER_MSEC,
MAX_PERIOD = USEC_PER_SEC,
/*
* A cgroup's vtime can run 50% behind the device vtime, which
* serves as its IO credit buffer. Surplus weight adjustment is
* immediately canceled if the vtime margin runs below 10%.
*/
MARGIN_PCT = 50,
INUSE_MARGIN_PCT = 10,
/* Have some play in waitq timer operations */
WAITQ_TIMER_MARGIN_PCT = 5,
/*
* vtime can wrap well within a reasonable uptime when vrate is
* consistently raised. Don't trust recorded cgroup vtime if the
* period counter indicates that it's older than 5mins.
*/
VTIME_VALID_DUR = 300 * USEC_PER_SEC,
/*
* Remember the past three non-zero usages and use the max for
* surplus calculation. Three slots guarantee that we remember one
* full period usage from the last active stretch even after
* partial deactivation and re-activation periods. Don't start
* giving away weight before collecting two data points to prevent
* hweight adjustments based on one partial activation period.
*/
NR_USAGE_SLOTS = 3,
MIN_VALID_USAGES = 2,
/* 1/64k is granular enough and can easily be handled w/ u32 */
HWEIGHT_WHOLE = 1 << 16,
/*
* As vtime is used to calculate the cost of each IO, it needs to
* be fairly high precision. For example, it should be able to
* represent the cost of a single page worth of discard with
* suffificient accuracy. At the same time, it should be able to
* represent reasonably long enough durations to be useful and
* convenient during operation.
*
* 1s worth of vtime is 2^37. This gives us both sub-nanosecond
* granularity and days of wrap-around time even at extreme vrates.
*/
VTIME_PER_SEC_SHIFT = 37,
VTIME_PER_SEC = 1LLU << VTIME_PER_SEC_SHIFT,
VTIME_PER_USEC = VTIME_PER_SEC / USEC_PER_SEC,
/* bound vrate adjustments within two orders of magnitude */
VRATE_MIN_PPM = 10000, /* 1% */
VRATE_MAX_PPM = 100000000, /* 10000% */
VRATE_MIN = VTIME_PER_USEC * VRATE_MIN_PPM / MILLION,
VRATE_CLAMP_ADJ_PCT = 4,
/* if IOs end up waiting for requests, issue less */
RQ_WAIT_BUSY_PCT = 5,
/* unbusy hysterisis */
UNBUSY_THR_PCT = 75,
/* don't let cmds which take a very long time pin lagging for too long */
MAX_LAGGING_PERIODS = 10,
/*
* If usage% * 1.25 + 2% is lower than hweight% by more than 3%,
* donate the surplus.
*/
SURPLUS_SCALE_PCT = 125, /* * 125% */
SURPLUS_SCALE_ABS = HWEIGHT_WHOLE / 50, /* + 2% */
SURPLUS_MIN_ADJ_DELTA = HWEIGHT_WHOLE / 33, /* 3% */
/* switch iff the conditions are met for longer than this */
AUTOP_CYCLE_NSEC = 10LLU * NSEC_PER_SEC,
/*
* Count IO size in 4k pages. The 12bit shift helps keeping
* size-proportional components of cost calculation in closer
* numbers of digits to per-IO cost components.
*/
IOC_PAGE_SHIFT = 12,
IOC_PAGE_SIZE = 1 << IOC_PAGE_SHIFT,
IOC_SECT_TO_PAGE_SHIFT = IOC_PAGE_SHIFT - SECTOR_SHIFT,
/* if apart further than 16M, consider randio for linear model */
LCOEF_RANDIO_PAGES = 4096,
};
enum ioc_running {
IOC_IDLE,
IOC_RUNNING,
IOC_STOP,
};
/* io.cost.qos controls including per-dev enable of the whole controller */
enum {
QOS_ENABLE,
QOS_CTRL,
NR_QOS_CTRL_PARAMS,
};
/* io.cost.qos params */
enum {
QOS_RPPM,
QOS_RLAT,
QOS_WPPM,
QOS_WLAT,
QOS_MIN,
QOS_MAX,
NR_QOS_PARAMS,
};
/* io.cost.model controls */
enum {
COST_CTRL,
COST_MODEL,
NR_COST_CTRL_PARAMS,
};
/* builtin linear cost model coefficients */
enum {
I_LCOEF_RBPS,
I_LCOEF_RSEQIOPS,
I_LCOEF_RRANDIOPS,
I_LCOEF_WBPS,
I_LCOEF_WSEQIOPS,
I_LCOEF_WRANDIOPS,
NR_I_LCOEFS,
};
enum {
LCOEF_RPAGE,
LCOEF_RSEQIO,
LCOEF_RRANDIO,
LCOEF_WPAGE,
LCOEF_WSEQIO,
LCOEF_WRANDIO,
NR_LCOEFS,
};
enum {
AUTOP_INVALID,
AUTOP_HDD,
AUTOP_SSD_QD1,
AUTOP_SSD_DFL,
AUTOP_SSD_FAST,
};
struct ioc_gq;
struct ioc_params {
u32 qos[NR_QOS_PARAMS];
u64 i_lcoefs[NR_I_LCOEFS];
u64 lcoefs[NR_LCOEFS];
u32 too_fast_vrate_pct;
u32 too_slow_vrate_pct;
};
struct ioc_missed {
u32 nr_met;
u32 nr_missed;
u32 last_met;
u32 last_missed;
};
struct ioc_pcpu_stat {
struct ioc_missed missed[2];
u64 rq_wait_ns;
u64 last_rq_wait_ns;
};
/* per device */
struct ioc {
struct rq_qos rqos;
bool enabled;
struct ioc_params params;
u32 period_us;
u32 margin_us;
u64 vrate_min;
u64 vrate_max;
spinlock_t lock;
struct timer_list timer;
struct list_head active_iocgs; /* active cgroups */
struct ioc_pcpu_stat __percpu *pcpu_stat;
enum ioc_running running;
atomic64_t vtime_rate;
seqcount_t period_seqcount;
u32 period_at; /* wallclock starttime */
u64 period_at_vtime; /* vtime starttime */
atomic64_t cur_period; /* inc'd each period */
int busy_level; /* saturation history */
u64 inuse_margin_vtime;
bool weights_updated;
atomic_t hweight_gen; /* for lazy hweights */
u64 autop_too_fast_at;
u64 autop_too_slow_at;
int autop_idx;
bool user_qos_params:1;
bool user_cost_model:1;
};
/* per device-cgroup pair */
struct ioc_gq {
struct blkg_policy_data pd;
struct ioc *ioc;
/*
* A iocg can get its weight from two sources - an explicit
* per-device-cgroup configuration or the default weight of the
* cgroup. `cfg_weight` is the explicit per-device-cgroup
* configuration. `weight` is the effective considering both
* sources.
*
* When an idle cgroup becomes active its `active` goes from 0 to
* `weight`. `inuse` is the surplus adjusted active weight.
* `active` and `inuse` are used to calculate `hweight_active` and
* `hweight_inuse`.
*
* `last_inuse` remembers `inuse` while an iocg is idle to persist
* surplus adjustments.
*/
u32 cfg_weight;
u32 weight;
u32 active;
u32 inuse;
u32 last_inuse;
sector_t cursor; /* to detect randio */
/*
* `vtime` is this iocg's vtime cursor which progresses as IOs are
* issued. If lagging behind device vtime, the delta represents
* the currently available IO budget. If runnning ahead, the
* overage.
*
* `vtime_done` is the same but progressed on completion rather
* than issue. The delta behind `vtime` represents the cost of
* currently in-flight IOs.
*
* `last_vtime` is used to remember `vtime` at the end of the last
* period to calculate utilization.
*/
atomic64_t vtime;
atomic64_t done_vtime;
u64 abs_vdebt;
u64 last_vtime;
/*
* The period this iocg was last active in. Used for deactivation
* and invalidating `vtime`.
*/
atomic64_t active_period;
struct list_head active_list;
/* see __propagate_active_weight() and current_hweight() for details */
u64 child_active_sum;
u64 child_inuse_sum;
int hweight_gen;
u32 hweight_active;
u32 hweight_inuse;
bool has_surplus;
struct wait_queue_head waitq;
struct hrtimer waitq_timer;
struct hrtimer delay_timer;
/* usage is recorded as fractions of HWEIGHT_WHOLE */
int usage_idx;
u32 usages[NR_USAGE_SLOTS];
/* this iocg's depth in the hierarchy and ancestors including self */
int level;
struct ioc_gq *ancestors[];
};
/* per cgroup */
struct ioc_cgrp {
struct blkcg_policy_data cpd;
unsigned int dfl_weight;
};
struct ioc_now {
u64 now_ns;
u32 now;
u64 vnow;
u64 vrate;
};
struct iocg_wait {
struct wait_queue_entry wait;
struct bio *bio;
u64 abs_cost;
bool committed;
};
struct iocg_wake_ctx {
struct ioc_gq *iocg;
u32 hw_inuse;
s64 vbudget;
};
static const struct ioc_params autop[] = {
[AUTOP_HDD] = {
.qos = {
[QOS_RLAT] = 250000, /* 250ms */
[QOS_WLAT] = 250000,
[QOS_MIN] = VRATE_MIN_PPM,
[QOS_MAX] = VRATE_MAX_PPM,
},
.i_lcoefs = {
[I_LCOEF_RBPS] = 174019176,
[I_LCOEF_RSEQIOPS] = 41708,
[I_LCOEF_RRANDIOPS] = 370,
[I_LCOEF_WBPS] = 178075866,
[I_LCOEF_WSEQIOPS] = 42705,
[I_LCOEF_WRANDIOPS] = 378,
},
},
[AUTOP_SSD_QD1] = {
.qos = {
[QOS_RLAT] = 25000, /* 25ms */
[QOS_WLAT] = 25000,
[QOS_MIN] = VRATE_MIN_PPM,
[QOS_MAX] = VRATE_MAX_PPM,
},
.i_lcoefs = {
[I_LCOEF_RBPS] = 245855193,
[I_LCOEF_RSEQIOPS] = 61575,
[I_LCOEF_RRANDIOPS] = 6946,
[I_LCOEF_WBPS] = 141365009,
[I_LCOEF_WSEQIOPS] = 33716,
[I_LCOEF_WRANDIOPS] = 26796,
},
},
[AUTOP_SSD_DFL] = {
.qos = {
[QOS_RLAT] = 25000, /* 25ms */
[QOS_WLAT] = 25000,
[QOS_MIN] = VRATE_MIN_PPM,
[QOS_MAX] = VRATE_MAX_PPM,
},
.i_lcoefs = {
[I_LCOEF_RBPS] = 488636629,
[I_LCOEF_RSEQIOPS] = 8932,
[I_LCOEF_RRANDIOPS] = 8518,
[I_LCOEF_WBPS] = 427891549,
[I_LCOEF_WSEQIOPS] = 28755,
[I_LCOEF_WRANDIOPS] = 21940,
},
.too_fast_vrate_pct = 500,
},
[AUTOP_SSD_FAST] = {
.qos = {
[QOS_RLAT] = 5000, /* 5ms */
[QOS_WLAT] = 5000,
[QOS_MIN] = VRATE_MIN_PPM,
[QOS_MAX] = VRATE_MAX_PPM,
},
.i_lcoefs = {
[I_LCOEF_RBPS] = 3102524156LLU,
[I_LCOEF_RSEQIOPS] = 724816,
[I_LCOEF_RRANDIOPS] = 778122,
[I_LCOEF_WBPS] = 1742780862LLU,
[I_LCOEF_WSEQIOPS] = 425702,
[I_LCOEF_WRANDIOPS] = 443193,
},
.too_slow_vrate_pct = 10,
},
};
/*
* vrate adjust percentages indexed by ioc->busy_level. We adjust up on
* vtime credit shortage and down on device saturation.
*/
static u32 vrate_adj_pct[] =
{ 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
4, 4, 4, 4, 4, 4, 4, 4, 8, 8, 8, 8, 8, 8, 8, 8, 16 };
static struct blkcg_policy blkcg_policy_iocost;
/* accessors and helpers */
static struct ioc *rqos_to_ioc(struct rq_qos *rqos)
{
return container_of(rqos, struct ioc, rqos);
}
static struct ioc *q_to_ioc(struct request_queue *q)
{
return rqos_to_ioc(rq_qos_id(q, RQ_QOS_COST));
}
static const char *q_name(struct request_queue *q)
{
if (test_bit(QUEUE_FLAG_REGISTERED, &q->queue_flags))
return kobject_name(q->kobj.parent);
else
return "<unknown>";
}
static const char __maybe_unused *ioc_name(struct ioc *ioc)
{
return q_name(ioc->rqos.q);
}
static struct ioc_gq *pd_to_iocg(struct blkg_policy_data *pd)
{
return pd ? container_of(pd, struct ioc_gq, pd) : NULL;
}
static struct ioc_gq *blkg_to_iocg(struct blkcg_gq *blkg)
{
return pd_to_iocg(blkg_to_pd(blkg, &blkcg_policy_iocost));
}
static struct blkcg_gq *iocg_to_blkg(struct ioc_gq *iocg)
{
return pd_to_blkg(&iocg->pd);
}
static struct ioc_cgrp *blkcg_to_iocc(struct blkcg *blkcg)
{
return container_of(blkcg_to_cpd(blkcg, &blkcg_policy_iocost),
struct ioc_cgrp, cpd);
}
/*
* Scale @abs_cost to the inverse of @hw_inuse. The lower the hierarchical
* weight, the more expensive each IO. Must round up.
*/
static u64 abs_cost_to_cost(u64 abs_cost, u32 hw_inuse)
{
return DIV64_U64_ROUND_UP(abs_cost * HWEIGHT_WHOLE, hw_inuse);
}
/*
* The inverse of abs_cost_to_cost(). Must round up.
*/
static u64 cost_to_abs_cost(u64 cost, u32 hw_inuse)
{
return DIV64_U64_ROUND_UP(cost * hw_inuse, HWEIGHT_WHOLE);
}
static void iocg_commit_bio(struct ioc_gq *iocg, struct bio *bio, u64 cost)
{
bio->bi_iocost_cost = cost;
atomic64_add(cost, &iocg->vtime);
}
#define CREATE_TRACE_POINTS
#include <trace/events/iocost.h>
/* latency Qos params changed, update period_us and all the dependent params */
static void ioc_refresh_period_us(struct ioc *ioc)
{
u32 ppm, lat, multi, period_us;
lockdep_assert_held(&ioc->lock);
/* pick the higher latency target */
if (ioc->params.qos[QOS_RLAT] >= ioc->params.qos[QOS_WLAT]) {
ppm = ioc->params.qos[QOS_RPPM];
lat = ioc->params.qos[QOS_RLAT];
} else {
ppm = ioc->params.qos[QOS_WPPM];
lat = ioc->params.qos[QOS_WLAT];
}
/*
* We want the period to be long enough to contain a healthy number
* of IOs while short enough for granular control. Define it as a
* multiple of the latency target. Ideally, the multiplier should
* be scaled according to the percentile so that it would nominally
* contain a certain number of requests. Let's be simpler and
* scale it linearly so that it's 2x >= pct(90) and 10x at pct(50).
*/
if (ppm)
multi = max_t(u32, (MILLION - ppm) / 50000, 2);
else
multi = 2;
period_us = multi * lat;
period_us = clamp_t(u32, period_us, MIN_PERIOD, MAX_PERIOD);
/* calculate dependent params */
ioc->period_us = period_us;
ioc->margin_us = period_us * MARGIN_PCT / 100;
ioc->inuse_margin_vtime = DIV64_U64_ROUND_UP(
period_us * VTIME_PER_USEC * INUSE_MARGIN_PCT, 100);
}
static int ioc_autop_idx(struct ioc *ioc)
{
int idx = ioc->autop_idx;
const struct ioc_params *p = &autop[idx];
u32 vrate_pct;
u64 now_ns;
/* rotational? */
if (!blk_queue_nonrot(ioc->rqos.q))
return AUTOP_HDD;
/* handle SATA SSDs w/ broken NCQ */
if (blk_queue_depth(ioc->rqos.q) == 1)
return AUTOP_SSD_QD1;
/* use one of the normal ssd sets */
if (idx < AUTOP_SSD_DFL)
return AUTOP_SSD_DFL;
/* if user is overriding anything, maintain what was there */
if (ioc->user_qos_params || ioc->user_cost_model)
return idx;
/* step up/down based on the vrate */
vrate_pct = div64_u64(atomic64_read(&ioc->vtime_rate) * 100,
VTIME_PER_USEC);
now_ns = ktime_get_ns();
if (p->too_fast_vrate_pct && p->too_fast_vrate_pct <= vrate_pct) {
if (!ioc->autop_too_fast_at)
ioc->autop_too_fast_at = now_ns;
if (now_ns - ioc->autop_too_fast_at >= AUTOP_CYCLE_NSEC)
return idx + 1;
} else {
ioc->autop_too_fast_at = 0;
}
if (p->too_slow_vrate_pct && p->too_slow_vrate_pct >= vrate_pct) {
if (!ioc->autop_too_slow_at)
ioc->autop_too_slow_at = now_ns;
if (now_ns - ioc->autop_too_slow_at >= AUTOP_CYCLE_NSEC)
return idx - 1;
} else {
ioc->autop_too_slow_at = 0;
}
return idx;
}
/*
* Take the followings as input
*
* @bps maximum sequential throughput
* @seqiops maximum sequential 4k iops
* @randiops maximum random 4k iops
*
* and calculate the linear model cost coefficients.
*
* *@page per-page cost 1s / (@bps / 4096)
* *@seqio base cost of a seq IO max((1s / @seqiops) - *@page, 0)
* @randiops base cost of a rand IO max((1s / @randiops) - *@page, 0)
*/
static void calc_lcoefs(u64 bps, u64 seqiops, u64 randiops,
u64 *page, u64 *seqio, u64 *randio)
{
u64 v;
*page = *seqio = *randio = 0;
if (bps)
*page = DIV64_U64_ROUND_UP(VTIME_PER_SEC,
DIV_ROUND_UP_ULL(bps, IOC_PAGE_SIZE));
if (seqiops) {
v = DIV64_U64_ROUND_UP(VTIME_PER_SEC, seqiops);
if (v > *page)
*seqio = v - *page;
}
if (randiops) {
v = DIV64_U64_ROUND_UP(VTIME_PER_SEC, randiops);
if (v > *page)
*randio = v - *page;
}
}
static void ioc_refresh_lcoefs(struct ioc *ioc)
{
u64 *u = ioc->params.i_lcoefs;
u64 *c = ioc->params.lcoefs;
calc_lcoefs(u[I_LCOEF_RBPS], u[I_LCOEF_RSEQIOPS], u[I_LCOEF_RRANDIOPS],
&c[LCOEF_RPAGE], &c[LCOEF_RSEQIO], &c[LCOEF_RRANDIO]);
calc_lcoefs(u[I_LCOEF_WBPS], u[I_LCOEF_WSEQIOPS], u[I_LCOEF_WRANDIOPS],
&c[LCOEF_WPAGE], &c[LCOEF_WSEQIO], &c[LCOEF_WRANDIO]);
}
static bool ioc_refresh_params(struct ioc *ioc, bool force)
{
const struct ioc_params *p;
int idx;
lockdep_assert_held(&ioc->lock);
idx = ioc_autop_idx(ioc);
p = &autop[idx];
if (idx == ioc->autop_idx && !force)
return false;
if (idx != ioc->autop_idx)
atomic64_set(&ioc->vtime_rate, VTIME_PER_USEC);
ioc->autop_idx = idx;
ioc->autop_too_fast_at = 0;
ioc->autop_too_slow_at = 0;
if (!ioc->user_qos_params)
memcpy(ioc->params.qos, p->qos, sizeof(p->qos));
if (!ioc->user_cost_model)
memcpy(ioc->params.i_lcoefs, p->i_lcoefs, sizeof(p->i_lcoefs));
ioc_refresh_period_us(ioc);
ioc_refresh_lcoefs(ioc);
ioc->vrate_min = DIV64_U64_ROUND_UP((u64)ioc->params.qos[QOS_MIN] *
VTIME_PER_USEC, MILLION);
ioc->vrate_max = div64_u64((u64)ioc->params.qos[QOS_MAX] *
VTIME_PER_USEC, MILLION);
return true;
}
/* take a snapshot of the current [v]time and vrate */
static void ioc_now(struct ioc *ioc, struct ioc_now *now)
{
unsigned seq;
now->now_ns = ktime_get();
now->now = ktime_to_us(now->now_ns);
now->vrate = atomic64_read(&ioc->vtime_rate);
/*
* The current vtime is
*
* vtime at period start + (wallclock time since the start) * vrate
*
* As a consistent snapshot of `period_at_vtime` and `period_at` is
* needed, they're seqcount protected.
*/
do {
seq = read_seqcount_begin(&ioc->period_seqcount);
now->vnow = ioc->period_at_vtime +
(now->now - ioc->period_at) * now->vrate;
} while (read_seqcount_retry(&ioc->period_seqcount, seq));
}
static void ioc_start_period(struct ioc *ioc, struct ioc_now *now)
{
lockdep_assert_held(&ioc->lock);
WARN_ON_ONCE(ioc->running != IOC_RUNNING);
write_seqcount_begin(&ioc->period_seqcount);
ioc->period_at = now->now;
ioc->period_at_vtime = now->vnow;
write_seqcount_end(&ioc->period_seqcount);
ioc->timer.expires = jiffies + usecs_to_jiffies(ioc->period_us);
add_timer(&ioc->timer);
}
/*
* Update @iocg's `active` and `inuse` to @active and @inuse, update level
* weight sums and propagate upwards accordingly.
*/
static void __propagate_active_weight(struct ioc_gq *iocg, u32 active, u32 inuse)
{
struct ioc *ioc = iocg->ioc;
int lvl;
lockdep_assert_held(&ioc->lock);
inuse = min(active, inuse);
for (lvl = iocg->level - 1; lvl >= 0; lvl--) {
struct ioc_gq *parent = iocg->ancestors[lvl];
struct ioc_gq *child = iocg->ancestors[lvl + 1];
u32 parent_active = 0, parent_inuse = 0;
/* update the level sums */
parent->child_active_sum += (s32)(active - child->active);
parent->child_inuse_sum += (s32)(inuse - child->inuse);
/* apply the udpates */
child->active = active;
child->inuse = inuse;
/*
* The delta between inuse and active sums indicates that
* that much of weight is being given away. Parent's inuse
* and active should reflect the ratio.
*/
if (parent->child_active_sum) {
parent_active = parent->weight;
parent_inuse = DIV64_U64_ROUND_UP(
parent_active * parent->child_inuse_sum,
parent->child_active_sum);
}
/* do we need to keep walking up? */
if (parent_active == parent->active &&
parent_inuse == parent->inuse)
break;
active = parent_active;
inuse = parent_inuse;
}
ioc->weights_updated = true;
}
static void commit_active_weights(struct ioc *ioc)
{
lockdep_assert_held(&ioc->lock);
if (ioc->weights_updated) {
/* paired with rmb in current_hweight(), see there */
smp_wmb();
atomic_inc(&ioc->hweight_gen);
ioc->weights_updated = false;
}
}
static void propagate_active_weight(struct ioc_gq *iocg, u32 active, u32 inuse)
{
__propagate_active_weight(iocg, active, inuse);
commit_active_weights(iocg->ioc);
}
static void current_hweight(struct ioc_gq *iocg, u32 *hw_activep, u32 *hw_inusep)
{
struct ioc *ioc = iocg->ioc;
int lvl;
u32 hwa, hwi;
int ioc_gen;
/* hot path - if uptodate, use cached */
ioc_gen = atomic_read(&ioc->hweight_gen);
if (ioc_gen == iocg->hweight_gen)
goto out;
/*
* Paired with wmb in commit_active_weights(). If we saw the
* updated hweight_gen, all the weight updates from
* __propagate_active_weight() are visible too.
*
* We can race with weight updates during calculation and get it
* wrong. However, hweight_gen would have changed and a future
* reader will recalculate and we're guaranteed to discard the
* wrong result soon.
*/
smp_rmb();
hwa = hwi = HWEIGHT_WHOLE;
for (lvl = 0; lvl <= iocg->level - 1; lvl++) {
struct ioc_gq *parent = iocg->ancestors[lvl];
struct ioc_gq *child = iocg->ancestors[lvl + 1];
u32 active_sum = READ_ONCE(parent->child_active_sum);
u32 inuse_sum = READ_ONCE(parent->child_inuse_sum);
u32 active = READ_ONCE(child->active);
u32 inuse = READ_ONCE(child->inuse);
/* we can race with deactivations and either may read as zero */
if (!active_sum || !inuse_sum)
continue;
active_sum = max(active, active_sum);
hwa = hwa * active / active_sum; /* max 16bits * 10000 */
inuse_sum = max(inuse, inuse_sum);
hwi = hwi * inuse / inuse_sum; /* max 16bits * 10000 */
}
iocg->hweight_active = max_t(u32, hwa, 1);
iocg->hweight_inuse = max_t(u32, hwi, 1);
iocg->hweight_gen = ioc_gen;