Skip to content

Commit 39be10c

Browse files
committed
basic04: add a solution for assignment 1
Add a solution for the first assignment of the basic04 lesson. Namely, copy the xdp_stats program from the basic04 to basic-solutions and extend it to reset stats if a new map ID is detected. Signed-off-by: Anton Protopopov <a.s.protopopov@gmail.com>
1 parent 9836914 commit 39be10c

File tree

3 files changed

+307
-1
lines changed

3 files changed

+307
-1
lines changed

basic-solutions/Makefile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
2+
3+
USER_TARGETS := xdp_stats
4+
5+
LIBBPF_DIR = ../libbpf/src/
6+
COMMON_DIR = ../common/
7+
8+
# Extend with another COMMON_OBJS
9+
COMMON_OBJS += $(COMMON_DIR)/common_libbpf.o
10+
11+
include $(COMMON_DIR)/common.mk

basic-solutions/README.org

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ For a user-space application, only the former header is needed.
6363

6464
*** Assignment 1: (xdp_stats.c) reload map file-descriptor
6565

66-
TBD
66+
See the [[file:xdp_stats.c][xdp_stats.c]] program in this directory.
6767

6868
*** Assignment 2: (xdp_loader.c) reuse pinned map
6969

basic-solutions/xdp_stats.c

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
/* SPDX-License-Identifier: GPL-2.0 */
2+
static const char *__doc__ = "XDP stats program\n"
3+
" - Finding xdp_stats_map via --dev name info\n";
4+
5+
#include <stdio.h>
6+
#include <stdlib.h>
7+
#include <string.h>
8+
#include <errno.h>
9+
#include <getopt.h>
10+
11+
#include <locale.h>
12+
#include <unistd.h>
13+
#include <time.h>
14+
15+
#include <bpf/bpf.h>
16+
/* Lesson#1: this prog does not need to #include <bpf/libbpf.h> as it only uses
17+
* the simple bpf-syscall wrappers, defined in libbpf #include<bpf/bpf.h>
18+
*/
19+
20+
#include <net/if.h>
21+
#include <linux/if_link.h> /* depend on kernel-headers installed */
22+
23+
#include "../common/common_params.h"
24+
#include "../common/common_user_bpf_xdp.h"
25+
#include "../common/xdp_stats_kern_user.h"
26+
27+
#include "bpf_util.h" /* bpf_num_possible_cpus */
28+
29+
static const struct option_wrapper long_options[] = {
30+
{{"help", no_argument, NULL, 'h' },
31+
"Show help", false},
32+
33+
{{"dev", required_argument, NULL, 'd' },
34+
"Operate on device <ifname>", "<ifname>", true},
35+
36+
{{"quiet", no_argument, NULL, 'q' },
37+
"Quiet mode (no output)"},
38+
39+
{{0, 0, NULL, 0 }}
40+
};
41+
42+
#define NANOSEC_PER_SEC 1000000000 /* 10^9 */
43+
static __u64 gettime(void)
44+
{
45+
struct timespec t;
46+
int res;
47+
48+
res = clock_gettime(CLOCK_MONOTONIC, &t);
49+
if (res < 0) {
50+
fprintf(stderr, "Error with gettimeofday! (%i)\n", res);
51+
exit(EXIT_FAIL);
52+
}
53+
return (__u64) t.tv_sec * NANOSEC_PER_SEC + t.tv_nsec;
54+
}
55+
56+
struct record {
57+
__u64 timestamp;
58+
struct datarec total; /* defined in common_kern_user.h */
59+
};
60+
61+
struct stats_record {
62+
struct record stats[XDP_ACTION_MAX];
63+
};
64+
65+
static double calc_period(struct record *r, struct record *p)
66+
{
67+
double period_ = 0;
68+
__u64 period = 0;
69+
70+
period = r->timestamp - p->timestamp;
71+
if (period > 0)
72+
period_ = ((double) period / NANOSEC_PER_SEC);
73+
74+
return period_;
75+
}
76+
77+
static void stats_print_header()
78+
{
79+
/* Print stats "header" */
80+
printf("%-12s\n", "XDP-action");
81+
}
82+
83+
static void stats_print(struct stats_record *stats_rec,
84+
struct stats_record *stats_prev)
85+
{
86+
struct record *rec, *prev;
87+
__u64 packets, bytes;
88+
double period;
89+
double pps; /* packets per sec */
90+
double bps; /* bits per sec */
91+
int i;
92+
93+
stats_print_header(); /* Print stats "header" */
94+
95+
/* Print for each XDP actions stats */
96+
for (i = 0; i < XDP_ACTION_MAX; i++)
97+
{
98+
char *fmt = "%-12s %'11lld pkts (%'10.0f pps)"
99+
" %'11lld Kbytes (%'6.0f Mbits/s)"
100+
" period:%f\n";
101+
const char *action = action2str(i);
102+
103+
rec = &stats_rec->stats[i];
104+
prev = &stats_prev->stats[i];
105+
106+
period = calc_period(rec, prev);
107+
if (period == 0)
108+
return;
109+
110+
packets = rec->total.rx_packets - prev->total.rx_packets;
111+
pps = packets / period;
112+
113+
bytes = rec->total.rx_bytes - prev->total.rx_bytes;
114+
bps = (bytes * 8)/ period / 1000000;
115+
116+
printf(fmt, action, rec->total.rx_packets, pps,
117+
rec->total.rx_bytes / 1000 , bps,
118+
period);
119+
}
120+
printf("\n");
121+
}
122+
123+
124+
/* BPF_MAP_TYPE_ARRAY */
125+
void map_get_value_array(int fd, __u32 key, struct datarec *value)
126+
{
127+
if ((bpf_map_lookup_elem(fd, &key, value)) != 0) {
128+
fprintf(stderr,
129+
"ERR: bpf_map_lookup_elem failed key:0x%X\n", key);
130+
}
131+
}
132+
133+
/* BPF_MAP_TYPE_PERCPU_ARRAY */
134+
void map_get_value_percpu_array(int fd, __u32 key, struct datarec *value)
135+
{
136+
/* For percpu maps, userspace gets a value per possible CPU */
137+
unsigned int nr_cpus = bpf_num_possible_cpus();
138+
struct datarec values[nr_cpus];
139+
__u64 sum_bytes = 0;
140+
__u64 sum_pkts = 0;
141+
int i;
142+
143+
if ((bpf_map_lookup_elem(fd, &key, values)) != 0) {
144+
fprintf(stderr,
145+
"ERR: bpf_map_lookup_elem failed key:0x%X\n", key);
146+
return;
147+
}
148+
149+
/* Sum values from each CPU */
150+
for (i = 0; i < nr_cpus; i++) {
151+
sum_pkts += values[i].rx_packets;
152+
sum_bytes += values[i].rx_bytes;
153+
}
154+
value->rx_packets = sum_pkts;
155+
value->rx_bytes = sum_bytes;
156+
}
157+
158+
static bool map_collect(int fd, __u32 map_type, __u32 key, struct record *rec)
159+
{
160+
struct datarec value;
161+
162+
/* Get time as close as possible to reading map contents */
163+
rec->timestamp = gettime();
164+
165+
switch (map_type) {
166+
case BPF_MAP_TYPE_ARRAY:
167+
map_get_value_array(fd, key, &value);
168+
break;
169+
case BPF_MAP_TYPE_PERCPU_ARRAY:
170+
map_get_value_percpu_array(fd, key, &value);
171+
break;
172+
default:
173+
fprintf(stderr, "ERR: Unknown map_type(%u) cannot handle\n",
174+
map_type);
175+
return false;
176+
break;
177+
}
178+
179+
rec->total.rx_packets = value.rx_packets;
180+
rec->total.rx_bytes = value.rx_bytes;
181+
return true;
182+
}
183+
184+
static void stats_collect(int map_fd, __u32 map_type,
185+
struct stats_record *stats_rec)
186+
{
187+
/* Collect all XDP actions stats */
188+
__u32 key;
189+
190+
for (key = 0; key < XDP_ACTION_MAX; key++) {
191+
map_collect(map_fd, map_type, key, &stats_rec->stats[key]);
192+
}
193+
}
194+
195+
static int stats_poll(const char *pin_dir, int map_fd, __u32 id,
196+
__u32 map_type, int interval)
197+
{
198+
struct bpf_map_info info = {};
199+
struct stats_record prev, record = { 0 };
200+
201+
/* Trick to pretty printf with thousands separators use %' */
202+
setlocale(LC_NUMERIC, "en_US");
203+
204+
/* Get initial reading quickly */
205+
stats_collect(map_fd, map_type, &record);
206+
usleep(1000000/4);
207+
208+
while (1) {
209+
prev = record; /* struct copy */
210+
211+
map_fd = open_bpf_map_file(pin_dir, "xdp_stats_map", &info);
212+
if (map_fd < 0) {
213+
return EXIT_FAIL_BPF;
214+
} else if (id != info.id) {
215+
printf("BPF map xdp_stats_map changed its ID, restarting\n");
216+
return 0;
217+
}
218+
219+
stats_collect(map_fd, map_type, &record);
220+
stats_print(&record, &prev);
221+
sleep(interval);
222+
}
223+
224+
return 0;
225+
}
226+
227+
#ifndef PATH_MAX
228+
#define PATH_MAX 4096
229+
#endif
230+
231+
const char *pin_basedir = "/sys/fs/bpf";
232+
233+
int main(int argc, char **argv)
234+
{
235+
const struct bpf_map_info map_expect = {
236+
.key_size = sizeof(__u32),
237+
.value_size = sizeof(struct datarec),
238+
.max_entries = XDP_ACTION_MAX,
239+
};
240+
struct bpf_map_info info = { 0 };
241+
char pin_dir[PATH_MAX];
242+
int stats_map_fd;
243+
int interval = 2;
244+
int len, err;
245+
246+
struct config cfg = {
247+
.ifindex = -1,
248+
.do_unload = false,
249+
};
250+
251+
/* Cmdline options can change progsec */
252+
parse_cmdline_args(argc, argv, long_options, &cfg, __doc__);
253+
254+
/* Required option */
255+
if (cfg.ifindex == -1) {
256+
fprintf(stderr, "ERR: required option --dev missing\n\n");
257+
usage(argv[0], __doc__, long_options, (argc == 1));
258+
return EXIT_FAIL_OPTION;
259+
}
260+
261+
/* Use the --dev name as subdir for finding pinned maps */
262+
len = snprintf(pin_dir, PATH_MAX, "%s/%s", pin_basedir, cfg.ifname);
263+
if (len < 0) {
264+
fprintf(stderr, "ERR: creating pin dirname\n");
265+
return EXIT_FAIL_OPTION;
266+
}
267+
268+
for ( ;; ) {
269+
stats_map_fd = open_bpf_map_file(pin_dir, "xdp_stats_map", &info);
270+
if (stats_map_fd < 0) {
271+
return EXIT_FAIL_BPF;
272+
}
273+
274+
/* check map info, e.g. datarec is expected size */
275+
err = check_map_fd_info(&info, &map_expect);
276+
if (err) {
277+
fprintf(stderr, "ERR: map via FD not compatible\n");
278+
return err;
279+
}
280+
if (verbose) {
281+
printf("\nCollecting stats from BPF map\n");
282+
printf(" - BPF map (bpf_map_type:%d) id:%d name:%s"
283+
" key_size:%d value_size:%d max_entries:%d\n",
284+
info.type, info.id, info.name,
285+
info.key_size, info.value_size, info.max_entries
286+
);
287+
}
288+
289+
err = stats_poll(pin_dir, stats_map_fd, info.id, info.type, interval);
290+
if (err < 0)
291+
return err;
292+
}
293+
294+
return EXIT_OK;
295+
}

0 commit comments

Comments
 (0)