--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2021 Wenbo Zhang
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_core_read.h>
+#include <bpf/bpf_tracing.h>
+#include "bits.bpf.h"
+#include "ext4dist.h"
+
+const volatile bool targ_ms = false;
+const volatile pid_t targ_tgid = 0;
+
+#define MAX_ENTRIES 10240
+
+struct hist hists[__MAX_FOP_TYPE];
+
+struct {
+ __uint(type, BPF_MAP_TYPE_HASH);
+ __uint(max_entries, MAX_ENTRIES);
+ __type(key, u32);
+ __type(value, u64);
+} starts SEC(".maps");
+
+static int trace_entry(void)
+{
+ u64 id = bpf_get_current_pid_tgid();
+ u32 tgid = id >> 32;
+ u32 pid = id;
+ u64 ts;
+
+ if (targ_tgid && targ_tgid != tgid)
+ return 0;
+ ts = bpf_ktime_get_ns();
+ bpf_map_update_elem(&starts, &pid, &ts, BPF_ANY);
+
+ return 0;
+}
+
+SEC("kprobe/ext4_file_read_iter")
+int BPF_KPROBE(kprobe1)
+{
+ return trace_entry();
+}
+
+SEC("kprobe/ext4_file_write_iter")
+int BPF_KPROBE(kprobe2)
+{
+ return trace_entry();
+}
+
+SEC("kprobe/ext4_file_open")
+int BPF_KPROBE(kprobe3)
+{
+ return trace_entry();
+}
+
+SEC("kprobe/ext4_sync_file")
+int BPF_KPROBE(kprobe4)
+{
+ return trace_entry();
+}
+
+SEC("fentry/ext4_file_read_iter")
+int BPF_PROG(fentry1)
+{
+ return trace_entry();
+}
+
+SEC("fentry/ext4_file_write_iter")
+int BPF_PROG(fentry2)
+{
+ return trace_entry();
+}
+
+SEC("fentry/ext4_file_open")
+int BPF_PROG(fentry3)
+{
+ return trace_entry();
+}
+
+SEC("fentry/ext4_sync_file")
+int BPF_PROG(fentry4)
+{
+ return trace_entry();
+}
+
+static int trace_return(enum ext4_fop_type type)
+{
+ u64 *tsp, slot, ts = bpf_ktime_get_ns();
+ u64 id = bpf_get_current_pid_tgid();
+ u32 pid = id;
+ s64 delta;
+
+ tsp = bpf_map_lookup_elem(&starts, &pid);
+ if (!tsp)
+ return 0;
+ delta = (s64)(ts - *tsp);
+ if (delta < 0)
+ goto cleanup;
+
+ if (targ_ms)
+ delta /= 1000000U;
+ else
+ delta /= 1000U;
+ slot = log2l(delta);
+ if (slot >= MAX_SLOTS)
+ slot = MAX_SLOTS - 1;
+ if (type >= __MAX_FOP_TYPE)
+ goto cleanup;
+ __sync_fetch_and_add(&hists[type].slots[slot], 1);
+
+cleanup:
+ bpf_map_delete_elem(&starts, &pid);
+ return 0;
+}
+
+SEC("kretprobe/ext4_file_read_iter")
+int BPF_KRETPROBE(kretprobe1)
+{
+ return trace_return(READ_ITER);
+}
+
+SEC("kretprobe/ext4_file_write_iter")
+int BPF_KRETPROBE(kretprobe2)
+{
+ return trace_return(WRITE_ITER);
+}
+
+SEC("kretprobe/ext4_file_open")
+int BPF_KRETPROBE(kretprobe3)
+{
+ return trace_return(OPEN);
+}
+
+SEC("kretprobe/ext4_sync_file")
+int BPF_KRETPROBE(kretprobe4)
+{
+ return trace_return(FSYNC);
+}
+
+SEC("fexit/ext4_file_read_iter")
+int BPF_PROG(fexit1)
+{
+ return trace_return(READ_ITER);
+}
+
+SEC("fexit/ext4_file_write_iter")
+int BPF_PROG(fexit2)
+{
+ return trace_return(WRITE_ITER);
+}
+
+SEC("fexit/ext4_file_open")
+int BPF_PROG(fexit3)
+{
+ return trace_return(OPEN);
+}
+
+SEC("fexit/ext4_sync_file")
+int BPF_PROG(fexit4)
+{
+ return trace_return(FSYNC);
+}
+
+char LICENSE[] SEC("license") = "GPL";
--- /dev/null
+// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
+// Copyright (c) 2021 Wenbo Zhang
+//
+// Based on ext4dist(8) from BCC by Brendan Gregg.
+// 9-Feb-2021 Wenbo Zhang Created this.
+#include <argp.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <bpf/libbpf.h>
+#include <bpf/bpf.h>
+#include "ext4dist.h"
+#include "ext4dist.skel.h"
+#include "trace_helpers.h"
+
+static struct env {
+ bool timestamp;
+ bool milliseconds;
+ pid_t pid;
+ time_t interval;
+ int times;
+ bool verbose;
+} env = {
+ .interval = 99999999,
+ .times = 99999999,
+};
+
+static volatile bool exiting;
+
+const char *argp_program_version = "ext4dist 0.1";
+const char *argp_program_bug_address =
+ "https://github.com/iovisor/bcc/tree/master/libbpf-tools";
+const char argp_program_doc[] =
+"Summarize ext4 operation latency.\n"
+"\n"
+"Usage: ext4dist [-h] [-T] [-m] [-p PID] [interval] [count]\n"
+"\n"
+"EXAMPLES:\n"
+" ext4dist # show operation latency as a histogram\n"
+" ext4dist -p 181 # trace PID 181 only\n"
+" ext4dist 1 10 # print 1 second summaries, 10 times\n"
+" ext4dist -m 5 # 5s summaries, milliseconds\n";
+
+static const struct argp_option opts[] = {
+ { "timestamp", 'T', NULL, 0, "Print timestamp" },
+ { "milliseconds", 'm', NULL, 0, "Millisecond histogram" },
+ { "pid", 'p', "PID", 0, "Process PID to trace" },
+ { "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 'm':
+ env.milliseconds = true;
+ break;
+ case 'p':
+ errno = 0;
+ env.pid = strtol(arg, NULL, 10);
+ if (errno || env.pid <= 0) {
+ fprintf(stderr, "invalid PID: %s\n", arg);
+ argp_usage(state);
+ }
+ 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 char *fop_names[] = {
+ [READ_ITER] = "read_iter",
+ [WRITE_ITER] = "write_iter",
+ [OPEN] = "open",
+ [FSYNC] = "fsync",
+};
+
+static struct hist zero;
+
+static int print_hists(struct ext4dist_bpf__bss *bss)
+{
+ const char *units = env.milliseconds ? "msecs" : "usecs";
+ enum ext4_fop_type type;
+
+ for (type = READ_ITER; type < __MAX_FOP_TYPE; type++) {
+ struct hist hist = bss->hists[type];
+
+ bss->hists[type] = zero;
+ if (!memcmp(&zero, &hist, sizeof(hist)))
+ continue;
+ printf("operation = '%s'\n", fop_names[type]);
+ print_log2_hist(hist.slots, MAX_SLOTS, units);
+ printf("\n");
+ }
+
+ return 0;
+}
+
+static bool should_fallback(void)
+{
+ /*
+ * Check whether EXT4 is compiled into a kernel module and whether
+ * the kernel supports module BTF.
+ *
+ * The purpose of this check is if the kernel supports module BTF,
+ * we can use fentry to get better performance, otherwise we need
+ * to fall back to use kprobe to be compatible with the old kernel.
+ */
+ if (is_kernel_module("ext4") && !access("/sys/kernel/btf/ext4", R_OK))
+ return true;
+ return false;
+}
+
+int main(int argc, char **argv)
+{
+ static const struct argp argp = {
+ .options = opts,
+ .parser = parse_arg,
+ .doc = argp_program_doc,
+ };
+ struct ext4dist_bpf *skel;
+ struct tm *tm;
+ 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;
+ }
+
+ skel = ext4dist_bpf__open();
+ if (!skel) {
+ fprintf(stderr, "failed to open BPF skelect\n");
+ return 1;
+ }
+
+ /* initialize global data (filtering options) */
+ skel->rodata->targ_ms = env.milliseconds;
+ skel->rodata->targ_tgid = env.pid;
+
+ if (should_fallback()) {
+ bpf_program__set_autoload(skel->progs.fentry1, false);
+ bpf_program__set_autoload(skel->progs.fentry2, false);
+ bpf_program__set_autoload(skel->progs.fentry3, false);
+ bpf_program__set_autoload(skel->progs.fentry4, false);
+ bpf_program__set_autoload(skel->progs.fexit1, false);
+ bpf_program__set_autoload(skel->progs.fexit2, false);
+ bpf_program__set_autoload(skel->progs.fexit3, false);
+ bpf_program__set_autoload(skel->progs.fexit4, false);
+ } else {
+ bpf_program__set_autoload(skel->progs.kprobe1, false);
+ bpf_program__set_autoload(skel->progs.kprobe2, false);
+ bpf_program__set_autoload(skel->progs.kprobe3, false);
+ bpf_program__set_autoload(skel->progs.kprobe4, false);
+ bpf_program__set_autoload(skel->progs.kretprobe1, false);
+ bpf_program__set_autoload(skel->progs.kretprobe2, false);
+ bpf_program__set_autoload(skel->progs.kretprobe3, false);
+ bpf_program__set_autoload(skel->progs.kretprobe4, false);
+ }
+
+ err = ext4dist_bpf__load(skel);
+ if (err) {
+ fprintf(stderr, "failed to load BPF skelect: %d\n", err);
+ goto cleanup;
+ }
+
+ err = ext4dist_bpf__attach(skel);
+ if (err) {
+ fprintf(stderr, "failed to attach BPF programs\n");
+ goto cleanup;
+ }
+
+ signal(SIGINT, sig_handler);
+
+ printf("Tracing ext4 operation latency... Hit Ctrl-C to end.\n");
+
+ /* main: poll */
+ while (1) {
+ sleep(env.interval);
+ printf("\n");
+
+ if (env.timestamp) {
+ time(&t);
+ tm = localtime(&t);
+ strftime(ts, sizeof(ts), "%H:%M:%S", tm);
+ printf("%-8s\n", ts);
+ }
+
+ err = print_hists(skel->bss);
+ if (err)
+ break;
+
+ if (exiting || --env.times == 0)
+ break;
+ }
+
+cleanup:
+ ext4dist_bpf__destroy(skel);
+
+ return err != 0;
+}