1 /* net/sched/sch_hhf.c Heavy-Hitter Filter (HHF)
3 * Copyright (C) 2013 Terry Lam <vtlam@google.com>
4 * Copyright (C) 2013 Nandita Dukkipati <nanditad@google.com>
7 #include <linux/jhash.h>
8 #include <linux/jiffies.h>
9 #include <linux/module.h>
10 #include <linux/skbuff.h>
11 #include <linux/vmalloc.h>
12 #include <net/pkt_sched.h>
15 /* Heavy-Hitter Filter (HHF)
18 * Flows are classified into two buckets: non-heavy-hitter and heavy-hitter
19 * buckets. Initially, a new flow starts as non-heavy-hitter. Once classified
20 * as heavy-hitter, it is immediately switched to the heavy-hitter bucket.
21 * The buckets are dequeued by a Weighted Deficit Round Robin (WDRR) scheduler,
22 * in which the heavy-hitter bucket is served with less weight.
23 * In other words, non-heavy-hitters (e.g., short bursts of critical traffic)
24 * are isolated from heavy-hitters (e.g., persistent bulk traffic) and also have
25 * higher share of bandwidth.
27 * To capture heavy-hitters, we use the "multi-stage filter" algorithm in the
29 * [EV02] C. Estan and G. Varghese, "New Directions in Traffic Measurement and
30 * Accounting", in ACM SIGCOMM, 2002.
32 * Conceptually, a multi-stage filter comprises k independent hash functions
33 * and k counter arrays. Packets are indexed into k counter arrays by k hash
34 * functions, respectively. The counters are then increased by the packet sizes.
36 * - For a heavy-hitter flow: *all* of its k array counters must be large.
37 * - For a non-heavy-hitter flow: some of its k array counters can be large
38 * due to hash collision with other small flows; however, with high
39 * probability, not *all* k counters are large.
41 * By the design of the multi-stage filter algorithm, the false negative rate
42 * (heavy-hitters getting away uncaptured) is zero. However, the algorithm is
43 * susceptible to false positives (non-heavy-hitters mistakenly classified as
45 * Therefore, we also implement the following optimizations to reduce false
46 * positives by avoiding unnecessary increment of the counter values:
47 * - Optimization O1: once a heavy-hitter is identified, its bytes are not
48 * accounted in the array counters. This technique is called "shielding"
49 * in Section 3.3.1 of [EV02].
50 * - Optimization O2: conservative update of counters
51 * (Section 3.3.2 of [EV02]),
52 * New counter value = max {old counter value,
53 * smallest counter value + packet bytes}
55 * Finally, we refresh the counters periodically since otherwise the counter
56 * values will keep accumulating.
58 * Once a flow is classified as heavy-hitter, we also save its per-flow state
59 * in an exact-matching flow table so that its subsequent packets can be
60 * dispatched to the heavy-hitter bucket accordingly.
63 * At a high level, this qdisc works as follows:
65 * - If the flow-id of p (e.g., TCP 5-tuple) is already in the exact-matching
66 * heavy-hitter flow table, denoted table T, then send p to the heavy-hitter
68 * - Otherwise, forward p to the multi-stage filter, denoted filter F
69 * + If F decides that p belongs to a non-heavy-hitter flow, then send p
70 * to the non-heavy-hitter bucket.
71 * + Otherwise, if F decides that p belongs to a new heavy-hitter flow,
72 * then set up a new flow entry for the flow-id of p in the table T and
73 * send p to the heavy-hitter bucket.
75 * In this implementation:
76 * - T is a fixed-size hash-table with 1024 entries. Hash collision is
77 * resolved by linked-list chaining.
78 * - F has four counter arrays, each array containing 1024 32-bit counters.
79 * That means 4 * 1024 * 32 bits = 16KB of memory.
80 * - Since each array in F contains 1024 counters, 10 bits are sufficient to
81 * index into each array.
82 * Hence, instead of having four hash functions, we chop the 32-bit
83 * skb-hash into three 10-bit chunks, and the remaining 10-bit chunk is
84 * computed as XOR sum of those three chunks.
85 * - We need to clear the counter arrays periodically; however, directly
86 * memsetting 16KB of memory can lead to cache eviction and unwanted delay.
87 * So by representing each counter by a valid bit, we only need to reset
88 * 4K of 1 bit (i.e. 512 bytes) instead of 16KB of memory.
89 * - The Deficit Round Robin engine is taken from fq_codel implementation
90 * (net/sched/sch_fq_codel.c). Note that wdrr_bucket corresponds to
91 * fq_codel_flow in fq_codel implementation.
95 /* Non-configurable parameters */
96 #define HH_FLOWS_CNT 1024 /* number of entries in exact-matching table T */
97 #define HHF_ARRAYS_CNT 4 /* number of arrays in multi-stage filter F */
98 #define HHF_ARRAYS_LEN 1024 /* number of counters in each array of F */
99 #define HHF_BIT_MASK_LEN 10 /* masking 10 bits */
100 #define HHF_BIT_MASK 0x3FF /* bitmask of 10 bits */
102 #define WDRR_BUCKET_CNT 2 /* two buckets for Weighted DRR */
103 enum wdrr_bucket_idx
{
104 WDRR_BUCKET_FOR_HH
= 0, /* bucket id for heavy-hitters */
105 WDRR_BUCKET_FOR_NON_HH
= 1 /* bucket id for non-heavy-hitters */
108 #define hhf_time_before(a, b) \
109 (typecheck(u32, a) && typecheck(u32, b) && ((s32)((a) - (b)) < 0))
111 /* Heavy-hitter per-flow state */
112 struct hh_flow_state
{
113 u32 hash_id
; /* hash of flow-id (e.g. TCP 5-tuple) */
114 u32 hit_timestamp
; /* last time heavy-hitter was seen */
115 struct list_head flowchain
; /* chaining under hash collision */
118 /* Weighted Deficit Round Robin (WDRR) scheduler */
120 struct sk_buff
*head
;
121 struct sk_buff
*tail
;
122 struct list_head bucketchain
;
126 struct hhf_sched_data
{
127 struct wdrr_bucket buckets
[WDRR_BUCKET_CNT
];
128 u32 perturbation
; /* hash perturbation */
129 u32 quantum
; /* psched_mtu(qdisc_dev(sch)); */
130 u32 drop_overlimit
; /* number of times max qdisc packet
133 struct list_head
*hh_flows
; /* table T (currently active HHs) */
134 u32 hh_flows_limit
; /* max active HH allocs */
135 u32 hh_flows_overlimit
; /* num of disallowed HH allocs */
136 u32 hh_flows_total_cnt
; /* total admitted HHs */
137 u32 hh_flows_current_cnt
; /* total current HHs */
138 u32
*hhf_arrays
[HHF_ARRAYS_CNT
]; /* HH filter F */
139 u32 hhf_arrays_reset_timestamp
; /* last time hhf_arrays
142 unsigned long *hhf_valid_bits
[HHF_ARRAYS_CNT
]; /* shadow valid bits
145 /* Similar to the "new_flows" vs. "old_flows" concept in fq_codel DRR */
146 struct list_head new_buckets
; /* list of new buckets */
147 struct list_head old_buckets
; /* list of old buckets */
149 /* Configurable HHF parameters */
150 u32 hhf_reset_timeout
; /* interval to reset counter
154 u32 hhf_admit_bytes
; /* counter thresh to classify as
155 * HH (default 128KB).
156 * With these default values,
157 * 128KB / 40ms = 25 Mbps
158 * i.e., we expect to capture HHs
161 u32 hhf_evict_timeout
; /* aging threshold to evict idle
162 * HHs out of table T. This should
163 * be large enough to avoid
164 * reordering during HH eviction.
167 u32 hhf_non_hh_weight
; /* WDRR weight for non-HHs
169 * i.e., non-HH : HH = 2 : 1)
173 static u32
hhf_time_stamp(void)
178 /* Looks up a heavy-hitter flow in a chaining list of table T. */
179 static struct hh_flow_state
*seek_list(const u32 hash
,
180 struct list_head
*head
,
181 struct hhf_sched_data
*q
)
183 struct hh_flow_state
*flow
, *next
;
184 u32 now
= hhf_time_stamp();
186 if (list_empty(head
))
189 list_for_each_entry_safe(flow
, next
, head
, flowchain
) {
190 u32 prev
= flow
->hit_timestamp
+ q
->hhf_evict_timeout
;
192 if (hhf_time_before(prev
, now
)) {
193 /* Delete expired heavy-hitters, but preserve one entry
194 * to avoid kzalloc() when next time this slot is hit.
196 if (list_is_last(&flow
->flowchain
, head
))
198 list_del(&flow
->flowchain
);
200 q
->hh_flows_current_cnt
--;
201 } else if (flow
->hash_id
== hash
) {
208 /* Returns a flow state entry for a new heavy-hitter. Either reuses an expired
209 * entry or dynamically alloc a new entry.
211 static struct hh_flow_state
*alloc_new_hh(struct list_head
*head
,
212 struct hhf_sched_data
*q
)
214 struct hh_flow_state
*flow
;
215 u32 now
= hhf_time_stamp();
217 if (!list_empty(head
)) {
218 /* Find an expired heavy-hitter flow entry. */
219 list_for_each_entry(flow
, head
, flowchain
) {
220 u32 prev
= flow
->hit_timestamp
+ q
->hhf_evict_timeout
;
222 if (hhf_time_before(prev
, now
))
227 if (q
->hh_flows_current_cnt
>= q
->hh_flows_limit
) {
228 q
->hh_flows_overlimit
++;
231 /* Create new entry. */
232 flow
= kzalloc(sizeof(struct hh_flow_state
), GFP_ATOMIC
);
236 q
->hh_flows_current_cnt
++;
237 INIT_LIST_HEAD(&flow
->flowchain
);
238 list_add_tail(&flow
->flowchain
, head
);
243 /* Assigns packets to WDRR buckets. Implements a multi-stage filter to
244 * classify heavy-hitters.
246 static enum wdrr_bucket_idx
hhf_classify(struct sk_buff
*skb
, struct Qdisc
*sch
)
248 struct hhf_sched_data
*q
= qdisc_priv(sch
);
250 u32 xorsum
, filter_pos
[HHF_ARRAYS_CNT
], flow_pos
;
251 struct hh_flow_state
*flow
;
252 u32 pkt_len
, min_hhf_val
;
255 u32 now
= hhf_time_stamp();
257 /* Reset the HHF counter arrays if this is the right time. */
258 prev
= q
->hhf_arrays_reset_timestamp
+ q
->hhf_reset_timeout
;
259 if (hhf_time_before(prev
, now
)) {
260 for (i
= 0; i
< HHF_ARRAYS_CNT
; i
++)
261 bitmap_zero(q
->hhf_valid_bits
[i
], HHF_ARRAYS_LEN
);
262 q
->hhf_arrays_reset_timestamp
= now
;
265 /* Get hashed flow-id of the skb. */
266 hash
= skb_get_hash_perturb(skb
, q
->perturbation
);
268 /* Check if this packet belongs to an already established HH flow. */
269 flow_pos
= hash
& HHF_BIT_MASK
;
270 flow
= seek_list(hash
, &q
->hh_flows
[flow_pos
], q
);
271 if (flow
) { /* found its HH flow */
272 flow
->hit_timestamp
= now
;
273 return WDRR_BUCKET_FOR_HH
;
276 /* Now pass the packet through the multi-stage filter. */
279 for (i
= 0; i
< HHF_ARRAYS_CNT
- 1; i
++) {
280 /* Split the skb_hash into three 10-bit chunks. */
281 filter_pos
[i
] = tmp_hash
& HHF_BIT_MASK
;
282 xorsum
^= filter_pos
[i
];
283 tmp_hash
>>= HHF_BIT_MASK_LEN
;
285 /* The last chunk is computed as XOR sum of other chunks. */
286 filter_pos
[HHF_ARRAYS_CNT
- 1] = xorsum
^ tmp_hash
;
288 pkt_len
= qdisc_pkt_len(skb
);
290 for (i
= 0; i
< HHF_ARRAYS_CNT
; i
++) {
293 if (!test_bit(filter_pos
[i
], q
->hhf_valid_bits
[i
])) {
294 q
->hhf_arrays
[i
][filter_pos
[i
]] = 0;
295 __set_bit(filter_pos
[i
], q
->hhf_valid_bits
[i
]);
298 val
= q
->hhf_arrays
[i
][filter_pos
[i
]] + pkt_len
;
299 if (min_hhf_val
> val
)
303 /* Found a new HH iff all counter values > HH admit threshold. */
304 if (min_hhf_val
> q
->hhf_admit_bytes
) {
305 /* Just captured a new heavy-hitter. */
306 flow
= alloc_new_hh(&q
->hh_flows
[flow_pos
], q
);
307 if (!flow
) /* memory alloc problem */
308 return WDRR_BUCKET_FOR_NON_HH
;
309 flow
->hash_id
= hash
;
310 flow
->hit_timestamp
= now
;
311 q
->hh_flows_total_cnt
++;
313 /* By returning without updating counters in q->hhf_arrays,
314 * we implicitly implement "shielding" (see Optimization O1).
316 return WDRR_BUCKET_FOR_HH
;
319 /* Conservative update of HHF arrays (see Optimization O2). */
320 for (i
= 0; i
< HHF_ARRAYS_CNT
; i
++) {
321 if (q
->hhf_arrays
[i
][filter_pos
[i
]] < min_hhf_val
)
322 q
->hhf_arrays
[i
][filter_pos
[i
]] = min_hhf_val
;
324 return WDRR_BUCKET_FOR_NON_HH
;
327 /* Removes one skb from head of bucket. */
328 static struct sk_buff
*dequeue_head(struct wdrr_bucket
*bucket
)
330 struct sk_buff
*skb
= bucket
->head
;
332 bucket
->head
= skb
->next
;
337 /* Tail-adds skb to bucket. */
338 static void bucket_add(struct wdrr_bucket
*bucket
, struct sk_buff
*skb
)
340 if (bucket
->head
== NULL
)
343 bucket
->tail
->next
= skb
;
348 static unsigned int hhf_drop(struct Qdisc
*sch
)
350 struct hhf_sched_data
*q
= qdisc_priv(sch
);
351 struct wdrr_bucket
*bucket
;
353 /* Always try to drop from heavy-hitters first. */
354 bucket
= &q
->buckets
[WDRR_BUCKET_FOR_HH
];
356 bucket
= &q
->buckets
[WDRR_BUCKET_FOR_NON_HH
];
359 struct sk_buff
*skb
= dequeue_head(bucket
);
362 qdisc_qstats_drop(sch
);
363 qdisc_qstats_backlog_dec(sch
, skb
);
367 /* Return id of the bucket from which the packet was dropped. */
368 return bucket
- q
->buckets
;
371 static int hhf_enqueue(struct sk_buff
*skb
, struct Qdisc
*sch
)
373 struct hhf_sched_data
*q
= qdisc_priv(sch
);
374 enum wdrr_bucket_idx idx
;
375 struct wdrr_bucket
*bucket
;
377 idx
= hhf_classify(skb
, sch
);
379 bucket
= &q
->buckets
[idx
];
380 bucket_add(bucket
, skb
);
381 qdisc_qstats_backlog_inc(sch
, skb
);
383 if (list_empty(&bucket
->bucketchain
)) {
386 /* The logic of new_buckets vs. old_buckets is the same as
387 * new_flows vs. old_flows in the implementation of fq_codel,
388 * i.e., short bursts of non-HHs should have strict priority.
390 if (idx
== WDRR_BUCKET_FOR_HH
) {
391 /* Always move heavy-hitters to old bucket. */
393 list_add_tail(&bucket
->bucketchain
, &q
->old_buckets
);
395 weight
= q
->hhf_non_hh_weight
;
396 list_add_tail(&bucket
->bucketchain
, &q
->new_buckets
);
398 bucket
->deficit
= weight
* q
->quantum
;
400 if (++sch
->q
.qlen
<= sch
->limit
)
401 return NET_XMIT_SUCCESS
;
404 /* Return Congestion Notification only if we dropped a packet from this
407 if (hhf_drop(sch
) == idx
)
410 /* As we dropped a packet, better let upper stack know this. */
411 qdisc_tree_decrease_qlen(sch
, 1);
412 return NET_XMIT_SUCCESS
;
415 static struct sk_buff
*hhf_dequeue(struct Qdisc
*sch
)
417 struct hhf_sched_data
*q
= qdisc_priv(sch
);
418 struct sk_buff
*skb
= NULL
;
419 struct wdrr_bucket
*bucket
;
420 struct list_head
*head
;
423 head
= &q
->new_buckets
;
424 if (list_empty(head
)) {
425 head
= &q
->old_buckets
;
426 if (list_empty(head
))
429 bucket
= list_first_entry(head
, struct wdrr_bucket
, bucketchain
);
431 if (bucket
->deficit
<= 0) {
432 int weight
= (bucket
- q
->buckets
== WDRR_BUCKET_FOR_HH
) ?
433 1 : q
->hhf_non_hh_weight
;
435 bucket
->deficit
+= weight
* q
->quantum
;
436 list_move_tail(&bucket
->bucketchain
, &q
->old_buckets
);
441 skb
= dequeue_head(bucket
);
443 qdisc_qstats_backlog_dec(sch
, skb
);
447 /* Force a pass through old_buckets to prevent starvation. */
448 if ((head
== &q
->new_buckets
) && !list_empty(&q
->old_buckets
))
449 list_move_tail(&bucket
->bucketchain
, &q
->old_buckets
);
451 list_del_init(&bucket
->bucketchain
);
454 qdisc_bstats_update(sch
, skb
);
455 bucket
->deficit
-= qdisc_pkt_len(skb
);
460 static void hhf_reset(struct Qdisc
*sch
)
464 while ((skb
= hhf_dequeue(sch
)) != NULL
)
468 static void *hhf_zalloc(size_t sz
)
470 void *ptr
= kzalloc(sz
, GFP_KERNEL
| __GFP_NOWARN
);
478 static void hhf_free(void *addr
)
483 static void hhf_destroy(struct Qdisc
*sch
)
486 struct hhf_sched_data
*q
= qdisc_priv(sch
);
488 for (i
= 0; i
< HHF_ARRAYS_CNT
; i
++) {
489 hhf_free(q
->hhf_arrays
[i
]);
490 hhf_free(q
->hhf_valid_bits
[i
]);
493 for (i
= 0; i
< HH_FLOWS_CNT
; i
++) {
494 struct hh_flow_state
*flow
, *next
;
495 struct list_head
*head
= &q
->hh_flows
[i
];
497 if (list_empty(head
))
499 list_for_each_entry_safe(flow
, next
, head
, flowchain
) {
500 list_del(&flow
->flowchain
);
504 hhf_free(q
->hh_flows
);
507 static const struct nla_policy hhf_policy
[TCA_HHF_MAX
+ 1] = {
508 [TCA_HHF_BACKLOG_LIMIT
] = { .type
= NLA_U32
},
509 [TCA_HHF_QUANTUM
] = { .type
= NLA_U32
},
510 [TCA_HHF_HH_FLOWS_LIMIT
] = { .type
= NLA_U32
},
511 [TCA_HHF_RESET_TIMEOUT
] = { .type
= NLA_U32
},
512 [TCA_HHF_ADMIT_BYTES
] = { .type
= NLA_U32
},
513 [TCA_HHF_EVICT_TIMEOUT
] = { .type
= NLA_U32
},
514 [TCA_HHF_NON_HH_WEIGHT
] = { .type
= NLA_U32
},
517 static int hhf_change(struct Qdisc
*sch
, struct nlattr
*opt
)
519 struct hhf_sched_data
*q
= qdisc_priv(sch
);
520 struct nlattr
*tb
[TCA_HHF_MAX
+ 1];
524 u32 new_quantum
= q
->quantum
;
525 u32 new_hhf_non_hh_weight
= q
->hhf_non_hh_weight
;
530 err
= nla_parse_nested(tb
, TCA_HHF_MAX
, opt
, hhf_policy
);
534 if (tb
[TCA_HHF_QUANTUM
])
535 new_quantum
= nla_get_u32(tb
[TCA_HHF_QUANTUM
]);
537 if (tb
[TCA_HHF_NON_HH_WEIGHT
])
538 new_hhf_non_hh_weight
= nla_get_u32(tb
[TCA_HHF_NON_HH_WEIGHT
]);
540 non_hh_quantum
= (u64
)new_quantum
* new_hhf_non_hh_weight
;
541 if (non_hh_quantum
> INT_MAX
)
546 if (tb
[TCA_HHF_BACKLOG_LIMIT
])
547 sch
->limit
= nla_get_u32(tb
[TCA_HHF_BACKLOG_LIMIT
]);
549 q
->quantum
= new_quantum
;
550 q
->hhf_non_hh_weight
= new_hhf_non_hh_weight
;
552 if (tb
[TCA_HHF_HH_FLOWS_LIMIT
])
553 q
->hh_flows_limit
= nla_get_u32(tb
[TCA_HHF_HH_FLOWS_LIMIT
]);
555 if (tb
[TCA_HHF_RESET_TIMEOUT
]) {
556 u32 us
= nla_get_u32(tb
[TCA_HHF_RESET_TIMEOUT
]);
558 q
->hhf_reset_timeout
= usecs_to_jiffies(us
);
561 if (tb
[TCA_HHF_ADMIT_BYTES
])
562 q
->hhf_admit_bytes
= nla_get_u32(tb
[TCA_HHF_ADMIT_BYTES
]);
564 if (tb
[TCA_HHF_EVICT_TIMEOUT
]) {
565 u32 us
= nla_get_u32(tb
[TCA_HHF_EVICT_TIMEOUT
]);
567 q
->hhf_evict_timeout
= usecs_to_jiffies(us
);
571 while (sch
->q
.qlen
> sch
->limit
) {
572 struct sk_buff
*skb
= hhf_dequeue(sch
);
576 qdisc_tree_decrease_qlen(sch
, qlen
- sch
->q
.qlen
);
578 sch_tree_unlock(sch
);
582 static int hhf_init(struct Qdisc
*sch
, struct nlattr
*opt
)
584 struct hhf_sched_data
*q
= qdisc_priv(sch
);
588 q
->quantum
= psched_mtu(qdisc_dev(sch
));
589 q
->perturbation
= prandom_u32();
590 INIT_LIST_HEAD(&q
->new_buckets
);
591 INIT_LIST_HEAD(&q
->old_buckets
);
593 /* Configurable HHF parameters */
594 q
->hhf_reset_timeout
= HZ
/ 25; /* 40 ms */
595 q
->hhf_admit_bytes
= 131072; /* 128 KB */
596 q
->hhf_evict_timeout
= HZ
; /* 1 sec */
597 q
->hhf_non_hh_weight
= 2;
600 int err
= hhf_change(sch
, opt
);
607 /* Initialize heavy-hitter flow table. */
608 q
->hh_flows
= hhf_zalloc(HH_FLOWS_CNT
*
609 sizeof(struct list_head
));
612 for (i
= 0; i
< HH_FLOWS_CNT
; i
++)
613 INIT_LIST_HEAD(&q
->hh_flows
[i
]);
615 /* Cap max active HHs at twice len of hh_flows table. */
616 q
->hh_flows_limit
= 2 * HH_FLOWS_CNT
;
617 q
->hh_flows_overlimit
= 0;
618 q
->hh_flows_total_cnt
= 0;
619 q
->hh_flows_current_cnt
= 0;
621 /* Initialize heavy-hitter filter arrays. */
622 for (i
= 0; i
< HHF_ARRAYS_CNT
; i
++) {
623 q
->hhf_arrays
[i
] = hhf_zalloc(HHF_ARRAYS_LEN
*
625 if (!q
->hhf_arrays
[i
]) {
630 q
->hhf_arrays_reset_timestamp
= hhf_time_stamp();
632 /* Initialize valid bits of heavy-hitter filter arrays. */
633 for (i
= 0; i
< HHF_ARRAYS_CNT
; i
++) {
634 q
->hhf_valid_bits
[i
] = hhf_zalloc(HHF_ARRAYS_LEN
/
636 if (!q
->hhf_valid_bits
[i
]) {
642 /* Initialize Weighted DRR buckets. */
643 for (i
= 0; i
< WDRR_BUCKET_CNT
; i
++) {
644 struct wdrr_bucket
*bucket
= q
->buckets
+ i
;
646 INIT_LIST_HEAD(&bucket
->bucketchain
);
653 static int hhf_dump(struct Qdisc
*sch
, struct sk_buff
*skb
)
655 struct hhf_sched_data
*q
= qdisc_priv(sch
);
658 opts
= nla_nest_start(skb
, TCA_OPTIONS
);
660 goto nla_put_failure
;
662 if (nla_put_u32(skb
, TCA_HHF_BACKLOG_LIMIT
, sch
->limit
) ||
663 nla_put_u32(skb
, TCA_HHF_QUANTUM
, q
->quantum
) ||
664 nla_put_u32(skb
, TCA_HHF_HH_FLOWS_LIMIT
, q
->hh_flows_limit
) ||
665 nla_put_u32(skb
, TCA_HHF_RESET_TIMEOUT
,
666 jiffies_to_usecs(q
->hhf_reset_timeout
)) ||
667 nla_put_u32(skb
, TCA_HHF_ADMIT_BYTES
, q
->hhf_admit_bytes
) ||
668 nla_put_u32(skb
, TCA_HHF_EVICT_TIMEOUT
,
669 jiffies_to_usecs(q
->hhf_evict_timeout
)) ||
670 nla_put_u32(skb
, TCA_HHF_NON_HH_WEIGHT
, q
->hhf_non_hh_weight
))
671 goto nla_put_failure
;
673 return nla_nest_end(skb
, opts
);
679 static int hhf_dump_stats(struct Qdisc
*sch
, struct gnet_dump
*d
)
681 struct hhf_sched_data
*q
= qdisc_priv(sch
);
682 struct tc_hhf_xstats st
= {
683 .drop_overlimit
= q
->drop_overlimit
,
684 .hh_overlimit
= q
->hh_flows_overlimit
,
685 .hh_tot_count
= q
->hh_flows_total_cnt
,
686 .hh_cur_count
= q
->hh_flows_current_cnt
,
689 return gnet_stats_copy_app(d
, &st
, sizeof(st
));
692 static struct Qdisc_ops hhf_qdisc_ops __read_mostly
= {
694 .priv_size
= sizeof(struct hhf_sched_data
),
696 .enqueue
= hhf_enqueue
,
697 .dequeue
= hhf_dequeue
,
698 .peek
= qdisc_peek_dequeued
,
702 .destroy
= hhf_destroy
,
703 .change
= hhf_change
,
705 .dump_stats
= hhf_dump_stats
,
706 .owner
= THIS_MODULE
,
709 static int __init
hhf_module_init(void)
711 return register_qdisc(&hhf_qdisc_ops
);
714 static void __exit
hhf_module_exit(void)
716 unregister_qdisc(&hhf_qdisc_ops
);
719 module_init(hhf_module_init
)
720 module_exit(hhf_module_exit
)
721 MODULE_AUTHOR("Terry Lam");
722 MODULE_AUTHOR("Nandita Dukkipati");
723 MODULE_LICENSE("GPL");