1 // SPDX-License-Identifier: GPL-2.0
2 /* Copyright (c) 2019 Facebook
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of version 2 of the GNU General Public
6 * License as published by the Free Software Foundation.
8 * Example program for Host Bandwidth Managment
10 * This program loads a cgroup skb BPF program to enforce cgroup output
11 * (egress) or input (ingress) bandwidth limits.
13 * USAGE: hbm [-d] [-l] [-n <id>] [-r <rate>] [-s] [-t <secs>] [-w] [-h] [prog]
15 * -d Print BPF trace debug buffer
16 * -l Also limit flows doing loopback
17 * -n <#> To create cgroup \"/hbm#\" and attach prog
19 * --no_cn Do not return cn notifications
20 * -r <rate> Rate limit in Mbps
21 * -s Get HBM stats (marked, dropped, etc.)
22 * -t <time> Exit after specified seconds (default is 0)
23 * -w Work conserving flag. cgroup can increase its bandwidth
24 * beyond the rate limit specified while there is available
25 * bandwidth. Current implementation assumes there is only
26 * NIC (eth0), but can be extended to support multiple NICs.
27 * Currrently only supported for egress.
29 * prog BPF program file name. Name defaults to hbm_out_kern.o
37 #include <sys/resource.h>
42 #include <linux/unistd.h>
44 #include <linux/bpf.h>
49 #include "bpf_rlimit.h"
50 #include "cgroup_helpers.h"
57 int minRate
= 1000; /* cgroup rate limit in Mbps */
58 int rate
= 1000; /* can grow if rate conserving is enabled */
63 bool work_conserving_flag
;
67 static void Usage(void);
68 static void read_trace_pipe2(void);
69 static void do_error(char *msg
, bool errno_flag
);
71 #define DEBUGFS "/sys/kernel/debug/tracing/"
73 struct bpf_object
*obj
;
75 int cgroup_storage_fd
;
77 static void read_trace_pipe2(void)
81 char *outFname
= "hbm_out.log";
83 trace_fd
= open(DEBUGFS
"trace_pipe", O_RDONLY
, 0);
85 printf("Error opening trace_pipe\n");
89 // Future support of ingress
91 // outFname = "hbm_in.log";
92 outf
= fopen(outFname
, "w");
95 printf("Error creating %s\n", outFname
);
98 static char buf
[4097];
101 sz
= read(trace_fd
, buf
, sizeof(buf
) - 1);
106 fprintf(outf
, "%s\n", buf
);
113 static void do_error(char *msg
, bool errno_flag
)
116 printf("ERROR: %s, errno: %d\n", msg
, errno
);
118 printf("ERROR: %s\n", msg
);
122 static int prog_load(char *prog
)
124 struct bpf_prog_load_attr prog_load_attr
= {
125 .prog_type
= BPF_PROG_TYPE_CGROUP_SKB
,
127 .expected_attach_type
= BPF_CGROUP_INET_EGRESS
,
134 if (access(prog
, O_RDONLY
) < 0) {
135 printf("Error accessing file %s: %s\n", prog
, strerror(errno
));
138 if (bpf_prog_load_xattr(&prog_load_attr
, &obj
, &bpfprog_fd
))
141 map
= bpf_object__find_map_by_name(obj
, "queue_stats");
142 map_fd
= bpf_map__fd(map
);
144 printf("Map not found: %s\n", strerror(map_fd
));
150 printf("ERROR: bpf_prog_load_xattr failed for: %s\n", prog
);
151 printf(" Output from verifier:\n%s\n------\n", bpf_log_buf
);
160 static int run_bpf_prog(char *prog
, int cg_id
)
166 int type
= BPF_CGROUP_INET_EGRESS
;
168 struct hbm_queue_stats qstats
= {0};
170 sprintf(cg_dir
, "/hbm%d", cg_id
);
171 map_fd
= prog_load(prog
);
175 if (setup_cgroup_environment()) {
176 printf("ERROR: setting cgroup environment\n");
179 cg1
= create_and_get_cgroup(cg_dir
);
181 printf("ERROR: create_and_get_cgroup\n");
184 if (join_cgroup(cg_dir
)) {
185 printf("ERROR: join_cgroup\n");
190 qstats
.stats
= stats_flag
? 1 : 0;
191 qstats
.loopback
= loopback_flag
? 1 : 0;
192 qstats
.no_cn
= no_cn_flag
? 1 : 0;
193 if (bpf_map_update_elem(map_fd
, &key
, &qstats
, BPF_ANY
)) {
194 printf("ERROR: Could not update map element\n");
199 type
= BPF_CGROUP_INET_INGRESS
;
200 if (bpf_prog_attach(bpfprog_fd
, cg1
, type
, 0)) {
201 printf("ERROR: bpf_prog_attach fails!\n");
202 log_err("Attaching prog");
206 if (work_conserving_flag
) {
207 struct timeval t0
, t_last
, t_new
;
209 unsigned long long last_eth_tx_bytes
, new_eth_tx_bytes
;
210 signed long long last_cg_tx_bytes
, new_cg_tx_bytes
;
211 signed long long delta_time
, delta_bytes
, delta_rate
;
213 #define DELTA_RATE_CHECK 10000 /* in us */
214 #define RATE_THRESHOLD 9500000000 /* 9.5 Gbps */
216 bpf_map_lookup_elem(map_fd
, &key
, &qstats
);
217 if (gettimeofday(&t0
, NULL
) < 0)
218 do_error("gettimeofday failed", true);
220 fin
= fopen("/sys/class/net/eth0/statistics/tx_bytes", "r");
221 if (fscanf(fin
, "%llu", &last_eth_tx_bytes
) != 1)
222 do_error("fscanf fails", false);
224 last_cg_tx_bytes
= qstats
.bytes_total
;
226 usleep(DELTA_RATE_CHECK
);
227 if (gettimeofday(&t_new
, NULL
) < 0)
228 do_error("gettimeofday failed", true);
229 delta_ms
= (t_new
.tv_sec
- t0
.tv_sec
) * 1000 +
230 (t_new
.tv_usec
- t0
.tv_usec
)/1000;
231 if (delta_ms
> dur
* 1000)
233 delta_time
= (t_new
.tv_sec
- t_last
.tv_sec
) * 1000000 +
234 (t_new
.tv_usec
- t_last
.tv_usec
);
238 fin
= fopen("/sys/class/net/eth0/statistics/tx_bytes",
240 if (fscanf(fin
, "%llu", &new_eth_tx_bytes
) != 1)
241 do_error("fscanf fails", false);
243 printf(" new_eth_tx_bytes:%llu\n",
245 bpf_map_lookup_elem(map_fd
, &key
, &qstats
);
246 new_cg_tx_bytes
= qstats
.bytes_total
;
247 delta_bytes
= new_eth_tx_bytes
- last_eth_tx_bytes
;
248 last_eth_tx_bytes
= new_eth_tx_bytes
;
249 delta_rate
= (delta_bytes
* 8000000) / delta_time
;
250 printf("%5d - eth_rate:%.1fGbps cg_rate:%.3fGbps",
251 delta_ms
, delta_rate
/1000000000.0,
253 if (delta_rate
< RATE_THRESHOLD
) {
254 /* can increase cgroup rate limit, but first
255 * check if we are using the current limit.
256 * Currently increasing by 6.25%, unknown
257 * if that is the optimal rate.
261 delta_bytes
= new_cg_tx_bytes
-
263 last_cg_tx_bytes
= new_cg_tx_bytes
;
264 delta_rate
= (delta_bytes
* 8000000) /
266 printf(" rate:%.3fGbps",
267 delta_rate
/1000000000.0);
268 rate_diff100
= (((long long)rate
)*1000000 -
270 (((long long) rate
) * 1000000);
271 printf(" rdiff:%d", rate_diff100
);
272 if (rate_diff100
<= 3) {
274 if (rate
> RATE_THRESHOLD
/ 1000000)
275 rate
= RATE_THRESHOLD
/ 1000000;
282 /* Need to decrease cgroup rate limit.
283 * Currently decreasing by 12.5%, unknown
292 if (bpf_map_update_elem(map_fd
, &key
, &qstats
, BPF_ANY
))
293 do_error("update map element fails", false);
299 if (stats_flag
&& bpf_map_lookup_elem(map_fd
, &key
, &qstats
)) {
304 sprintf(fname
, "hbm.%d.in", cg_id
);
306 sprintf(fname
, "hbm.%d.out", cg_id
);
307 fout
= fopen(fname
, "w");
308 fprintf(fout
, "id:%d\n", cg_id
);
309 fprintf(fout
, "ERROR: Could not lookup queue_stats\n");
310 } else if (stats_flag
&& qstats
.lastPacketTime
>
311 qstats
.firstPacketTime
) {
312 long long delta_us
= (qstats
.lastPacketTime
-
313 qstats
.firstPacketTime
)/1000;
314 unsigned int rate_mbps
= ((qstats
.bytes_total
-
315 qstats
.bytes_dropped
) * 8 /
317 double percent_pkts
, percent_bytes
;
321 static const char *returnValNames
[] = {
327 #define RET_VAL_COUNT 4
329 // Future support of ingress
331 // sprintf(fname, "hbm.%d.in", cg_id);
333 sprintf(fname
, "hbm.%d.out", cg_id
);
334 fout
= fopen(fname
, "w");
335 fprintf(fout
, "id:%d\n", cg_id
);
336 fprintf(fout
, "rate_mbps:%d\n", rate_mbps
);
337 fprintf(fout
, "duration:%.1f secs\n",
338 (qstats
.lastPacketTime
- qstats
.firstPacketTime
) /
340 fprintf(fout
, "packets:%d\n", (int)qstats
.pkts_total
);
341 fprintf(fout
, "bytes_MB:%d\n", (int)(qstats
.bytes_total
/
343 fprintf(fout
, "pkts_dropped:%d\n", (int)qstats
.pkts_dropped
);
344 fprintf(fout
, "bytes_dropped_MB:%d\n",
345 (int)(qstats
.bytes_dropped
/
347 // Marked Pkts and Bytes
348 percent_pkts
= (qstats
.pkts_marked
* 100.0) /
349 (qstats
.pkts_total
+ 1);
350 percent_bytes
= (qstats
.bytes_marked
* 100.0) /
351 (qstats
.bytes_total
+ 1);
352 fprintf(fout
, "pkts_marked_percent:%6.2f\n", percent_pkts
);
353 fprintf(fout
, "bytes_marked_percent:%6.2f\n", percent_bytes
);
355 // Dropped Pkts and Bytes
356 percent_pkts
= (qstats
.pkts_dropped
* 100.0) /
357 (qstats
.pkts_total
+ 1);
358 percent_bytes
= (qstats
.bytes_dropped
* 100.0) /
359 (qstats
.bytes_total
+ 1);
360 fprintf(fout
, "pkts_dropped_percent:%6.2f\n", percent_pkts
);
361 fprintf(fout
, "bytes_dropped_percent:%6.2f\n", percent_bytes
);
364 percent_pkts
= (qstats
.pkts_ecn_ce
* 100.0) /
365 (qstats
.pkts_total
+ 1);
366 fprintf(fout
, "pkts_ecn_ce:%6.2f (%d)\n", percent_pkts
,
367 (int)qstats
.pkts_ecn_ce
);
370 fprintf(fout
, "avg cwnd:%d\n",
371 (int)(qstats
.sum_cwnd
/ (qstats
.sum_cwnd_cnt
+ 1)));
373 fprintf(fout
, "avg rtt:%d\n",
374 (int)(qstats
.sum_rtt
/ (qstats
.pkts_total
+ 1)));
377 fprintf(fout
, "avg credit_ms:%.03f\n",
379 (qstats
.pkts_total
+ 1.0)) / 1000000.0);
381 fprintf(fout
, "avg credit:%d\n",
382 (int)(qstats
.sum_credit
/
383 (1500 * ((int)qstats
.pkts_total
) + 1)));
385 // Return values stats
386 for (k
= 0; k
< RET_VAL_COUNT
; k
++) {
387 percent_pkts
= (qstats
.returnValCount
[k
] * 100.0) /
388 (qstats
.pkts_total
+ 1);
389 fprintf(fout
, "%s:%6.2f (%d)\n", returnValNames
[k
],
390 percent_pkts
, (int)qstats
.returnValCount
[k
]);
403 cleanup_cgroup_environment();
408 static void Usage(void)
410 printf("This program loads a cgroup skb BPF program to enforce\n"
411 "cgroup output (egress) bandwidth limits.\n\n"
412 "USAGE: hbm [-o] [-d] [-l] [-n <id>] [--no_cn] [-r <rate>]\n"
413 " [-s] [-t <secs>] [-w] [-h] [prog]\n"
415 " -o indicates egress direction (default)\n"
416 " -d print BPF trace debug buffer\n"
417 " --edt use fq's Earliest Departure Time\n"
418 " -l also limit flows using loopback\n"
419 " -n <#> to create cgroup \"/hbm#\" and attach prog\n"
420 " Default is /hbm1\n"
421 " --no_cn disable CN notifications\n"
422 " -r <rate> Rate in Mbps\n"
423 " -s Update HBM stats\n"
424 " -t <time> Exit after specified seconds (default is 0)\n"
425 " -w Work conserving flag. cgroup can increase\n"
426 " bandwidth beyond the rate limit specified\n"
427 " while there is available bandwidth. Current\n"
428 " implementation assumes there is only eth0\n"
429 " but can be extended to support multiple NICs\n"
430 " -h print this info\n"
431 " prog BPF program file name. Name defaults to\n"
432 " hbm_out_kern.o\n");
435 int main(int argc
, char **argv
)
437 char *prog
= "hbm_out_kern.o";
440 char *optstring
= "iodln:r:st:wh";
441 struct option loptions
[] = {
442 {"no_cn", 0, NULL
, 1},
447 while ((k
= getopt_long(argc
, argv
, optstring
, loptions
, NULL
)) != -1) {
453 prog
= "hbm_edt_kern.o";
462 loopback_flag
= true;
465 cg_id
= atoi(optarg
);
468 minRate
= atoi(optarg
) * 1.024;
478 work_conserving_flag
= true;
481 if (optopt
== 'n' || optopt
== 'r' || optopt
== 't')
483 "Option -%c requires an argument.\n\n",
495 printf("HBM prog: %s\n", prog
!= NULL
? prog
: "NULL");
497 return run_bpf_prog(prog
, cg_id
);