diff options
Diffstat (limited to 'lib/util/stats.c')
-rw-r--r-- | lib/util/stats.c | 292 |
1 files changed, 292 insertions, 0 deletions
diff --git a/lib/util/stats.c b/lib/util/stats.c new file mode 100644 index 0000000..f04c968 --- /dev/null +++ b/lib/util/stats.c @@ -0,0 +1,292 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <getopt.h> + +#include <locale.h> +#include <unistd.h> +#include <time.h> + +#include <bpf/bpf.h> +#include <bpf/libbpf.h> + +#include "stats.h" +#include "util.h" +#include "logging.h" + +#define NANOSEC_PER_SEC 1000000000 /* 10^9 */ +static int gettime(__u64 *nstime) +{ + struct timespec t; + int res; + + res = clock_gettime(CLOCK_MONOTONIC, &t); + if (res < 0) { + pr_warn("Error with gettimeofday! (%i)\n", res); + return res; + } + + *nstime = (__u64)t.tv_sec * NANOSEC_PER_SEC + t.tv_nsec; + return 0; +} + +static double calc_period(struct record *r, struct record *p) +{ + double period_ = 0; + __u64 period = 0; + + period = r->timestamp - p->timestamp; + if (period > 0) + period_ = ((double)period / NANOSEC_PER_SEC); + + return period_; +} + +int stats_print_one(struct stats_record *stats_rec) +{ + __u64 packets, bytes; + struct record *rec; + int i, err; + + /* Print for each XDP actions stats */ + for (i = 0; i < XDP_ACTION_MAX; i++) { + char *fmt = " %-35s %'11lld pkts %'11lld KiB\n"; + const char *action = action2str(i); + + rec = &stats_rec->stats[i]; + packets = rec->total.rx_packets; + bytes = rec->total.rx_bytes; + + if (rec->enabled) { + err = printf(fmt, action, packets, bytes / 1024); + if (err < 0) + return err; + } + } + + return 0; +} + +int stats_print(struct stats_record *stats_rec, struct stats_record *stats_prev) +{ + struct record *rec, *prev; + __u64 packets, bytes; + struct timespec t; + bool first = true; + double period; + double pps; /* packets per sec */ + double bps; /* bits per sec */ + int i, err; + + err = clock_gettime(CLOCK_REALTIME, &t); + if (err < 0) { + pr_warn("Error with gettimeofday! (%i)\n", err); + return err; + } + + /* Print for each XDP actions stats */ + for (i = 0; i < XDP_ACTION_MAX; i++) { + char *fmt = "%-12s %'11lld pkts (%'10.0f pps)" + " %'11lld KiB (%'6.0f Mbits/s)\n"; + const char *action = action2str(i); + + rec = &stats_rec->stats[i]; + prev = &stats_prev->stats[i]; + + if (!rec->enabled) + continue; + + packets = rec->total.rx_packets - prev->total.rx_packets; + bytes = rec->total.rx_bytes - prev->total.rx_bytes; + + period = calc_period(rec, prev); + if (period == 0) + return 0; + + if (first) { + printf("Period of %fs ending at %ld.%06ld\n", period, + (long) t.tv_sec, (long) t.tv_nsec / 1000); + first = false; + } + + pps = packets / period; + + bps = (bytes * 8) / period / 1000000; + + printf(fmt, action, rec->total.rx_packets, pps, + rec->total.rx_bytes / 1024, bps, period); + } + printf("\n"); + + return 0; +} + +/* BPF_MAP_TYPE_ARRAY */ +static int map_get_value_array(int fd, __u32 key, struct xdp_stats_record *value) +{ + int err = 0; + + err = bpf_map_lookup_elem(fd, &key, value); + if (err) + pr_debug("bpf_map_lookup_elem failed key:0x%X\n", key); + + return err; +} + +/* BPF_MAP_TYPE_PERCPU_ARRAY */ +static int map_get_value_percpu_array(int fd, __u32 key, struct xdp_stats_record *value) +{ + /* For percpu maps, userspace gets a value per possible CPU */ + int nr_cpus = libbpf_num_possible_cpus(); + struct xdp_stats_record *values; + __u64 sum_bytes = 0; + __u64 sum_pkts = 0; + int i, err; + + if (nr_cpus < 0) + return nr_cpus; + + values = calloc(nr_cpus, sizeof(*values)); + if (!values) + return -ENOMEM; + + err = bpf_map_lookup_elem(fd, &key, values); + if (err) { + pr_debug("bpf_map_lookup_elem failed key:0x%X\n", key); + goto out; + } + + /* Sum values from each CPU */ + for (i = 0; i < nr_cpus; i++) { + sum_pkts += values[i].rx_packets; + sum_bytes += values[i].rx_bytes; + } + value->rx_packets = sum_pkts; + value->rx_bytes = sum_bytes; +out: + free(values); + return err; +} + +static int map_collect(int fd, __u32 map_type, __u32 key, struct record *rec) +{ + struct xdp_stats_record value = {}; + int err; + + /* Get time as close as possible to reading map contents */ + err = gettime(&rec->timestamp); + if (err) + return err; + + switch (map_type) { + case BPF_MAP_TYPE_ARRAY: + err = map_get_value_array(fd, key, &value); + break; + case BPF_MAP_TYPE_PERCPU_ARRAY: + err = map_get_value_percpu_array(fd, key, &value); + break; + default: + pr_warn("Unknown map_type: %u cannot handle\n", map_type); + err = -EINVAL; + break; + } + + if (err) + return err; + + rec->total.rx_packets = value.rx_packets; + rec->total.rx_bytes = value.rx_bytes; + return 0; +} + +int stats_collect(int map_fd, __u32 map_type, struct stats_record *stats_rec) +{ + /* Collect all XDP actions stats */ + __u32 key; + int err; + + for (key = 0; key < XDP_ACTION_MAX; key++) { + if (!stats_rec->stats[key].enabled) + continue; + + err = map_collect(map_fd, map_type, key, + &stats_rec->stats[key]); + if (err) + return err; + } + + return 0; +} + +static int check_map_pin(__u32 map_id, const char *pin_dir, const char *map_name) +{ + struct bpf_map_info info = {}; + int fd, ret = 0; + + fd = get_pinned_map_fd(pin_dir, map_name, &info); + if (fd < 0) { + if (fd == -ENOENT) + pr_warn("Stats map disappeared while polling\n"); + else + pr_warn("Unable to re-open stats map\n"); + return fd; + } + + if (info.id != map_id) { + pr_warn("Stats map ID changed while polling\n"); + ret = -EINVAL; + } + close(fd); + + return ret; +} + +int stats_poll(int map_fd, int interval, bool *exit, + const char *pin_dir, const char *map_name) +{ + struct bpf_map_info info = {}; + struct stats_record prev, record = { 0 }; + __u32 info_len = sizeof(info); + __u32 map_type, map_id; + int err; + + record.stats[XDP_DROP].enabled = true; + record.stats[XDP_PASS].enabled = true; + record.stats[XDP_REDIRECT].enabled = true; + record.stats[XDP_TX].enabled = true; + + if (!interval) + return -EINVAL; + + err = bpf_obj_get_info_by_fd(map_fd, &info, &info_len); + if (err) + return -errno; + map_type = info.type; + map_id = info.id; + + /* Get initial reading quickly */ + stats_collect(map_fd, map_type, &record); + + usleep(1000000 / 4); + + while (!*exit) { + if (pin_dir) { + err = check_map_pin(map_id, pin_dir, map_name); + if (err) + return err; + } + + memset(&info, 0, sizeof(info)); + prev = record; /* struct copy */ + stats_collect(map_fd, map_type, &record); + err = stats_print(&record, &prev); + if (err) + return err; + usleep(interval * 1000); + } + + return 0; +} |