Linux Audio

Check our new training course

Loading...
Note: File does not exist in v4.6.
  1/* SPDX-License-Identifier: GPL-2.0
  2 * Copyright (c) 2017 Jesper Dangaard Brouer, Red Hat Inc.
  3 */
  4static const char *__doc__ = " XDP RX-queue info extract example\n\n"
  5	"Monitor how many packets per sec (pps) are received\n"
  6	"per NIC RX queue index and which CPU processed the packet\n"
  7	;
  8
  9#include <errno.h>
 10#include <signal.h>
 11#include <stdio.h>
 12#include <stdlib.h>
 13#include <stdbool.h>
 14#include <string.h>
 15#include <unistd.h>
 16#include <locale.h>
 17#include <sys/resource.h>
 18#include <getopt.h>
 19#include <net/if.h>
 20#include <time.h>
 21
 22#include <arpa/inet.h>
 23#include <linux/if_link.h>
 24
 25#include "libbpf.h"
 26#include "bpf_load.h"
 27#include "bpf_util.h"
 28
 29static int ifindex = -1;
 30static char ifname_buf[IF_NAMESIZE];
 31static char *ifname;
 32
 33static __u32 xdp_flags;
 34
 35/* Exit return codes */
 36#define EXIT_OK		0
 37#define EXIT_FAIL		1
 38#define EXIT_FAIL_OPTION	2
 39#define EXIT_FAIL_XDP		3
 40#define EXIT_FAIL_BPF		4
 41#define EXIT_FAIL_MEM		5
 42
 43static const struct option long_options[] = {
 44	{"help",	no_argument,		NULL, 'h' },
 45	{"dev",		required_argument,	NULL, 'd' },
 46	{"skb-mode",	no_argument,		NULL, 'S' },
 47	{"sec",		required_argument,	NULL, 's' },
 48	{"no-separators", no_argument,		NULL, 'z' },
 49	{"action",	required_argument,	NULL, 'a' },
 50	{0, 0, NULL,  0 }
 51};
 52
 53static void int_exit(int sig)
 54{
 55	fprintf(stderr,
 56		"Interrupted: Removing XDP program on ifindex:%d device:%s\n",
 57		ifindex, ifname);
 58	if (ifindex > -1)
 59		bpf_set_link_xdp_fd(ifindex, -1, xdp_flags);
 60	exit(EXIT_OK);
 61}
 62
 63struct config {
 64	__u32 action;
 65	int ifindex;
 66};
 67#define XDP_ACTION_MAX (XDP_TX + 1)
 68#define XDP_ACTION_MAX_STRLEN 11
 69static const char *xdp_action_names[XDP_ACTION_MAX] = {
 70	[XDP_ABORTED]	= "XDP_ABORTED",
 71	[XDP_DROP]	= "XDP_DROP",
 72	[XDP_PASS]	= "XDP_PASS",
 73	[XDP_TX]	= "XDP_TX",
 74};
 75
 76static const char *action2str(int action)
 77{
 78	if (action < XDP_ACTION_MAX)
 79		return xdp_action_names[action];
 80	return NULL;
 81}
 82
 83static int parse_xdp_action(char *action_str)
 84{
 85	size_t maxlen;
 86	__u64 action = -1;
 87	int i;
 88
 89	for (i = 0; i < XDP_ACTION_MAX; i++) {
 90		maxlen = XDP_ACTION_MAX_STRLEN;
 91		if (strncmp(xdp_action_names[i], action_str, maxlen) == 0) {
 92			action = i;
 93			break;
 94		}
 95	}
 96	return action;
 97}
 98
 99static void list_xdp_actions(void)
100{
101	int i;
102
103	printf("Available XDP --action <options>\n");
104	for (i = 0; i < XDP_ACTION_MAX; i++)
105		printf("\t%s\n", xdp_action_names[i]);
106	printf("\n");
107}
108
109static void usage(char *argv[])
110{
111	int i;
112
113	printf("\nDOCUMENTATION:\n%s\n", __doc__);
114	printf(" Usage: %s (options-see-below)\n", argv[0]);
115	printf(" Listing options:\n");
116	for (i = 0; long_options[i].name != 0; i++) {
117		printf(" --%-12s", long_options[i].name);
118		if (long_options[i].flag != NULL)
119			printf(" flag (internal value:%d)",
120				*long_options[i].flag);
121		else
122			printf(" short-option: -%c",
123				long_options[i].val);
124		printf("\n");
125	}
126	printf("\n");
127	list_xdp_actions();
128}
129
130#define NANOSEC_PER_SEC 1000000000 /* 10^9 */
131static __u64 gettime(void)
132{
133	struct timespec t;
134	int res;
135
136	res = clock_gettime(CLOCK_MONOTONIC, &t);
137	if (res < 0) {
138		fprintf(stderr, "Error with gettimeofday! (%i)\n", res);
139		exit(EXIT_FAIL);
140	}
141	return (__u64) t.tv_sec * NANOSEC_PER_SEC + t.tv_nsec;
142}
143
144/* Common stats data record shared with _kern.c */
145struct datarec {
146	__u64 processed;
147	__u64 issue;
148};
149struct record {
150	__u64 timestamp;
151	struct datarec total;
152	struct datarec *cpu;
153};
154struct stats_record {
155	struct record stats;
156	struct record *rxq;
157};
158
159static struct datarec *alloc_record_per_cpu(void)
160{
161	unsigned int nr_cpus = bpf_num_possible_cpus();
162	struct datarec *array;
163	size_t size;
164
165	size = sizeof(struct datarec) * nr_cpus;
166	array = malloc(size);
167	memset(array, 0, size);
168	if (!array) {
169		fprintf(stderr, "Mem alloc error (nr_cpus:%u)\n", nr_cpus);
170		exit(EXIT_FAIL_MEM);
171	}
172	return array;
173}
174
175static struct record *alloc_record_per_rxq(void)
176{
177	unsigned int nr_rxqs = map_data[2].def.max_entries;
178	struct record *array;
179	size_t size;
180
181	size = sizeof(struct record) * nr_rxqs;
182	array = malloc(size);
183	memset(array, 0, size);
184	if (!array) {
185		fprintf(stderr, "Mem alloc error (nr_rxqs:%u)\n", nr_rxqs);
186		exit(EXIT_FAIL_MEM);
187	}
188	return array;
189}
190
191static struct stats_record *alloc_stats_record(void)
192{
193	unsigned int nr_rxqs = map_data[2].def.max_entries;
194	struct stats_record *rec;
195	int i;
196
197	rec = malloc(sizeof(*rec));
198	memset(rec, 0, sizeof(*rec));
199	if (!rec) {
200		fprintf(stderr, "Mem alloc error\n");
201		exit(EXIT_FAIL_MEM);
202	}
203	rec->rxq = alloc_record_per_rxq();
204	for (i = 0; i < nr_rxqs; i++)
205		rec->rxq[i].cpu = alloc_record_per_cpu();
206
207	rec->stats.cpu = alloc_record_per_cpu();
208	return rec;
209}
210
211static void free_stats_record(struct stats_record *r)
212{
213	unsigned int nr_rxqs = map_data[2].def.max_entries;
214	int i;
215
216	for (i = 0; i < nr_rxqs; i++)
217		free(r->rxq[i].cpu);
218
219	free(r->rxq);
220	free(r->stats.cpu);
221	free(r);
222}
223
224static bool map_collect_percpu(int fd, __u32 key, struct record *rec)
225{
226	/* For percpu maps, userspace gets a value per possible CPU */
227	unsigned int nr_cpus = bpf_num_possible_cpus();
228	struct datarec values[nr_cpus];
229	__u64 sum_processed = 0;
230	__u64 sum_issue = 0;
231	int i;
232
233	if ((bpf_map_lookup_elem(fd, &key, values)) != 0) {
234		fprintf(stderr,
235			"ERR: bpf_map_lookup_elem failed key:0x%X\n", key);
236		return false;
237	}
238	/* Get time as close as possible to reading map contents */
239	rec->timestamp = gettime();
240
241	/* Record and sum values from each CPU */
242	for (i = 0; i < nr_cpus; i++) {
243		rec->cpu[i].processed = values[i].processed;
244		sum_processed        += values[i].processed;
245		rec->cpu[i].issue = values[i].issue;
246		sum_issue        += values[i].issue;
247	}
248	rec->total.processed = sum_processed;
249	rec->total.issue     = sum_issue;
250	return true;
251}
252
253static void stats_collect(struct stats_record *rec)
254{
255	int fd, i, max_rxqs;
256
257	fd = map_data[1].fd; /* map: stats_global_map */
258	map_collect_percpu(fd, 0, &rec->stats);
259
260	fd = map_data[2].fd; /* map: rx_queue_index_map */
261	max_rxqs = map_data[2].def.max_entries;
262	for (i = 0; i < max_rxqs; i++)
263		map_collect_percpu(fd, i, &rec->rxq[i]);
264}
265
266static double calc_period(struct record *r, struct record *p)
267{
268	double period_ = 0;
269	__u64 period = 0;
270
271	period = r->timestamp - p->timestamp;
272	if (period > 0)
273		period_ = ((double) period / NANOSEC_PER_SEC);
274
275	return period_;
276}
277
278static __u64 calc_pps(struct datarec *r, struct datarec *p, double period_)
279{
280	__u64 packets = 0;
281	__u64 pps = 0;
282
283	if (period_ > 0) {
284		packets = r->processed - p->processed;
285		pps = packets / period_;
286	}
287	return pps;
288}
289
290static __u64 calc_errs_pps(struct datarec *r,
291			    struct datarec *p, double period_)
292{
293	__u64 packets = 0;
294	__u64 pps = 0;
295
296	if (period_ > 0) {
297		packets = r->issue - p->issue;
298		pps = packets / period_;
299	}
300	return pps;
301}
302
303static void stats_print(struct stats_record *stats_rec,
304			struct stats_record *stats_prev,
305			int action)
306{
307	unsigned int nr_cpus = bpf_num_possible_cpus();
308	unsigned int nr_rxqs = map_data[2].def.max_entries;
309	double pps = 0, err = 0;
310	struct record *rec, *prev;
311	double t;
312	int rxq;
313	int i;
314
315	/* Header */
316	printf("\nRunning XDP on dev:%s (ifindex:%d) action:%s\n",
317	       ifname, ifindex, action2str(action));
318
319	/* stats_global_map */
320	{
321		char *fmt_rx = "%-15s %-7d %'-11.0f %'-10.0f %s\n";
322		char *fm2_rx = "%-15s %-7s %'-11.0f\n";
323		char *errstr = "";
324
325		printf("%-15s %-7s %-11s %-11s\n",
326		       "XDP stats", "CPU", "pps", "issue-pps");
327
328		rec  =  &stats_rec->stats;
329		prev = &stats_prev->stats;
330		t = calc_period(rec, prev);
331		for (i = 0; i < nr_cpus; i++) {
332			struct datarec *r = &rec->cpu[i];
333			struct datarec *p = &prev->cpu[i];
334
335			pps = calc_pps     (r, p, t);
336			err = calc_errs_pps(r, p, t);
337			if (err > 0)
338				errstr = "invalid-ifindex";
339			if (pps > 0)
340				printf(fmt_rx, "XDP-RX CPU",
341					i, pps, err, errstr);
342		}
343		pps  = calc_pps     (&rec->total, &prev->total, t);
344		err  = calc_errs_pps(&rec->total, &prev->total, t);
345		printf(fm2_rx, "XDP-RX CPU", "total", pps, err);
346	}
347
348	/* rx_queue_index_map */
349	printf("\n%-15s %-7s %-11s %-11s\n",
350	       "RXQ stats", "RXQ:CPU", "pps", "issue-pps");
351
352	for (rxq = 0; rxq < nr_rxqs; rxq++) {
353		char *fmt_rx = "%-15s %3d:%-3d %'-11.0f %'-10.0f %s\n";
354		char *fm2_rx = "%-15s %3d:%-3s %'-11.0f\n";
355		char *errstr = "";
356		int rxq_ = rxq;
357
358		/* Last RXQ in map catch overflows */
359		if (rxq_ == nr_rxqs - 1)
360			rxq_ = -1;
361
362		rec  =  &stats_rec->rxq[rxq];
363		prev = &stats_prev->rxq[rxq];
364		t = calc_period(rec, prev);
365		for (i = 0; i < nr_cpus; i++) {
366			struct datarec *r = &rec->cpu[i];
367			struct datarec *p = &prev->cpu[i];
368
369			pps = calc_pps     (r, p, t);
370			err = calc_errs_pps(r, p, t);
371			if (err > 0) {
372				if (rxq_ == -1)
373					errstr = "map-overflow-RXQ";
374				else
375					errstr = "err";
376			}
377			if (pps > 0)
378				printf(fmt_rx, "rx_queue_index",
379				       rxq_, i, pps, err, errstr);
380		}
381		pps  = calc_pps     (&rec->total, &prev->total, t);
382		err  = calc_errs_pps(&rec->total, &prev->total, t);
383		if (pps || err)
384			printf(fm2_rx, "rx_queue_index", rxq_, "sum", pps, err);
385	}
386}
387
388
389/* Pointer swap trick */
390static inline void swap(struct stats_record **a, struct stats_record **b)
391{
392	struct stats_record *tmp;
393
394	tmp = *a;
395	*a = *b;
396	*b = tmp;
397}
398
399static void stats_poll(int interval, int action)
400{
401	struct stats_record *record, *prev;
402
403	record = alloc_stats_record();
404	prev   = alloc_stats_record();
405	stats_collect(record);
406
407	while (1) {
408		swap(&prev, &record);
409		stats_collect(record);
410		stats_print(record, prev, action);
411		sleep(interval);
412	}
413
414	free_stats_record(record);
415	free_stats_record(prev);
416}
417
418
419int main(int argc, char **argv)
420{
421	struct rlimit r = {10 * 1024 * 1024, RLIM_INFINITY};
422	bool use_separators = true;
423	struct config cfg = { 0 };
424	char filename[256];
425	int longindex = 0;
426	int interval = 2;
427	__u32 key = 0;
428	int opt, err;
429
430	char action_str_buf[XDP_ACTION_MAX_STRLEN + 1 /* for \0 */] = { 0 };
431	int action = XDP_PASS; /* Default action */
432	char *action_str = NULL;
433
434	snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]);
435
436	if (setrlimit(RLIMIT_MEMLOCK, &r)) {
437		perror("setrlimit(RLIMIT_MEMLOCK)");
438		return 1;
439	}
440
441	if (load_bpf_file(filename)) {
442		fprintf(stderr, "ERR in load_bpf_file(): %s", bpf_log_buf);
443		return EXIT_FAIL;
444	}
445
446	if (!prog_fd[0]) {
447		fprintf(stderr, "ERR: load_bpf_file: %s\n", strerror(errno));
448		return EXIT_FAIL;
449	}
450
451	/* Parse commands line args */
452	while ((opt = getopt_long(argc, argv, "hSd:",
453				  long_options, &longindex)) != -1) {
454		switch (opt) {
455		case 'd':
456			if (strlen(optarg) >= IF_NAMESIZE) {
457				fprintf(stderr, "ERR: --dev name too long\n");
458				goto error;
459			}
460			ifname = (char *)&ifname_buf;
461			strncpy(ifname, optarg, IF_NAMESIZE);
462			ifindex = if_nametoindex(ifname);
463			if (ifindex == 0) {
464				fprintf(stderr,
465					"ERR: --dev name unknown err(%d):%s\n",
466					errno, strerror(errno));
467				goto error;
468			}
469			break;
470		case 's':
471			interval = atoi(optarg);
472			break;
473		case 'S':
474			xdp_flags |= XDP_FLAGS_SKB_MODE;
475			break;
476		case 'z':
477			use_separators = false;
478			break;
479		case 'a':
480			action_str = (char *)&action_str_buf;
481			strncpy(action_str, optarg, XDP_ACTION_MAX_STRLEN);
482			break;
483		case 'h':
484		error:
485		default:
486			usage(argv);
487			return EXIT_FAIL_OPTION;
488		}
489	}
490	/* Required option */
491	if (ifindex == -1) {
492		fprintf(stderr, "ERR: required option --dev missing\n");
493		usage(argv);
494		return EXIT_FAIL_OPTION;
495	}
496	cfg.ifindex = ifindex;
497
498	/* Parse action string */
499	if (action_str) {
500		action = parse_xdp_action(action_str);
501		if (action < 0) {
502			fprintf(stderr, "ERR: Invalid XDP --action: %s\n",
503				action_str);
504			list_xdp_actions();
505			return EXIT_FAIL_OPTION;
506		}
507	}
508	cfg.action = action;
509
510	/* Trick to pretty printf with thousands separators use %' */
511	if (use_separators)
512		setlocale(LC_NUMERIC, "en_US");
513
514	/* User-side setup ifindex in config_map */
515	err = bpf_map_update_elem(map_fd[0], &key, &cfg, 0);
516	if (err) {
517		fprintf(stderr, "Store config failed (err:%d)\n", err);
518		exit(EXIT_FAIL_BPF);
519	}
520
521	/* Remove XDP program when program is interrupted */
522	signal(SIGINT, int_exit);
523
524	if (bpf_set_link_xdp_fd(ifindex, prog_fd[0], xdp_flags) < 0) {
525		fprintf(stderr, "link set xdp fd failed\n");
526		return EXIT_FAIL_XDP;
527	}
528
529	stats_poll(interval, action);
530	return EXIT_OK;
531}