blob: bdfce592207a2c5f1b320f9e4398d7cd1bf5d626 [file] [log] [blame]
brakmoa1270fe2019-03-01 12:38:49 -08001// SPDX-License-Identifier: GPL-2.0
2/* Copyright (c) 2019 Facebook
3 *
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.
7 *
8 * Example program for Host Bandwidth Managment
9 *
10 * This program loads a cgroup skb BPF program to enforce cgroup output
11 * (egress) or input (ingress) bandwidth limits.
12 *
13 * USAGE: hbm [-d] [-l] [-n <id>] [-r <rate>] [-s] [-t <secs>] [-w] [-h] [prog]
14 * Where:
15 * -d Print BPF trace debug buffer
16 * -l Also limit flows doing loopback
17 * -n <#> To create cgroup \"/hbm#\" and attach prog
18 * Default is /hbm1
brakmoffd81552019-05-28 16:59:39 -070019 * --no_cn Do not return cn notifications
brakmoa1270fe2019-03-01 12:38:49 -080020 * -r <rate> Rate limit in Mbps
21 * -s Get HBM stats (marked, dropped, etc.)
Colin Ian King5b4f21b2019-03-05 17:31:13 +000022 * -t <time> Exit after specified seconds (default is 0)
brakmoa1270fe2019-03-01 12:38:49 -080023 * -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.
28 * -h Print this info
29 * prog BPF program file name. Name defaults to hbm_out_kern.o
30 */
31
32#define _GNU_SOURCE
33
34#include <stdio.h>
35#include <stdlib.h>
36#include <assert.h>
37#include <sys/resource.h>
38#include <sys/time.h>
39#include <unistd.h>
40#include <errno.h>
41#include <fcntl.h>
42#include <linux/unistd.h>
43
44#include <linux/bpf.h>
45#include <bpf/bpf.h>
brakmoffd81552019-05-28 16:59:39 -070046#include <getopt.h>
brakmoa1270fe2019-03-01 12:38:49 -080047
48#include "bpf_load.h"
49#include "bpf_rlimit.h"
50#include "cgroup_helpers.h"
51#include "hbm.h"
52#include "bpf_util.h"
53#include "bpf/bpf.h"
54#include "bpf/libbpf.h"
55
56bool outFlag = true;
57int minRate = 1000; /* cgroup rate limit in Mbps */
58int rate = 1000; /* can grow if rate conserving is enabled */
59int dur = 1;
60bool stats_flag;
61bool loopback_flag;
62bool debugFlag;
63bool work_conserving_flag;
brakmoffd81552019-05-28 16:59:39 -070064bool no_cn_flag;
brakmoa1270fe2019-03-01 12:38:49 -080065
66static void Usage(void);
67static void read_trace_pipe2(void);
68static void do_error(char *msg, bool errno_flag);
69
70#define DEBUGFS "/sys/kernel/debug/tracing/"
71
72struct bpf_object *obj;
73int bpfprog_fd;
74int cgroup_storage_fd;
75
76static void read_trace_pipe2(void)
77{
78 int trace_fd;
79 FILE *outf;
80 char *outFname = "hbm_out.log";
81
82 trace_fd = open(DEBUGFS "trace_pipe", O_RDONLY, 0);
83 if (trace_fd < 0) {
84 printf("Error opening trace_pipe\n");
85 return;
86 }
87
88// Future support of ingress
89// if (!outFlag)
90// outFname = "hbm_in.log";
91 outf = fopen(outFname, "w");
92
93 if (outf == NULL)
94 printf("Error creating %s\n", outFname);
95
96 while (1) {
97 static char buf[4097];
98 ssize_t sz;
99
100 sz = read(trace_fd, buf, sizeof(buf) - 1);
101 if (sz > 0) {
102 buf[sz] = 0;
103 puts(buf);
104 if (outf != NULL) {
105 fprintf(outf, "%s\n", buf);
106 fflush(outf);
107 }
108 }
109 }
110}
111
112static void do_error(char *msg, bool errno_flag)
113{
114 if (errno_flag)
115 printf("ERROR: %s, errno: %d\n", msg, errno);
116 else
117 printf("ERROR: %s\n", msg);
118 exit(1);
119}
120
121static int prog_load(char *prog)
122{
123 struct bpf_prog_load_attr prog_load_attr = {
124 .prog_type = BPF_PROG_TYPE_CGROUP_SKB,
125 .file = prog,
126 .expected_attach_type = BPF_CGROUP_INET_EGRESS,
127 };
128 int map_fd;
129 struct bpf_map *map;
130
131 int ret = 0;
132
133 if (access(prog, O_RDONLY) < 0) {
134 printf("Error accessing file %s: %s\n", prog, strerror(errno));
135 return 1;
136 }
137 if (bpf_prog_load_xattr(&prog_load_attr, &obj, &bpfprog_fd))
138 ret = 1;
139 if (!ret) {
140 map = bpf_object__find_map_by_name(obj, "queue_stats");
141 map_fd = bpf_map__fd(map);
142 if (map_fd < 0) {
143 printf("Map not found: %s\n", strerror(map_fd));
144 ret = 1;
145 }
146 }
147
148 if (ret) {
149 printf("ERROR: load_bpf_file failed for: %s\n", prog);
150 printf(" Output from verifier:\n%s\n------\n", bpf_log_buf);
151 ret = -1;
152 } else {
153 ret = map_fd;
154 }
155
156 return ret;
157}
158
159static int run_bpf_prog(char *prog, int cg_id)
160{
161 int map_fd;
162 int rc = 0;
163 int key = 0;
164 int cg1 = 0;
165 int type = BPF_CGROUP_INET_EGRESS;
166 char cg_dir[100];
167 struct hbm_queue_stats qstats = {0};
168
169 sprintf(cg_dir, "/hbm%d", cg_id);
170 map_fd = prog_load(prog);
171 if (map_fd == -1)
172 return 1;
173
174 if (setup_cgroup_environment()) {
175 printf("ERROR: setting cgroup environment\n");
176 goto err;
177 }
178 cg1 = create_and_get_cgroup(cg_dir);
179 if (!cg1) {
180 printf("ERROR: create_and_get_cgroup\n");
181 goto err;
182 }
183 if (join_cgroup(cg_dir)) {
184 printf("ERROR: join_cgroup\n");
185 goto err;
186 }
187
188 qstats.rate = rate;
189 qstats.stats = stats_flag ? 1 : 0;
190 qstats.loopback = loopback_flag ? 1 : 0;
brakmoffd81552019-05-28 16:59:39 -0700191 qstats.no_cn = no_cn_flag ? 1 : 0;
brakmoa1270fe2019-03-01 12:38:49 -0800192 if (bpf_map_update_elem(map_fd, &key, &qstats, BPF_ANY)) {
193 printf("ERROR: Could not update map element\n");
194 goto err;
195 }
196
197 if (!outFlag)
198 type = BPF_CGROUP_INET_INGRESS;
199 if (bpf_prog_attach(bpfprog_fd, cg1, type, 0)) {
200 printf("ERROR: bpf_prog_attach fails!\n");
201 log_err("Attaching prog");
202 goto err;
203 }
204
205 if (work_conserving_flag) {
206 struct timeval t0, t_last, t_new;
207 FILE *fin;
208 unsigned long long last_eth_tx_bytes, new_eth_tx_bytes;
209 signed long long last_cg_tx_bytes, new_cg_tx_bytes;
210 signed long long delta_time, delta_bytes, delta_rate;
211 int delta_ms;
212#define DELTA_RATE_CHECK 10000 /* in us */
213#define RATE_THRESHOLD 9500000000 /* 9.5 Gbps */
214
215 bpf_map_lookup_elem(map_fd, &key, &qstats);
216 if (gettimeofday(&t0, NULL) < 0)
217 do_error("gettimeofday failed", true);
218 t_last = t0;
219 fin = fopen("/sys/class/net/eth0/statistics/tx_bytes", "r");
220 if (fscanf(fin, "%llu", &last_eth_tx_bytes) != 1)
221 do_error("fscanf fails", false);
222 fclose(fin);
223 last_cg_tx_bytes = qstats.bytes_total;
224 while (true) {
225 usleep(DELTA_RATE_CHECK);
226 if (gettimeofday(&t_new, NULL) < 0)
227 do_error("gettimeofday failed", true);
228 delta_ms = (t_new.tv_sec - t0.tv_sec) * 1000 +
229 (t_new.tv_usec - t0.tv_usec)/1000;
230 if (delta_ms > dur * 1000)
231 break;
232 delta_time = (t_new.tv_sec - t_last.tv_sec) * 1000000 +
233 (t_new.tv_usec - t_last.tv_usec);
234 if (delta_time == 0)
235 continue;
236 t_last = t_new;
237 fin = fopen("/sys/class/net/eth0/statistics/tx_bytes",
238 "r");
239 if (fscanf(fin, "%llu", &new_eth_tx_bytes) != 1)
240 do_error("fscanf fails", false);
241 fclose(fin);
242 printf(" new_eth_tx_bytes:%llu\n",
243 new_eth_tx_bytes);
244 bpf_map_lookup_elem(map_fd, &key, &qstats);
245 new_cg_tx_bytes = qstats.bytes_total;
246 delta_bytes = new_eth_tx_bytes - last_eth_tx_bytes;
247 last_eth_tx_bytes = new_eth_tx_bytes;
248 delta_rate = (delta_bytes * 8000000) / delta_time;
249 printf("%5d - eth_rate:%.1fGbps cg_rate:%.3fGbps",
250 delta_ms, delta_rate/1000000000.0,
251 rate/1000.0);
252 if (delta_rate < RATE_THRESHOLD) {
253 /* can increase cgroup rate limit, but first
254 * check if we are using the current limit.
255 * Currently increasing by 6.25%, unknown
256 * if that is the optimal rate.
257 */
258 int rate_diff100;
259
260 delta_bytes = new_cg_tx_bytes -
261 last_cg_tx_bytes;
262 last_cg_tx_bytes = new_cg_tx_bytes;
263 delta_rate = (delta_bytes * 8000000) /
264 delta_time;
265 printf(" rate:%.3fGbps",
266 delta_rate/1000000000.0);
267 rate_diff100 = (((long long)rate)*1000000 -
268 delta_rate) * 100 /
269 (((long long) rate) * 1000000);
270 printf(" rdiff:%d", rate_diff100);
271 if (rate_diff100 <= 3) {
272 rate += (rate >> 4);
273 if (rate > RATE_THRESHOLD / 1000000)
274 rate = RATE_THRESHOLD / 1000000;
275 qstats.rate = rate;
276 printf(" INC\n");
277 } else {
278 printf("\n");
279 }
280 } else {
281 /* Need to decrease cgroup rate limit.
282 * Currently decreasing by 12.5%, unknown
283 * if that is optimal
284 */
285 printf(" DEC\n");
286 rate -= (rate >> 3);
287 if (rate < minRate)
288 rate = minRate;
289 qstats.rate = rate;
290 }
291 if (bpf_map_update_elem(map_fd, &key, &qstats, BPF_ANY))
292 do_error("update map element fails", false);
293 }
294 } else {
295 sleep(dur);
296 }
297 // Get stats!
298 if (stats_flag && bpf_map_lookup_elem(map_fd, &key, &qstats)) {
299 char fname[100];
300 FILE *fout;
301
302 if (!outFlag)
303 sprintf(fname, "hbm.%d.in", cg_id);
304 else
305 sprintf(fname, "hbm.%d.out", cg_id);
306 fout = fopen(fname, "w");
307 fprintf(fout, "id:%d\n", cg_id);
308 fprintf(fout, "ERROR: Could not lookup queue_stats\n");
309 } else if (stats_flag && qstats.lastPacketTime >
310 qstats.firstPacketTime) {
311 long long delta_us = (qstats.lastPacketTime -
312 qstats.firstPacketTime)/1000;
313 unsigned int rate_mbps = ((qstats.bytes_total -
314 qstats.bytes_dropped) * 8 /
315 delta_us);
316 double percent_pkts, percent_bytes;
317 char fname[100];
318 FILE *fout;
brakmod58c6f72019-05-28 16:59:40 -0700319 int k;
320 static const char *returnValNames[] = {
321 "DROP_PKT",
322 "ALLOW_PKT",
323 "DROP_PKT_CWR",
324 "ALLOW_PKT_CWR"
325 };
326#define RET_VAL_COUNT 4
brakmoa1270fe2019-03-01 12:38:49 -0800327
328// Future support of ingress
329// if (!outFlag)
330// sprintf(fname, "hbm.%d.in", cg_id);
331// else
332 sprintf(fname, "hbm.%d.out", cg_id);
333 fout = fopen(fname, "w");
334 fprintf(fout, "id:%d\n", cg_id);
335 fprintf(fout, "rate_mbps:%d\n", rate_mbps);
336 fprintf(fout, "duration:%.1f secs\n",
337 (qstats.lastPacketTime - qstats.firstPacketTime) /
338 1000000000.0);
339 fprintf(fout, "packets:%d\n", (int)qstats.pkts_total);
340 fprintf(fout, "bytes_MB:%d\n", (int)(qstats.bytes_total /
341 1000000));
342 fprintf(fout, "pkts_dropped:%d\n", (int)qstats.pkts_dropped);
343 fprintf(fout, "bytes_dropped_MB:%d\n",
344 (int)(qstats.bytes_dropped /
345 1000000));
346 // Marked Pkts and Bytes
347 percent_pkts = (qstats.pkts_marked * 100.0) /
348 (qstats.pkts_total + 1);
349 percent_bytes = (qstats.bytes_marked * 100.0) /
350 (qstats.bytes_total + 1);
351 fprintf(fout, "pkts_marked_percent:%6.2f\n", percent_pkts);
352 fprintf(fout, "bytes_marked_percent:%6.2f\n", percent_bytes);
353
354 // Dropped Pkts and Bytes
355 percent_pkts = (qstats.pkts_dropped * 100.0) /
356 (qstats.pkts_total + 1);
357 percent_bytes = (qstats.bytes_dropped * 100.0) /
358 (qstats.bytes_total + 1);
359 fprintf(fout, "pkts_dropped_percent:%6.2f\n", percent_pkts);
360 fprintf(fout, "bytes_dropped_percent:%6.2f\n", percent_bytes);
brakmod58c6f72019-05-28 16:59:40 -0700361
362 // ECN CE markings
363 percent_pkts = (qstats.pkts_ecn_ce * 100.0) /
364 (qstats.pkts_total + 1);
365 fprintf(fout, "pkts_ecn_ce:%6.2f (%d)\n", percent_pkts,
366 (int)qstats.pkts_ecn_ce);
367
368 // Average cwnd
369 fprintf(fout, "avg cwnd:%d\n",
370 (int)(qstats.sum_cwnd / (qstats.sum_cwnd_cnt + 1)));
371 // Average rtt
372 fprintf(fout, "avg rtt:%d\n",
373 (int)(qstats.sum_rtt / (qstats.pkts_total + 1)));
374 // Average credit
375 fprintf(fout, "avg credit:%d\n",
376 (int)(qstats.sum_credit /
377 (1500 * ((int)qstats.pkts_total) + 1)));
378
379 // Return values stats
380 for (k = 0; k < RET_VAL_COUNT; k++) {
381 percent_pkts = (qstats.returnValCount[k] * 100.0) /
382 (qstats.pkts_total + 1);
383 fprintf(fout, "%s:%6.2f (%d)\n", returnValNames[k],
384 percent_pkts, (int)qstats.returnValCount[k]);
385 }
brakmoa1270fe2019-03-01 12:38:49 -0800386 fclose(fout);
387 }
388
389 if (debugFlag)
390 read_trace_pipe2();
391 return rc;
392err:
393 rc = 1;
394
395 if (cg1)
396 close(cg1);
397 cleanup_cgroup_environment();
398
399 return rc;
400}
401
402static void Usage(void)
403{
404 printf("This program loads a cgroup skb BPF program to enforce\n"
405 "cgroup output (egress) bandwidth limits.\n\n"
brakmoffd81552019-05-28 16:59:39 -0700406 "USAGE: hbm [-o] [-d] [-l] [-n <id>] [--no_cn] [-r <rate>]\n"
407 " [-s] [-t <secs>] [-w] [-h] [prog]\n"
brakmoa1270fe2019-03-01 12:38:49 -0800408 " Where:\n"
409 " -o indicates egress direction (default)\n"
410 " -d print BPF trace debug buffer\n"
411 " -l also limit flows using loopback\n"
412 " -n <#> to create cgroup \"/hbm#\" and attach prog\n"
413 " Default is /hbm1\n"
Colin Ian King2ed99332019-06-03 14:36:53 +0100414 " --no_cn disable CN notifications\n"
brakmoa1270fe2019-03-01 12:38:49 -0800415 " -r <rate> Rate in Mbps\n"
416 " -s Update HBM stats\n"
Colin Ian King5b4f21b2019-03-05 17:31:13 +0000417 " -t <time> Exit after specified seconds (default is 0)\n"
brakmoa1270fe2019-03-01 12:38:49 -0800418 " -w Work conserving flag. cgroup can increase\n"
419 " bandwidth beyond the rate limit specified\n"
420 " while there is available bandwidth. Current\n"
421 " implementation assumes there is only eth0\n"
422 " but can be extended to support multiple NICs\n"
423 " -h print this info\n"
424 " prog BPF program file name. Name defaults to\n"
425 " hbm_out_kern.o\n");
426}
427
428int main(int argc, char **argv)
429{
430 char *prog = "hbm_out_kern.o";
431 int k;
432 int cg_id = 1;
433 char *optstring = "iodln:r:st:wh";
brakmoffd81552019-05-28 16:59:39 -0700434 struct option loptions[] = {
435 {"no_cn", 0, NULL, 1},
436 {NULL, 0, NULL, 0}
437 };
brakmoa1270fe2019-03-01 12:38:49 -0800438
brakmoffd81552019-05-28 16:59:39 -0700439 while ((k = getopt_long(argc, argv, optstring, loptions, NULL)) != -1) {
brakmoa1270fe2019-03-01 12:38:49 -0800440 switch (k) {
brakmoffd81552019-05-28 16:59:39 -0700441 case 1:
442 no_cn_flag = true;
443 break;
brakmoa1270fe2019-03-01 12:38:49 -0800444 case'o':
445 break;
446 case 'd':
447 debugFlag = true;
448 break;
449 case 'l':
450 loopback_flag = true;
451 break;
452 case 'n':
453 cg_id = atoi(optarg);
454 break;
455 case 'r':
456 minRate = atoi(optarg) * 1.024;
457 rate = minRate;
458 break;
459 case 's':
460 stats_flag = true;
461 break;
462 case 't':
463 dur = atoi(optarg);
464 break;
465 case 'w':
466 work_conserving_flag = true;
467 break;
468 case '?':
469 if (optopt == 'n' || optopt == 'r' || optopt == 't')
470 fprintf(stderr,
471 "Option -%c requires an argument.\n\n",
472 optopt);
473 case 'h':
474 // fallthrough
475 default:
476 Usage();
477 return 0;
478 }
479 }
480
481 if (optind < argc)
482 prog = argv[optind];
483 printf("HBM prog: %s\n", prog != NULL ? prog : "NULL");
484
485 return run_bpf_prog(prog, cg_id);
486}