libbpf-tools: add cachestat
authorWenbo Zhang <ethercflow@gmail.com>
Thu, 11 Mar 2021 04:44:30 +0000 (12:44 +0800)
committeryonghong-song <ys114321@gmail.com>
Fri, 12 Mar 2021 07:48:24 +0000 (23:48 -0800)
Signed-off-by: Wenbo Zhang <ethercflow@gmail.com>
libbpf-tools/.gitignore
libbpf-tools/Makefile
libbpf-tools/cachestat.bpf.c [new file with mode: 0644]
libbpf-tools/cachestat.c [new file with mode: 0644]

index b67d7af45a088f7dcd1974a0abac2c4a46b86bc2..00f921daec6bc66e892ee102cc97ee6f03762263 100644 (file)
@@ -4,6 +4,7 @@
 /biosnoop
 /biostacks
 /bitesize
+/cachestat
 /cpudist
 /cpufreq
 /drsnoop
index 9e07c73a76af2b4e0bc66882f3989e7c348f4c34..5bbd0c42044002b8cd5c4a92904131c8c887267a 100644 (file)
@@ -21,6 +21,7 @@ APPS = \
        biosnoop \
        biostacks \
        bitesize \
+       cachestat \
        cpudist \
        cpufreq \
        drsnoop \
diff --git a/libbpf-tools/cachestat.bpf.c b/libbpf-tools/cachestat.bpf.c
new file mode 100644 (file)
index 0000000..36b7fca
--- /dev/null
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2021 Wenbo Zhang
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+__s64 total;   /* total cache accesses without counting dirties */
+__s64 misses;  /* total of add to lru because of read misses */
+__u64 mbd;     /* total of mark_buffer_dirty events */
+
+SEC("fentry/add_to_page_cache_lru")
+int BPF_PROG(add_to_page_cache_lru)
+{
+       __sync_fetch_and_add(&misses, 1);
+       return 0;
+}
+
+SEC("fentry/mark_page_accessed")
+int BPF_PROG(mark_page_accessed)
+{
+       __sync_fetch_and_add(&total, 1);
+       return 0;
+}
+
+SEC("fentry/account_page_dirtied")
+int BPF_PROG(account_page_dirtied)
+{
+       __sync_fetch_and_add(&misses, -1);
+       return 0;
+}
+
+SEC("fentry/mark_buffer_dirty")
+int BPF_PROG(mark_buffer_dirty)
+{
+       __sync_fetch_and_add(&total, -1);
+       __sync_fetch_and_add(&mbd, 1);
+       return 0;
+}
+
+char LICENSE[] SEC("license") = "GPL";
diff --git a/libbpf-tools/cachestat.c b/libbpf-tools/cachestat.c
new file mode 100644 (file)
index 0000000..ca2011a
--- /dev/null
@@ -0,0 +1,214 @@
+// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
+// Copyright (c) 2021 Wenbo Zhang
+//
+// Based on cachestat(8) from BCC by Brendan Gregg and Allan McAleavy.
+// 8-Mar-2021   Wenbo Zhang   Created this.
+#include <argp.h>
+#include <signal.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <time.h>
+#include <bpf/libbpf.h>
+#include <bpf/bpf.h>
+#include "cachestat.skel.h"
+#include "trace_helpers.h"
+
+static struct env {
+       time_t interval;
+       int times;
+       bool timestamp;
+       bool verbose;
+} env = {
+       .interval = 1,
+       .times = 99999999,
+};
+
+static volatile bool exiting;
+
+const char *argp_program_version = "cachestat 0.1";
+const char *argp_program_bug_address =
+       "https://github.com/iovisor/bcc/tree/master/libbpf-tools";
+const char argp_program_doc[] =
+"Count cache kernel function calls.\n"
+"\n"
+"USAGE: cachestat [--help] [-T] [interval] [count]\n"
+"\n"
+"EXAMPLES:\n"
+"    cachestat          # shows hits and misses to the file system page cache\n"
+"    cachestat -T       # include timestamps\n"
+"    cachestat 1 10     # print 1 second summaries, 10 times\n";
+
+static const struct argp_option opts[] = {
+       { "timestamp", 'T', NULL, 0, "Print timestamp" },
+       { "verbose", 'v', NULL, 0, "Verbose debug output" },
+       {},
+};
+
+static error_t parse_arg(int key, char *arg, struct argp_state *state)
+{
+       static int pos_args;
+
+       switch (key) {
+       case 'v':
+               env.verbose = true;
+               break;
+       case 'T':
+               env.timestamp = true;
+               break;
+       case ARGP_KEY_ARG:
+               errno = 0;
+               if (pos_args == 0) {
+                       env.interval = strtol(arg, NULL, 10);
+                       if (errno) {
+                               fprintf(stderr, "invalid internal\n");
+                               argp_usage(state);
+                       }
+               } else if (pos_args == 1) {
+                       env.times = strtol(arg, NULL, 10);
+                       if (errno) {
+                               fprintf(stderr, "invalid times\n");
+                               argp_usage(state);
+                       }
+               } else {
+                       fprintf(stderr,
+                               "unrecognized positional argument: %s\n", arg);
+                       argp_usage(state);
+               }
+               pos_args++;
+               break;
+       default:
+               return ARGP_ERR_UNKNOWN;
+       }
+       return 0;
+}
+
+int libbpf_print_fn(enum libbpf_print_level level,
+                   const char *format, va_list args)
+{
+       if (level == LIBBPF_DEBUG && !env.verbose)
+               return 0;
+       return vfprintf(stderr, format, args);
+}
+
+static void sig_handler(int sig)
+{
+       exiting = true;
+}
+
+static int get_meminfo(__u64 *buffers, __u64 *cached)
+{
+       FILE *f;
+
+       f = fopen("/proc/meminfo", "r");
+       if (!f)
+               return -1;
+       if (fscanf(f,
+                  "MemTotal: %*u kB\n"
+                  "MemFree: %*u kB\n"
+                  "MemAvailable: %*u kB\n"
+                  "Buffers: %llu kB\n"
+                  "Cached: %llu kB\n",
+                  buffers, cached) != 2) {
+               fclose(f);
+               return -1;
+       }
+       fclose(f);
+       return 0;
+}
+
+int main(int argc, char **argv)
+{
+       static const struct argp argp = {
+               .options = opts,
+               .parser = parse_arg,
+               .doc = argp_program_doc,
+       };
+       __u64 buffers, cached, mbd;
+       struct cachestat_bpf *obj;
+       __s64 total, misses, hits;
+       struct tm *tm;
+       float ratio;
+       char ts[32];
+       time_t t;
+       int err;
+
+       err = argp_parse(&argp, argc, argv, 0, NULL, NULL);
+       if (err)
+               return err;
+
+       libbpf_set_print(libbpf_print_fn);
+
+       err = bump_memlock_rlimit();
+       if (err) {
+               fprintf(stderr, "failed to increase rlimit: %d\n", err);
+               return 1;
+       }
+
+       obj = cachestat_bpf__open_and_load();
+       if (!obj) {
+               fprintf(stderr, "failed to open and/or load BPF object\n");
+               return 1;
+       }
+
+       err = cachestat_bpf__attach(obj);
+       if (err) {
+               fprintf(stderr, "failed to attach BPF programs\n");
+               goto cleanup;
+       }
+
+       signal(SIGINT, sig_handler);
+
+       if (env.timestamp)
+               printf("%-8s ", "TIME");
+       printf("%8s %8s %8s %8s %12s %10s\n", "HITS", "MISSES", "DIRTIES",
+               "HITRATIO", "BUFFERS_MB", "CACHED_MB");
+
+       while (1) {
+               sleep(env.interval);
+
+               /* total = total cache accesses without counting dirties */
+               total = __atomic_exchange_n(&obj->bss->total, 0, __ATOMIC_RELAXED);
+               /* misses = total of add to lru because of read misses */
+               misses = __atomic_exchange_n(&obj->bss->misses, 0, __ATOMIC_RELAXED);
+               /* mbd = total of mark_buffer_dirty events */
+               mbd = __atomic_exchange_n(&obj->bss->mbd, 0, __ATOMIC_RELAXED);
+
+               if (total < 0)
+                       total = 0;
+               if (misses < 0)
+                       misses = 0;
+               hits = total - misses;
+               /*
+                * If hits are < 0, then its possible misses are overestimated
+                * due to possibly page cache read ahead adding more pages than
+                * needed. In this case just assume misses as total and reset
+                * hits.
+                */
+               if (hits < 0) {
+                       misses = total;
+                       hits = 0;
+               }
+               ratio = total > 0 ? hits * 1.0 / total : 0.0;
+               err = get_meminfo(&buffers, &cached);
+               if (err) {
+                       fprintf(stderr, "failed to get meminfo: %d\n", err);
+                       goto cleanup;
+               }
+               if (env.timestamp) {
+                       time(&t);
+                       tm = localtime(&t);
+                       strftime(ts, sizeof(ts), "%H:%M:%S", tm);
+                       printf("%-8s ", ts);
+               }
+               printf("%8lld %8lld %8llu %7.2f%% %12llu %10llu\n",
+                       hits, misses, mbd, 100 * ratio,
+                       buffers / 1024, cached / 1024);
+
+               if (exiting || --env.times == 0)
+                       break;
+       }
+
+cleanup:
+       cachestat_bpf__destroy(obj);
+       return err != 0;
+}