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 * -r <rate> Rate limit in Mbps
20 * -s Get HBM stats (marked, dropped, etc.)
21 * -t <time> Exit after specified seconds (default is 0)
22 * -w Work conserving flag. cgroup can increase its bandwidth
23 * beyond the rate limit specified while there is available
24 * bandwidth. Current implementation assumes there is only
25 * NIC (eth0), but can be extended to support multiple NICs.
26 * Currrently only supported for egress.
28 * prog BPF program file name. Name defaults to hbm_out_kern.o
36 #include <sys/resource.h>
41 #include <linux/unistd.h>
43 #include <linux/bpf.h>
47 #include "bpf_rlimit.h"
48 #include "cgroup_helpers.h"
52 #include "bpf/libbpf.h"
55 int minRate = 1000; /* cgroup rate limit in Mbps */
56 int rate = 1000; /* can grow if rate conserving is enabled */
61 bool work_conserving_flag;
63 static void Usage(void);
64 static void read_trace_pipe2(void);
65 static void do_error(char *msg, bool errno_flag);
67 #define DEBUGFS "/sys/kernel/debug/tracing/"
69 struct bpf_object *obj;
71 int cgroup_storage_fd;
73 static void read_trace_pipe2(void)
77 char *outFname = "hbm_out.log";
79 trace_fd = open(DEBUGFS "trace_pipe", O_RDONLY, 0);
81 printf("Error opening trace_pipe\n");
85 // Future support of ingress
87 // outFname = "hbm_in.log";
88 outf = fopen(outFname, "w");
91 printf("Error creating %s\n", outFname);
94 static char buf[4097];
97 sz = read(trace_fd, buf, sizeof(buf) - 1);
102 fprintf(outf, "%s\n", buf);
109 static void do_error(char *msg, bool errno_flag)
112 printf("ERROR: %s, errno: %d\n", msg, errno);
114 printf("ERROR: %s\n", msg);
118 static int prog_load(char *prog)
120 struct bpf_prog_load_attr prog_load_attr = {
121 .prog_type = BPF_PROG_TYPE_CGROUP_SKB,
123 .expected_attach_type = BPF_CGROUP_INET_EGRESS,
130 if (access(prog, O_RDONLY) < 0) {
131 printf("Error accessing file %s: %s\n", prog, strerror(errno));
134 if (bpf_prog_load_xattr(&prog_load_attr, &obj, &bpfprog_fd))
137 map = bpf_object__find_map_by_name(obj, "queue_stats");
138 map_fd = bpf_map__fd(map);
140 printf("Map not found: %s\n", strerror(map_fd));
146 printf("ERROR: load_bpf_file failed for: %s\n", prog);
147 printf(" Output from verifier:\n%s\n------\n", bpf_log_buf);
156 static int run_bpf_prog(char *prog, int cg_id)
162 int type = BPF_CGROUP_INET_EGRESS;
164 struct hbm_queue_stats qstats = {0};
166 sprintf(cg_dir, "/hbm%d", cg_id);
167 map_fd = prog_load(prog);
171 if (setup_cgroup_environment()) {
172 printf("ERROR: setting cgroup environment\n");
175 cg1 = create_and_get_cgroup(cg_dir);
177 printf("ERROR: create_and_get_cgroup\n");
180 if (join_cgroup(cg_dir)) {
181 printf("ERROR: join_cgroup\n");
186 qstats.stats = stats_flag ? 1 : 0;
187 qstats.loopback = loopback_flag ? 1 : 0;
188 if (bpf_map_update_elem(map_fd, &key, &qstats, BPF_ANY)) {
189 printf("ERROR: Could not update map element\n");
194 type = BPF_CGROUP_INET_INGRESS;
195 if (bpf_prog_attach(bpfprog_fd, cg1, type, 0)) {
196 printf("ERROR: bpf_prog_attach fails!\n");
197 log_err("Attaching prog");
201 if (work_conserving_flag) {
202 struct timeval t0, t_last, t_new;
204 unsigned long long last_eth_tx_bytes, new_eth_tx_bytes;
205 signed long long last_cg_tx_bytes, new_cg_tx_bytes;
206 signed long long delta_time, delta_bytes, delta_rate;
208 #define DELTA_RATE_CHECK 10000 /* in us */
209 #define RATE_THRESHOLD 9500000000 /* 9.5 Gbps */
211 bpf_map_lookup_elem(map_fd, &key, &qstats);
212 if (gettimeofday(&t0, NULL) < 0)
213 do_error("gettimeofday failed", true);
215 fin = fopen("/sys/class/net/eth0/statistics/tx_bytes", "r");
216 if (fscanf(fin, "%llu", &last_eth_tx_bytes) != 1)
217 do_error("fscanf fails", false);
219 last_cg_tx_bytes = qstats.bytes_total;
221 usleep(DELTA_RATE_CHECK);
222 if (gettimeofday(&t_new, NULL) < 0)
223 do_error("gettimeofday failed", true);
224 delta_ms = (t_new.tv_sec - t0.tv_sec) * 1000 +
225 (t_new.tv_usec - t0.tv_usec)/1000;
226 if (delta_ms > dur * 1000)
228 delta_time = (t_new.tv_sec - t_last.tv_sec) * 1000000 +
229 (t_new.tv_usec - t_last.tv_usec);
233 fin = fopen("/sys/class/net/eth0/statistics/tx_bytes",
235 if (fscanf(fin, "%llu", &new_eth_tx_bytes) != 1)
236 do_error("fscanf fails", false);
238 printf(" new_eth_tx_bytes:%llu\n",
240 bpf_map_lookup_elem(map_fd, &key, &qstats);
241 new_cg_tx_bytes = qstats.bytes_total;
242 delta_bytes = new_eth_tx_bytes - last_eth_tx_bytes;
243 last_eth_tx_bytes = new_eth_tx_bytes;
244 delta_rate = (delta_bytes * 8000000) / delta_time;
245 printf("%5d - eth_rate:%.1fGbps cg_rate:%.3fGbps",
246 delta_ms, delta_rate/1000000000.0,
248 if (delta_rate < RATE_THRESHOLD) {
249 /* can increase cgroup rate limit, but first
250 * check if we are using the current limit.
251 * Currently increasing by 6.25%, unknown
252 * if that is the optimal rate.
256 delta_bytes = new_cg_tx_bytes -
258 last_cg_tx_bytes = new_cg_tx_bytes;
259 delta_rate = (delta_bytes * 8000000) /
261 printf(" rate:%.3fGbps",
262 delta_rate/1000000000.0);
263 rate_diff100 = (((long long)rate)*1000000 -
265 (((long long) rate) * 1000000);
266 printf(" rdiff:%d", rate_diff100);
267 if (rate_diff100 <= 3) {
269 if (rate > RATE_THRESHOLD / 1000000)
270 rate = RATE_THRESHOLD / 1000000;
277 /* Need to decrease cgroup rate limit.
278 * Currently decreasing by 12.5%, unknown
287 if (bpf_map_update_elem(map_fd, &key, &qstats, BPF_ANY))
288 do_error("update map element fails", false);
294 if (stats_flag && bpf_map_lookup_elem(map_fd, &key, &qstats)) {
299 sprintf(fname, "hbm.%d.in", cg_id);
301 sprintf(fname, "hbm.%d.out", cg_id);
302 fout = fopen(fname, "w");
303 fprintf(fout, "id:%d\n", cg_id);
304 fprintf(fout, "ERROR: Could not lookup queue_stats\n");
305 } else if (stats_flag && qstats.lastPacketTime >
306 qstats.firstPacketTime) {
307 long long delta_us = (qstats.lastPacketTime -
308 qstats.firstPacketTime)/1000;
309 unsigned int rate_mbps = ((qstats.bytes_total -
310 qstats.bytes_dropped) * 8 /
312 double percent_pkts, percent_bytes;
316 // Future support of ingress
318 // sprintf(fname, "hbm.%d.in", cg_id);
320 sprintf(fname, "hbm.%d.out", cg_id);
321 fout = fopen(fname, "w");
322 fprintf(fout, "id:%d\n", cg_id);
323 fprintf(fout, "rate_mbps:%d\n", rate_mbps);
324 fprintf(fout, "duration:%.1f secs\n",
325 (qstats.lastPacketTime - qstats.firstPacketTime) /
327 fprintf(fout, "packets:%d\n", (int)qstats.pkts_total);
328 fprintf(fout, "bytes_MB:%d\n", (int)(qstats.bytes_total /
330 fprintf(fout, "pkts_dropped:%d\n", (int)qstats.pkts_dropped);
331 fprintf(fout, "bytes_dropped_MB:%d\n",
332 (int)(qstats.bytes_dropped /
334 // Marked Pkts and Bytes
335 percent_pkts = (qstats.pkts_marked * 100.0) /
336 (qstats.pkts_total + 1);
337 percent_bytes = (qstats.bytes_marked * 100.0) /
338 (qstats.bytes_total + 1);
339 fprintf(fout, "pkts_marked_percent:%6.2f\n", percent_pkts);
340 fprintf(fout, "bytes_marked_percent:%6.2f\n", percent_bytes);
342 // Dropped Pkts and Bytes
343 percent_pkts = (qstats.pkts_dropped * 100.0) /
344 (qstats.pkts_total + 1);
345 percent_bytes = (qstats.bytes_dropped * 100.0) /
346 (qstats.bytes_total + 1);
347 fprintf(fout, "pkts_dropped_percent:%6.2f\n", percent_pkts);
348 fprintf(fout, "bytes_dropped_percent:%6.2f\n", percent_bytes);
360 cleanup_cgroup_environment();
365 static void Usage(void)
367 printf("This program loads a cgroup skb BPF program to enforce\n"
368 "cgroup output (egress) bandwidth limits.\n\n"
369 "USAGE: hbm [-o] [-d] [-l] [-n <id>] [-r <rate>] [-s]\n"
370 " [-t <secs>] [-w] [-h] [prog]\n"
372 " -o indicates egress direction (default)\n"
373 " -d print BPF trace debug buffer\n"
374 " -l also limit flows using loopback\n"
375 " -n <#> to create cgroup \"/hbm#\" and attach prog\n"
376 " Default is /hbm1\n"
377 " -r <rate> Rate in Mbps\n"
378 " -s Update HBM stats\n"
379 " -t <time> Exit after specified seconds (default is 0)\n"
380 " -w Work conserving flag. cgroup can increase\n"
381 " bandwidth beyond the rate limit specified\n"
382 " while there is available bandwidth. Current\n"
383 " implementation assumes there is only eth0\n"
384 " but can be extended to support multiple NICs\n"
385 " -h print this info\n"
386 " prog BPF program file name. Name defaults to\n"
387 " hbm_out_kern.o\n");
390 int main(int argc, char **argv)
392 char *prog = "hbm_out_kern.o";
395 char *optstring = "iodln:r:st:wh";
397 while ((k = getopt(argc, argv, optstring)) != -1) {
405 loopback_flag = true;
408 cg_id = atoi(optarg);
411 minRate = atoi(optarg) * 1.024;
421 work_conserving_flag = true;
424 if (optopt == 'n' || optopt == 'r' || optopt == 't')
426 "Option -%c requires an argument.\n\n",
438 printf("HBM prog: %s\n", prog != NULL ? prog : "NULL");
440 return run_bpf_prog(prog, cg_id);