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
, struct sk_buff
**to_free
)
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_backlog_dec(sch
, skb
);
363 qdisc_drop(skb
, sch
, to_free
);
366 /* Return id of the bucket from which the packet was dropped. */
367 return bucket
- q
->buckets
;
370 static int hhf_enqueue(struct sk_buff
*skb
, struct Qdisc
*sch
,
371 struct sk_buff
**to_free
)
373 struct hhf_sched_data
*q
= qdisc_priv(sch
);
374 enum wdrr_bucket_idx idx
;
375 struct wdrr_bucket
*bucket
;
376 unsigned int prev_backlog
;
378 idx
= hhf_classify(skb
, sch
);
380 bucket
= &q
->buckets
[idx
];
381 bucket_add(bucket
, skb
);
382 qdisc_qstats_backlog_inc(sch
, skb
);
384 if (list_empty(&bucket
->bucketchain
)) {
387 /* The logic of new_buckets vs. old_buckets is the same as
388 * new_flows vs. old_flows in the implementation of fq_codel,
389 * i.e., short bursts of non-HHs should have strict priority.
391 if (idx
== WDRR_BUCKET_FOR_HH
) {
392 /* Always move heavy-hitters to old bucket. */
394 list_add_tail(&bucket
->bucketchain
, &q
->old_buckets
);
396 weight
= q
->hhf_non_hh_weight
;
397 list_add_tail(&bucket
->bucketchain
, &q
->new_buckets
);
399 bucket
->deficit
= weight
* q
->quantum
;
401 if (++sch
->q
.qlen
<= sch
->limit
)
402 return NET_XMIT_SUCCESS
;
404 prev_backlog
= sch
->qstats
.backlog
;
406 /* Return Congestion Notification only if we dropped a packet from this
409 if (hhf_drop(sch
, to_free
) == idx
)
412 /* As we dropped a packet, better let upper stack know this. */
413 qdisc_tree_reduce_backlog(sch
, 1, prev_backlog
- sch
->qstats
.backlog
);
414 return NET_XMIT_SUCCESS
;
417 static struct sk_buff
*hhf_dequeue(struct Qdisc
*sch
)
419 struct hhf_sched_data
*q
= qdisc_priv(sch
);
420 struct sk_buff
*skb
= NULL
;
421 struct wdrr_bucket
*bucket
;
422 struct list_head
*head
;
425 head
= &q
->new_buckets
;
426 if (list_empty(head
)) {
427 head
= &q
->old_buckets
;
428 if (list_empty(head
))
431 bucket
= list_first_entry(head
, struct wdrr_bucket
, bucketchain
);
433 if (bucket
->deficit
<= 0) {
434 int weight
= (bucket
- q
->buckets
== WDRR_BUCKET_FOR_HH
) ?
435 1 : q
->hhf_non_hh_weight
;
437 bucket
->deficit
+= weight
* q
->quantum
;
438 list_move_tail(&bucket
->bucketchain
, &q
->old_buckets
);
443 skb
= dequeue_head(bucket
);
445 qdisc_qstats_backlog_dec(sch
, skb
);
449 /* Force a pass through old_buckets to prevent starvation. */
450 if ((head
== &q
->new_buckets
) && !list_empty(&q
->old_buckets
))
451 list_move_tail(&bucket
->bucketchain
, &q
->old_buckets
);
453 list_del_init(&bucket
->bucketchain
);
456 qdisc_bstats_update(sch
, skb
);
457 bucket
->deficit
-= qdisc_pkt_len(skb
);
462 static void hhf_reset(struct Qdisc
*sch
)
466 while ((skb
= hhf_dequeue(sch
)) != NULL
)
467 rtnl_kfree_skbs(skb
, skb
);
470 static void hhf_destroy(struct Qdisc
*sch
)
473 struct hhf_sched_data
*q
= qdisc_priv(sch
);
475 for (i
= 0; i
< HHF_ARRAYS_CNT
; i
++) {
476 kvfree(q
->hhf_arrays
[i
]);
477 kvfree(q
->hhf_valid_bits
[i
]);
483 for (i
= 0; i
< HH_FLOWS_CNT
; i
++) {
484 struct hh_flow_state
*flow
, *next
;
485 struct list_head
*head
= &q
->hh_flows
[i
];
487 if (list_empty(head
))
489 list_for_each_entry_safe(flow
, next
, head
, flowchain
) {
490 list_del(&flow
->flowchain
);
497 static const struct nla_policy hhf_policy
[TCA_HHF_MAX
+ 1] = {
498 [TCA_HHF_BACKLOG_LIMIT
] = { .type
= NLA_U32
},
499 [TCA_HHF_QUANTUM
] = { .type
= NLA_U32
},
500 [TCA_HHF_HH_FLOWS_LIMIT
] = { .type
= NLA_U32
},
501 [TCA_HHF_RESET_TIMEOUT
] = { .type
= NLA_U32
},
502 [TCA_HHF_ADMIT_BYTES
] = { .type
= NLA_U32
},
503 [TCA_HHF_EVICT_TIMEOUT
] = { .type
= NLA_U32
},
504 [TCA_HHF_NON_HH_WEIGHT
] = { .type
= NLA_U32
},
507 static int hhf_change(struct Qdisc
*sch
, struct nlattr
*opt
,
508 struct netlink_ext_ack
*extack
)
510 struct hhf_sched_data
*q
= qdisc_priv(sch
);
511 struct nlattr
*tb
[TCA_HHF_MAX
+ 1];
512 unsigned int qlen
, prev_backlog
;
515 u32 new_quantum
= q
->quantum
;
516 u32 new_hhf_non_hh_weight
= q
->hhf_non_hh_weight
;
521 err
= nla_parse_nested(tb
, TCA_HHF_MAX
, opt
, hhf_policy
, NULL
);
525 if (tb
[TCA_HHF_QUANTUM
])
526 new_quantum
= nla_get_u32(tb
[TCA_HHF_QUANTUM
]);
528 if (tb
[TCA_HHF_NON_HH_WEIGHT
])
529 new_hhf_non_hh_weight
= nla_get_u32(tb
[TCA_HHF_NON_HH_WEIGHT
]);
531 non_hh_quantum
= (u64
)new_quantum
* new_hhf_non_hh_weight
;
532 if (non_hh_quantum
> INT_MAX
)
537 if (tb
[TCA_HHF_BACKLOG_LIMIT
])
538 sch
->limit
= nla_get_u32(tb
[TCA_HHF_BACKLOG_LIMIT
]);
540 q
->quantum
= new_quantum
;
541 q
->hhf_non_hh_weight
= new_hhf_non_hh_weight
;
543 if (tb
[TCA_HHF_HH_FLOWS_LIMIT
])
544 q
->hh_flows_limit
= nla_get_u32(tb
[TCA_HHF_HH_FLOWS_LIMIT
]);
546 if (tb
[TCA_HHF_RESET_TIMEOUT
]) {
547 u32 us
= nla_get_u32(tb
[TCA_HHF_RESET_TIMEOUT
]);
549 q
->hhf_reset_timeout
= usecs_to_jiffies(us
);
552 if (tb
[TCA_HHF_ADMIT_BYTES
])
553 q
->hhf_admit_bytes
= nla_get_u32(tb
[TCA_HHF_ADMIT_BYTES
]);
555 if (tb
[TCA_HHF_EVICT_TIMEOUT
]) {
556 u32 us
= nla_get_u32(tb
[TCA_HHF_EVICT_TIMEOUT
]);
558 q
->hhf_evict_timeout
= usecs_to_jiffies(us
);
562 prev_backlog
= sch
->qstats
.backlog
;
563 while (sch
->q
.qlen
> sch
->limit
) {
564 struct sk_buff
*skb
= hhf_dequeue(sch
);
566 rtnl_kfree_skbs(skb
, skb
);
568 qdisc_tree_reduce_backlog(sch
, qlen
- sch
->q
.qlen
,
569 prev_backlog
- sch
->qstats
.backlog
);
571 sch_tree_unlock(sch
);
575 static int hhf_init(struct Qdisc
*sch
, struct nlattr
*opt
,
576 struct netlink_ext_ack
*extack
)
578 struct hhf_sched_data
*q
= qdisc_priv(sch
);
582 q
->quantum
= psched_mtu(qdisc_dev(sch
));
583 q
->perturbation
= prandom_u32();
584 INIT_LIST_HEAD(&q
->new_buckets
);
585 INIT_LIST_HEAD(&q
->old_buckets
);
587 /* Configurable HHF parameters */
588 q
->hhf_reset_timeout
= HZ
/ 25; /* 40 ms */
589 q
->hhf_admit_bytes
= 131072; /* 128 KB */
590 q
->hhf_evict_timeout
= HZ
; /* 1 sec */
591 q
->hhf_non_hh_weight
= 2;
594 int err
= hhf_change(sch
, opt
, extack
);
601 /* Initialize heavy-hitter flow table. */
602 q
->hh_flows
= kvcalloc(HH_FLOWS_CNT
, sizeof(struct list_head
),
606 for (i
= 0; i
< HH_FLOWS_CNT
; i
++)
607 INIT_LIST_HEAD(&q
->hh_flows
[i
]);
609 /* Cap max active HHs at twice len of hh_flows table. */
610 q
->hh_flows_limit
= 2 * HH_FLOWS_CNT
;
611 q
->hh_flows_overlimit
= 0;
612 q
->hh_flows_total_cnt
= 0;
613 q
->hh_flows_current_cnt
= 0;
615 /* Initialize heavy-hitter filter arrays. */
616 for (i
= 0; i
< HHF_ARRAYS_CNT
; i
++) {
617 q
->hhf_arrays
[i
] = kvcalloc(HHF_ARRAYS_LEN
,
620 if (!q
->hhf_arrays
[i
]) {
621 /* Note: hhf_destroy() will be called
627 q
->hhf_arrays_reset_timestamp
= hhf_time_stamp();
629 /* Initialize valid bits of heavy-hitter filter arrays. */
630 for (i
= 0; i
< HHF_ARRAYS_CNT
; i
++) {
631 q
->hhf_valid_bits
[i
] = kvzalloc(HHF_ARRAYS_LEN
/
632 BITS_PER_BYTE
, GFP_KERNEL
);
633 if (!q
->hhf_valid_bits
[i
]) {
634 /* Note: hhf_destroy() will be called
641 /* Initialize Weighted DRR buckets. */
642 for (i
= 0; i
< WDRR_BUCKET_CNT
; i
++) {
643 struct wdrr_bucket
*bucket
= q
->buckets
+ i
;
645 INIT_LIST_HEAD(&bucket
->bucketchain
);
652 static int hhf_dump(struct Qdisc
*sch
, struct sk_buff
*skb
)
654 struct hhf_sched_data
*q
= qdisc_priv(sch
);
657 opts
= nla_nest_start(skb
, TCA_OPTIONS
);
659 goto nla_put_failure
;
661 if (nla_put_u32(skb
, TCA_HHF_BACKLOG_LIMIT
, sch
->limit
) ||
662 nla_put_u32(skb
, TCA_HHF_QUANTUM
, q
->quantum
) ||
663 nla_put_u32(skb
, TCA_HHF_HH_FLOWS_LIMIT
, q
->hh_flows_limit
) ||
664 nla_put_u32(skb
, TCA_HHF_RESET_TIMEOUT
,
665 jiffies_to_usecs(q
->hhf_reset_timeout
)) ||
666 nla_put_u32(skb
, TCA_HHF_ADMIT_BYTES
, q
->hhf_admit_bytes
) ||
667 nla_put_u32(skb
, TCA_HHF_EVICT_TIMEOUT
,
668 jiffies_to_usecs(q
->hhf_evict_timeout
)) ||
669 nla_put_u32(skb
, TCA_HHF_NON_HH_WEIGHT
, q
->hhf_non_hh_weight
))
670 goto nla_put_failure
;
672 return nla_nest_end(skb
, opts
);
678 static int hhf_dump_stats(struct Qdisc
*sch
, struct gnet_dump
*d
)
680 struct hhf_sched_data
*q
= qdisc_priv(sch
);
681 struct tc_hhf_xstats st
= {
682 .drop_overlimit
= q
->drop_overlimit
,
683 .hh_overlimit
= q
->hh_flows_overlimit
,
684 .hh_tot_count
= q
->hh_flows_total_cnt
,
685 .hh_cur_count
= q
->hh_flows_current_cnt
,
688 return gnet_stats_copy_app(d
, &st
, sizeof(st
));
691 static struct Qdisc_ops hhf_qdisc_ops __read_mostly
= {
693 .priv_size
= sizeof(struct hhf_sched_data
),
695 .enqueue
= hhf_enqueue
,
696 .dequeue
= hhf_dequeue
,
697 .peek
= qdisc_peek_dequeued
,
700 .destroy
= hhf_destroy
,
701 .change
= hhf_change
,
703 .dump_stats
= hhf_dump_stats
,
704 .owner
= THIS_MODULE
,
707 static int __init
hhf_module_init(void)
709 return register_qdisc(&hhf_qdisc_ops
);
712 static void __exit
hhf_module_exit(void)
714 unregister_qdisc(&hhf_qdisc_ops
);
717 module_init(hhf_module_init
)
718 module_exit(hhf_module_exit
)
719 MODULE_AUTHOR("Terry Lam");
720 MODULE_AUTHOR("Nandita Dukkipati");
721 MODULE_LICENSE("GPL");