diff --git a/samples/bpf/xdp_redirect_cpu_user.c b/samples/bpf/xdp_redirect_cpu_user.c
index 3983ed71d879..4c9f32229508 100644
--- a/samples/bpf/xdp_redirect_cpu_user.c
+++ b/samples/bpf/xdp_redirect_cpu_user.c
@@ -216,7 +216,7 @@ static void __stats_poll(int interval, bool use_separators, char *prog_name,
 	for (;;) {
 		swap(&prev, &record);
 		sample_stats_collect(mask, record);
-		sample_stats_print(mask, record, prev, NULL);
+		sample_stats_print(mask, record, prev, NULL, interval);
 		/* Depends on SAMPLE_CPUMAP_KTHREAD_CNT */
 		sample_stats_print_cpumap_remote(record, prev,
 						 bpf_num_possible_cpus(),
diff --git a/samples/bpf/xdp_redirect_map_user.c b/samples/bpf/xdp_redirect_map_user.c
index b2c7adad99ec..ed53dd2cd93a 100644
--- a/samples/bpf/xdp_redirect_map_user.c
+++ b/samples/bpf/xdp_redirect_map_user.c
@@ -75,7 +75,7 @@ static void usage(const char *prog)
 
 int main(int argc, char **argv)
 {
-	int mask = SAMPLE_RX_CNT | SAMPLE_REDIRECT_ERR_CNT |
+	int mask = SAMPLE_RX_CNT | SAMPLE_REDIRECT_ERR_MAP_CNT |
 		   SAMPLE_EXCEPTION_CNT | SAMPLE_DEVMAP_XMIT_CNT;
 	struct bpf_prog_load_attr prog_load_attr = {
 		.prog_type	= BPF_PROG_TYPE_UNSPEC,
diff --git a/samples/bpf/xdp_sample_user.c b/samples/bpf/xdp_sample_user.c
index d0b26023f1db..909257ffe54c 100644
--- a/samples/bpf/xdp_sample_user.c
+++ b/samples/bpf/xdp_sample_user.c
@@ -26,8 +26,10 @@
 #define SIOCETHTOOL 0x8946
 #endif
 
+#include <fcntl.h>
 #include <arpa/inet.h>
 #include <linux/if_link.h>
+#include <sys/utsname.h>
 
 #include <bpf/bpf.h>
 #include <bpf/libbpf.h>
@@ -39,6 +41,7 @@ struct bpf_link *tp_links[NUM_TP] = {};
 int map_fds[NUM_MAP], tp_cnt, n_cpus;
 static int sample_sig_fd;
 enum log_level sample_log_level = LL_SIMPLE;
+static struct sample_output sum_out;
 static bool err_exp;
 
 #define __sample_print(fmt, cond, printer, ...)                                \
@@ -58,6 +61,9 @@ static bool err_exp;
 	})
 #define print_err(err, fmt, ...) __print_err(err, fmt, printf, ##__VA_ARGS__)
 
+#define print_link_err(err, str, width, type)                                  \
+	__print_err(err, str, print_link, width, type)
+
 #define __COLUMN(x) "%'10" x " %-13s"
 #define FMT_COLUMNf __COLUMN(".0f")
 #define FMT_COLUMNd __COLUMN("d")
@@ -71,6 +77,66 @@ static bool err_exp;
 #define PASS(pass) pass, "pass/s"
 #define REDIR(redir) redir, "redir/s"
 
+static const char *elixir_search[NUM_TP] = {
+	[TP_REDIRECT_CNT] = "_trace_xdp_redirect",
+	[TP_REDIRECT_MAP_CNT] = "_trace_xdp_redirect_map",
+	[TP_REDIRECT_ERR_CNT] = "_trace_xdp_redirect_err",
+	[TP_REDIRECT_MAP_ERR_CNT] = "_trace_xdp_redirect_map_err",
+	[TP_CPUMAP_ENQUEUE_CNT] = "trace_xdp_cpumap_enqueue",
+	[TP_CPUMAP_KTHREAD_CNT] = "trace_xdp_cpumap_kthread",
+	[TP_EXCEPTION_CNT] = "trace_xdp_exception",
+	[TP_DEVMAP_XMIT_CNT] = "trace_xdp_devmap_xmit",
+};
+
+static const char *make_url(enum tp_type i)
+{
+	const char *key = elixir_search[i];
+	static struct utsname uts = {};
+	static char url[128];
+	static bool uts_init;
+	int maj, min;
+	char c[2];
+
+	if (!uts_init) {
+		if (uname(&uts) < 0)
+			return NULL;
+		uts_init = true;
+	}
+
+	if (!key || sscanf(uts.release, "%d.%d%1s", &maj, &min, c) != 3)
+		return NULL;
+
+	snprintf(url, sizeof(url), "https://elixir.bootlin.com/linux/v%d.%d/C/ident/%s",
+		 maj, min, key);
+
+	return url;
+}
+
+static void print_link(const char *str, int width, enum tp_type i)
+{
+	static int t = -1;
+	const char *s;
+	int fd, l;
+
+	if (t < 0) {
+		fd = open("/proc/self/fd/1", O_RDONLY);
+		if (fd < 0)
+			return;
+		t = isatty(fd);
+		close(fd);
+	}
+
+	s = make_url(i);
+	if (!s || !t) {
+		printf("  %-*s", width, str);
+		return;
+	}
+
+	l = strlen(str);
+	width = width - l > 0 ? width - l : 0;
+	printf("  \x1B]8;;%s\a%s\x1B]8;;\a%*c", s, str, width, ' ');
+}
+
 #define NANOSEC_PER_SEC 1000000000 /* 10^9 */
 static __u64 gettime(void)
 {
@@ -333,8 +399,11 @@ static void stats_get_cpumap_enqueue(struct stats_record *stats_rec,
 
 			if (err > 0)
 				err = pps / err; /* calc average bulk size */
-			print_default("  %-20s " FMT_COLUMNf FMT_COLUMNf __COLUMN(".2f") "\n",
-				      str, PPS(pps), DROP(drop), err, "bulk_avg");
+
+			print_link_err(drop, str, 20, TP_CPUMAP_ENQUEUE_CNT);
+			print_err(drop,
+				  " " FMT_COLUMNf FMT_COLUMNf __COLUMN(".2f") "\n",
+				  PPS(pps), DROP(drop), err, "bulk_avg");
 		}
 
 		for (i = 0; i < nr_cpus; i++) {
@@ -375,8 +444,9 @@ static void stats_get_cpumap_kthread(struct stats_record *stats_rec,
 	drop = calc_drop_pps(&rec->total, &prev->total, t);
 	err = calc_errs_pps(&rec->total, &prev->total, t);
 
-	print_default("  %-20s " FMT_COLUMNf FMT_COLUMNf FMT_COLUMNf "\n", "kthread total",
-		      PPS(pps), DROP(drop), err, "sched");
+	print_link_err(drop, pps ? "kthread total" : "kthread", 20, TP_CPUMAP_KTHREAD_CNT);
+	print_err(drop, " " FMT_COLUMNf FMT_COLUMNf FMT_COLUMNf "\n",
+			  PPS(pps), DROP(drop), err, "sched");
 
 	for (i = 0; i < nr_cpus; i++) {
 		struct datarec *r = &rec->cpu[i];
@@ -632,7 +702,9 @@ static void stats_print(const char *prefix, int mask, struct stats_record *r,
 
 	if (mask & SAMPLE_REDIRECT_CNT) {
 		str = out->redir_cnt.suc ? "redirect total" : "redirect";
-		print_default("  %-20s " FMT_COLUMNl "\n", str, REDIR(out->redir_cnt.suc));
+		print_link_err(0, str, 20, mask & _SAMPLE_REDIRECT_MAP ?
+				TP_REDIRECT_MAP_CNT : TP_REDIRECT_CNT);
+		print_default(" " FMT_COLUMNl "\n", REDIR(out->redir_cnt.suc));
 
 		stats_get_redirect_cnt(r, p, nr_cpus, NULL);
 	}
@@ -640,6 +712,8 @@ static void stats_print(const char *prefix, int mask, struct stats_record *r,
 	if (mask & SAMPLE_REDIRECT_ERR_CNT) {
 		str = (sample_log_level & LL_DEFAULT) && out->redir_cnt.err ?
 			"redirect_err total" : "redirect_err";
+		print_link_err(out->redir_cnt.err, str, 20, mask & _SAMPLE_REDIRECT_MAP ?
+			       TP_REDIRECT_MAP_ERR_CNT : TP_REDIRECT_ERR_CNT);
 		print_err(out->redir_cnt.err, "  %-20s " FMT_COLUMNl "\n", str,
 			  ERR(out->redir_cnt.err));
 
@@ -648,8 +722,9 @@ static void stats_print(const char *prefix, int mask, struct stats_record *r,
 
 	if (mask & SAMPLE_EXCEPTION_CNT) {
 		str = out->except_cnt.hits ? "xdp_exception total" : "xdp_exception";
-		print_err(out->except_cnt.hits, "  %-20s " FMT_COLUMNl "\n",
-			  str, HITS(out->except_cnt.hits));
+
+		print_link_err(out->except_cnt.hits, str, 20, TP_EXCEPTION_CNT);
+		print_err(out->except_cnt.hits, " " FMT_COLUMNl "\n", HITS(out->except_cnt.hits));
 
 		stats_get_exception_cnt(r, p, nr_cpus, NULL);
 	}
@@ -657,9 +732,11 @@ static void stats_print(const char *prefix, int mask, struct stats_record *r,
 	if (mask & SAMPLE_DEVMAP_XMIT_CNT) {
 		str = (sample_log_level & LL_DEFAULT) && out->xmit_cnt.pps ?
 			"devmap_xmit total" : "devmap_xmit";
+
+		print_link_err(out->xmit_cnt.err, str, 20, TP_DEVMAP_XMIT_CNT);
 		print_err(out->xmit_cnt.err,
-			  "  %-20s " FMT_COLUMNl FMT_COLUMNl FMT_COLUMNl __COLUMN(".2f") "\n",
-			  str, XMIT(out->xmit_cnt.pps), DROP(out->xmit_cnt.drop),
+			  " " FMT_COLUMNl FMT_COLUMNl FMT_COLUMNl __COLUMN(".2f") "\n",
+			  XMIT(out->xmit_cnt.pps), DROP(out->xmit_cnt.drop),
 			  out->xmit_cnt.err, "drv_err/s", out->xmit_cnt.bavg, "bulk_avg");
 
 		stats_get_devmap_xmit(r, p, nr_cpus, NULL);
@@ -747,7 +824,7 @@ void sample_exit(int status)
 {
 	while (tp_cnt)
 		bpf_link__destroy(tp_links[--tp_cnt]);
-
+	sample_summary_print();
 	close(sample_sig_fd);
 	exit(status);
 }
@@ -783,8 +860,46 @@ void sample_stats_collect(int mask, struct stats_record *rec)
 		map_collect_percpu(map_fds[DEVMAP_XMIT_CNT], 0, &rec->devmap_xmit);
 }
 
+void sample_summary_update(struct sample_output *out, int interval)
+{
+	sum_out.totals.rx += out->totals.rx;
+	sum_out.totals.redir += out->totals.redir;
+	sum_out.totals.drop += out->totals.drop;
+	sum_out.totals.err += out->totals.err;
+	sum_out.totals.xmit += out->totals.xmit;
+	sum_out.rx_cnt.pps += interval;
+}
+
+void sample_summary_print(void)
+{
+	double period = sum_out.rx_cnt.pps;
+
+	print_always("\nTotals\n");
+	if (sum_out.totals.rx) {
+		double pkts = sum_out.totals.rx;
+
+		print_always("  Packets received    : %'-10llu\n", sum_out.totals.rx);
+		print_always("  Average packets/s   : %'-10.0f\n", sample_round(pkts/period));
+	}
+	if (sum_out.totals.redir) {
+		double pkts = sum_out.totals.redir;
+
+		print_always("  Packets redirected  : %'-10llu\n", sum_out.totals.redir);
+		print_always("  Average redir/s     : %'-10.0f\n", sample_round(pkts/period));
+	}
+	print_always("  Packets dropped     : %'-10llu\n", sum_out.totals.drop);
+	print_always("  Errors recorded     : %'-10llu\n", sum_out.totals.err);
+	if (sum_out.totals.xmit) {
+		double pkts = sum_out.totals.xmit;
+
+		print_always("  Packets transmitted : %'-10llu\n", sum_out.totals.xmit);
+		print_always("  Average transmit/s  : %'-10.0f\n", sample_round(pkts/period));
+	}
+}
+
 void sample_stats_print(int mask, struct stats_record *cur,
-			struct stats_record *prev, char *prog_name)
+			struct stats_record *prev, char *prog_name,
+			int interval)
 {
 	struct sample_output out = {};
 
@@ -803,6 +918,8 @@ void sample_stats_print(int mask, struct stats_record *cur,
 	if (mask & SAMPLE_DEVMAP_XMIT_CNT)
 		stats_get_devmap_xmit(cur, prev, 0, &out);
 
+	sample_summary_update(&out, interval);
+
 	stats_print(prog_name, mask, cur, prev, &out);
 }
 
@@ -821,7 +938,7 @@ void sample_stats_poll(int interval, int mask, char *prog_name, int use_separato
 	for (;;) {
 		swap(&prev, &record);
 		sample_stats_collect(mask, record);
-		sample_stats_print(mask, record, prev, prog_name);
+		sample_stats_print(mask, record, prev, prog_name, interval);
 		fflush(stdout);
 		sleep(interval);
 		sample_reset_mode();
diff --git a/samples/bpf/xdp_sample_user.h b/samples/bpf/xdp_sample_user.h
index 6ca934b346ef..abe4ec25c310 100644
--- a/samples/bpf/xdp_sample_user.h
+++ b/samples/bpf/xdp_sample_user.h
@@ -26,13 +26,16 @@ enum tp_type {
 };
 
 enum stats_mask {
-	SAMPLE_RX_CNT	        = 1U << 1,
-	SAMPLE_REDIRECT_ERR_CNT	= 1U << 2,
-	SAMPLE_CPUMAP_ENQUEUE_CNT  = 1U << 3,
-	SAMPLE_CPUMAP_KTHREAD_CNT  = 1U << 4,
-	SAMPLE_EXCEPTION_CNT	= 1U << 5,
-	SAMPLE_DEVMAP_XMIT_CNT  = 1U << 6,
-	SAMPLE_REDIRECT_CNT	= 1U << 7,
+	_SAMPLE_REDIRECT_MAP        = 1U << 0,
+	SAMPLE_RX_CNT               = 1U << 1,
+	SAMPLE_REDIRECT_ERR_CNT     = 1U << 2,
+	SAMPLE_CPUMAP_ENQUEUE_CNT   = 1U << 3,
+	SAMPLE_CPUMAP_KTHREAD_CNT   = 1U << 4,
+	SAMPLE_EXCEPTION_CNT        = 1U << 5,
+	SAMPLE_DEVMAP_XMIT_CNT      = 1U << 6,
+	SAMPLE_REDIRECT_CNT         = 1U << 7,
+	SAMPLE_REDIRECT_MAP_CNT     = SAMPLE_REDIRECT_CNT | _SAMPLE_REDIRECT_MAP,
+	SAMPLE_REDIRECT_ERR_MAP_CNT = SAMPLE_REDIRECT_ERR_CNT | _SAMPLE_REDIRECT_MAP,
 };
 
 static const char *const map_type_strings[] = {
@@ -153,8 +156,11 @@ void sample_exit(int status);
 struct stats_record *alloc_stats_record(void);
 void free_stats_record(struct stats_record *rec);
 void sample_stats_print(int mask, struct stats_record *cur,
-			struct stats_record *prev, char *prog_name);
+			struct stats_record *prev, char *prog_name,
+			int interval);
 void sample_stats_collect(int mask, struct stats_record *rec);
+void sample_summary_update(struct sample_output *out, int interval);
+void sample_summary_print(void);
 void sample_stats_poll(int interval, int mask, char *prog_name,
 		       int use_separators);
 void sample_stats_print_cpumap_remote(struct stats_record *stats_rec,
