aboutsummaryrefslogtreecommitdiffstats
path: root/lib/dynamic_queue_limits.c
diff options
context:
space:
mode:
authorTom Herbert <therbert@google.com>2011-11-28 11:32:35 -0500
committerDavid S. Miller <davem@davemloft.net>2011-11-29 12:46:19 -0500
commit75957ba36c05b979701e9ec64b37819adc12f830 (patch)
treec980b8489d6c09cc71ce80f61fb892e3c8806c96 /lib/dynamic_queue_limits.c
parent85c10f28286148ee5cdba1d22c81936ff160596e (diff)
dql: Dynamic queue limits
Implementation of dynamic queue limits (dql). This is a libary which allows a queue limit to be dynamically managed. The goal of dql is to set the queue limit, number of objects to the queue, to be minimized without allowing the queue to be starved. dql would be used with a queue which has these properties: 1) Objects are queued up to some limit which can be expressed as a count of objects. 2) Periodically a completion process executes which retires consumed objects. 3) Starvation occurs when limit has been reached, all queued data has actually been consumed but completion processing has not yet run, so queuing new data is blocked. 4) Minimizing the amount of queued data is desirable. A canonical example of such a queue would be a NIC HW transmit queue. The queue limit is dynamic, it will increase or decrease over time depending on the workload. The queue limit is recalculated each time completion processing is done. Increases occur when the queue is starved and can exponentially increase over successive intervals. Decreases occur when more data is being maintained in the queue than needed to prevent starvation. The number of extra objects, or "slack", is measured over successive intervals, and to avoid hysteresis the limit is only reduced by the miminum slack seen over a configurable time period. dql API provides routines to manage the queue: - dql_init is called to intialize the dql structure - dql_reset is called to reset dynamic values - dql_queued called when objects are being enqueued - dql_avail returns availability in the queue - dql_completed is called when objects have be consumed in the queue Configuration consists of: - max_limit, maximum limit - min_limit, minimum limit - slack_hold_time, time to measure instances of slack before reducing queue limit Signed-off-by: Tom Herbert <therbert@google.com> Acked-by: Eric Dumazet <eric.dumazet@gmail.com> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'lib/dynamic_queue_limits.c')
-rw-r--r--lib/dynamic_queue_limits.c133
1 files changed, 133 insertions, 0 deletions
diff --git a/lib/dynamic_queue_limits.c b/lib/dynamic_queue_limits.c
new file mode 100644
index 000000000000..3d1bdcdd7db4
--- /dev/null
+++ b/lib/dynamic_queue_limits.c
@@ -0,0 +1,133 @@
1/*
2 * Dynamic byte queue limits. See include/linux/dynamic_queue_limits.h
3 *
4 * Copyright (c) 2011, Tom Herbert <therbert@google.com>
5 */
6#include <linux/module.h>
7#include <linux/types.h>
8#include <linux/ctype.h>
9#include <linux/kernel.h>
10#include <linux/dynamic_queue_limits.h>
11
12#define POSDIFF(A, B) ((A) > (B) ? (A) - (B) : 0)
13
14/* Records completed count and recalculates the queue limit */
15void dql_completed(struct dql *dql, unsigned int count)
16{
17 unsigned int inprogress, prev_inprogress, limit;
18 unsigned int ovlimit, all_prev_completed, completed;
19
20 /* Can't complete more than what's in queue */
21 BUG_ON(count > dql->num_queued - dql->num_completed);
22
23 completed = dql->num_completed + count;
24 limit = dql->limit;
25 ovlimit = POSDIFF(dql->num_queued - dql->num_completed, limit);
26 inprogress = dql->num_queued - completed;
27 prev_inprogress = dql->prev_num_queued - dql->num_completed;
28 all_prev_completed = POSDIFF(completed, dql->prev_num_queued);
29
30 if ((ovlimit && !inprogress) ||
31 (dql->prev_ovlimit && all_prev_completed)) {
32 /*
33 * Queue considered starved if:
34 * - The queue was over-limit in the last interval,
35 * and there is no more data in the queue.
36 * OR
37 * - The queue was over-limit in the previous interval and
38 * when enqueuing it was possible that all queued data
39 * had been consumed. This covers the case when queue
40 * may have becomes starved between completion processing
41 * running and next time enqueue was scheduled.
42 *
43 * When queue is starved increase the limit by the amount
44 * of bytes both sent and completed in the last interval,
45 * plus any previous over-limit.
46 */
47 limit += POSDIFF(completed, dql->prev_num_queued) +
48 dql->prev_ovlimit;
49 dql->slack_start_time = jiffies;
50 dql->lowest_slack = UINT_MAX;
51 } else if (inprogress && prev_inprogress && !all_prev_completed) {
52 /*
53 * Queue was not starved, check if the limit can be decreased.
54 * A decrease is only considered if the queue has been busy in
55 * the whole interval (the check above).
56 *
57 * If there is slack, the amount of execess data queued above
58 * the the amount needed to prevent starvation, the queue limit
59 * can be decreased. To avoid hysteresis we consider the
60 * minimum amount of slack found over several iterations of the
61 * completion routine.
62 */
63 unsigned int slack, slack_last_objs;
64
65 /*
66 * Slack is the maximum of
67 * - The queue limit plus previous over-limit minus twice
68 * the number of objects completed. Note that two times
69 * number of completed bytes is a basis for an upper bound
70 * of the limit.
71 * - Portion of objects in the last queuing operation that
72 * was not part of non-zero previous over-limit. That is
73 * "round down" by non-overlimit portion of the last
74 * queueing operation.
75 */
76 slack = POSDIFF(limit + dql->prev_ovlimit,
77 2 * (completed - dql->num_completed));
78 slack_last_objs = dql->prev_ovlimit ?
79 POSDIFF(dql->prev_last_obj_cnt, dql->prev_ovlimit) : 0;
80
81 slack = max(slack, slack_last_objs);
82
83 if (slack < dql->lowest_slack)
84 dql->lowest_slack = slack;
85
86 if (time_after(jiffies,
87 dql->slack_start_time + dql->slack_hold_time)) {
88 limit = POSDIFF(limit, dql->lowest_slack);
89 dql->slack_start_time = jiffies;
90 dql->lowest_slack = UINT_MAX;
91 }
92 }
93
94 /* Enforce bounds on limit */
95 limit = clamp(limit, dql->min_limit, dql->max_limit);
96
97 if (limit != dql->limit) {
98 dql->limit = limit;
99 ovlimit = 0;
100 }
101
102 dql->adj_limit = limit + completed;
103 dql->prev_ovlimit = ovlimit;
104 dql->prev_last_obj_cnt = dql->last_obj_cnt;
105 dql->num_completed = completed;
106 dql->prev_num_queued = dql->num_queued;
107}
108EXPORT_SYMBOL(dql_completed);
109
110void dql_reset(struct dql *dql)
111{
112 /* Reset all dynamic values */
113 dql->limit = 0;
114 dql->num_queued = 0;
115 dql->num_completed = 0;
116 dql->last_obj_cnt = 0;
117 dql->prev_num_queued = 0;
118 dql->prev_last_obj_cnt = 0;
119 dql->prev_ovlimit = 0;
120 dql->lowest_slack = UINT_MAX;
121 dql->slack_start_time = jiffies;
122}
123EXPORT_SYMBOL(dql_reset);
124
125int dql_init(struct dql *dql, unsigned hold_time)
126{
127 dql->max_limit = DQL_MAX_LIMIT;
128 dql->min_limit = 0;
129 dql->slack_hold_time = hold_time;
130 dql_reset(dql);
131 return 0;
132}
133EXPORT_SYMBOL(dql_init);