From 2f88cef90f9728ec8c7bee7bd48fdbcf197806c3 Mon Sep 17 00:00:00 2001 From: Andrii Nakryiko Date: Thu, 11 Jul 2024 21:40:51 -0700 Subject: [PATCH] uprobe-stress: add uprobe stress-testing tool Implemented a stress-testing tool for uprobes/uretprobes. It constantly triggers a set of user space functions, in parallel it attached and detached uprobes and uretprobes to random subset of them. To make things more interesting we also randomly and in parallel mmap() /proc/self/exe and fork() process, to trigger all the different code paths in uprobe-related functionality in the kernel. Signed-off-by: Andrii Nakryiko --- examples/c/.gitignore | 3 + examples/c/Makefile | 3 +- examples/c/uprobe-stress.bpf.c | 30 ++ examples/c/uprobe-stress.c | 809 +++++++++++++++++++++++++++++++++ examples/c/uprobe-stress.h | 12 + 5 files changed, 856 insertions(+), 1 deletion(-) create mode 100644 examples/c/uprobe-stress.bpf.c create mode 100644 examples/c/uprobe-stress.c create mode 100644 examples/c/uprobe-stress.h diff --git a/examples/c/.gitignore b/examples/c/.gitignore index 456deb07..2ad2f5d9 100644 --- a/examples/c/.gitignore +++ b/examples/c/.gitignore @@ -14,3 +14,6 @@ /lsm /cmake-build-debug/ /cmake-build-release/ +/uprobe-stress +.gdb_history + diff --git a/examples/c/Makefile b/examples/c/Makefile index bc9e71b2..70ba283a 100644 --- a/examples/c/Makefile +++ b/examples/c/Makefile @@ -24,7 +24,8 @@ INCLUDES := -I$(OUTPUT) -I../../libbpf/include/uapi -I$(dir $(VMLINUX)) -I$(LIBB CFLAGS := -g -Wall ALL_LDFLAGS := $(LDFLAGS) $(EXTRA_LDFLAGS) -APPS = minimal minimal_legacy bootstrap uprobe kprobe fentry usdt sockfilter tc ksyscall task_iter lsm +APPS = minimal minimal_legacy bootstrap uprobe kprobe fentry usdt sockfilter tc ksyscall task_iter lsm \ + uprobe-stress CARGO ?= $(shell which cargo) ifeq ($(strip $(CARGO)),) diff --git a/examples/c/uprobe-stress.bpf.c b/examples/c/uprobe-stress.bpf.c new file mode 100644 index 00000000..d3b7f1c1 --- /dev/null +++ b/examples/c/uprobe-stress.bpf.c @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +#include "vmlinux.h" +#include +#include +#include "uprobe-stress.h" + +char LICENSE[] SEC("license") = "Dual BSD/GPL"; + +struct counter enter_hits[MAX_CPUS]; +struct counter exit_hits[MAX_CPUS]; + +SEC("uprobe.multi") +int uprobe(struct pt_regs *ctx) +{ + int cpu = bpf_get_smp_processor_id(); + + __sync_add_and_fetch(&enter_hits[cpu & CPU_MASK].value, 1); + + return 0; +} + +SEC("uretprobe.multi") +int uretprobe(struct pt_regs *ctx) +{ + int cpu = bpf_get_smp_processor_id(); + + __sync_add_and_fetch(&exit_hits[cpu & CPU_MASK].value, 1); + + return 0; +} diff --git a/examples/c/uprobe-stress.c b/examples/c/uprobe-stress.c new file mode 100644 index 00000000..10b128b5 --- /dev/null +++ b/examples/c/uprobe-stress.c @@ -0,0 +1,809 @@ +// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +/* Copyright (c) 2020 Facebook */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "uprobe-stress.h" +#include "uprobe-stress.skel.h" + +#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0])) + +static inline int rand_num(int max) +{ + return (unsigned)rand() % max; +} + +static inline void atomic_inc(long *value) +{ + (void)__atomic_add_fetch(value, 1, __ATOMIC_RELAXED); +} + +static inline void atomic_add(long *value, long n) +{ + (void)__atomic_add_fetch(value, n, __ATOMIC_RELAXED); +} + +static inline long atomic_swap(long *value, long n) +{ + return __atomic_exchange_n(value, n, __ATOMIC_RELAXED); +} + +static inline long atomic_load(long *value) +{ + return __atomic_load_n(value, __ATOMIC_RELAXED); +} + +static unsigned long long time_now_ns(void) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec * 1000000000ULL + ts.tv_nsec; +} + +#define UPROBE_MAX_DEPTH 72 + +#define ATTACH_MAX_LINKS_PER_THREAD 5 +#define ATTACH_MAX_PROBES_PER_LINK 10 +#define ATTACH_MAX_SLEEP_US (25 * 1000) + +#define MMAP_MAX_MMAPS_PER_THREAD 10 +#define MMAP_MAX_SLEEP_US (1 * 1000) + +#define FORK_MAX_FORKS_PER_THREAD 10 +#define FORK_MAX_SLEEP_US (1000 * 1000) +#define FORK_MAX_RUN_TIME_MS 20 + +struct trig_stats { + long total_calls; +}; + +struct attach_stats { + long total_links; + long total_uprobes; + long total_uretprobes; +}; + +struct mmap_stats { + long total_mmaps; +}; + +struct fork_stats { + long total_forks; +}; + +struct all_stats { + struct trig_stats trig; + struct attach_stats attach; + struct mmap_stats mmap; + struct fork_stats fork; + long uprobe_hits; + long uretprobe_hits; +}; + +static struct env { + bool verbose; + + bool child_mode; + int child_run_time_ms; + + struct uprobe_stress_bpf *skel; + + pthread_t *trig_threads; + struct trig_stats **trig_stats; + int trig_thread_cnt; + int uprobe_max_depth; + int uprobe_fn_cnt; + + pthread_t *attach_threads; + struct attach_stats **attach_stats; + int attach_thread_cnt; + int attach_max_links_per_thread; + int attach_max_probes_per_link; + int attach_max_sleep_us; + + pthread_t *mmap_threads; + struct mmap_stats **mmap_stats; + int mmap_thread_cnt; + int mmap_max_mmaps_per_thread; + int mmap_max_sleep_us; + + pthread_t *fork_threads; + struct fork_stats **fork_stats; + int fork_thread_cnt; + int fork_max_forks_per_thread; + int fork_max_sleep_us; + int fork_max_run_time_ms; + + pthread_t stats_thread; + int stats_period_ms; +} env = { + .uprobe_max_depth = UPROBE_MAX_DEPTH, + + .trig_thread_cnt = 1, + .attach_thread_cnt = 1, + .mmap_thread_cnt = 1, + .fork_thread_cnt = 1, + + .attach_max_links_per_thread = ATTACH_MAX_LINKS_PER_THREAD, + .attach_max_probes_per_link = ATTACH_MAX_PROBES_PER_LINK, + .attach_max_sleep_us = ATTACH_MAX_SLEEP_US, + + .mmap_max_mmaps_per_thread = MMAP_MAX_MMAPS_PER_THREAD, + .mmap_max_sleep_us = MMAP_MAX_SLEEP_US, + + .fork_max_forks_per_thread = FORK_MAX_FORKS_PER_THREAD, + .fork_max_sleep_us = FORK_MAX_SLEEP_US, + .fork_max_run_time_ms = FORK_MAX_RUN_TIME_MS, + + .stats_period_ms = 5000, +}; + +const char *argp_program_version = "uprobe-stress 0.0"; +const char *argp_program_bug_address = ""; +const char argp_program_doc[] = "Uprobe/uretprobe kernel subsystem stress generator.\n"; + +static const struct argp_option opts[] = { + { "verbose", 'v', NULL, 0, "Verbose debug output" }, + { "trigger-threads", 't', "COUNT", 0, "Number of triggering threads" }, + { "attach-threads", 'a', "COUNT", 0, "Number of registration threads" }, + { "mmap-threads", 'm', "COUNT", 0, "Number of mmaping threads" }, + { "fork-threads", 'f', "COUNT", 0, "Number of forking threads" }, + { "child", 'c', "RUN_TIME_MS", 0, "Child mode: trigger uprobes for specified amont of time" }, + {}, +}; + +static error_t parse_arg(int key, char *arg, struct argp_state *state) +{ + switch (key) { + case 'v': + env.verbose = true; + break; + case 't': + env.trig_thread_cnt = strtol(arg, NULL, 10); + if (env.trig_thread_cnt <= 0) { + fprintf(stderr, "Invalid trigger-threads: %s\n", arg); + argp_usage(state); + } + break; + case 'a': + env.attach_thread_cnt = strtol(arg, NULL, 10); + if (env.attach_thread_cnt <= 0) { + fprintf(stderr, "Invalid attach-threads: %s\n", arg); + argp_usage(state); + } + break; + case 'm': + env.mmap_thread_cnt = strtol(arg, NULL, 10); + if (env.mmap_thread_cnt <= 0) { + fprintf(stderr, "Invalid mmap-threads: %s\n", arg); + argp_usage(state); + } + break; + case 'f': + env.fork_thread_cnt = strtol(arg, NULL, 10); + if (env.fork_thread_cnt <= 0) { + fprintf(stderr, "Invalid fork-threads: %s\n", arg); + argp_usage(state); + } + break; + case 'c': + env.child_mode = true; + env.child_run_time_ms = strtol(arg, NULL, 10); + if (env.child_run_time_ms < 0) { + fprintf(stderr, "Invalid chilld run time: %s\n", arg); + argp_usage(state); + } + break; + case ARGP_KEY_ARG: + argp_usage(state); + break; + default: + return ARGP_ERR_UNKNOWN; + } + return 0; +} + +static const struct argp argp = { + .options = opts, + .parser = parse_arg, + .doc = argp_program_doc, +}; + +static 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 volatile bool exiting = false; + +static void sig_handler(int sig) +{ + signal(SIGINT, SIG_DFL); + signal(SIGTERM, SIG_DFL); + exiting = true; +} + +#define __PASTE(a, b) a##b +#define PASTE(a, b) __PASTE(a, b) + +#define NAME(name, idx) PASTE(name, idx) + +#define F(body, name, idx) body(name, idx) + +#define F10(body, name, idx) \ + F(body, PASTE(name, idx), 0) F(body, PASTE(name, idx), 1) F(body, PASTE(name, idx), 2) \ + F(body, PASTE(name, idx), 3) F(body, PASTE(name, idx), 4) F(body, PASTE(name, idx), 5) \ + F(body, PASTE(name, idx), 6) F(body, PASTE(name, idx), 7) F(body, PASTE(name, idx), 8) \ + F(body, PASTE(name, idx), 9) + +#define F100(body, name, idx) \ + F10(body, PASTE(name, idx), 0) F10(body, PASTE(name, idx), 1) F10(body, PASTE(name, idx), 2) \ + F10(body, PASTE(name, idx), 3) F10(body, PASTE(name, idx), 4) F10(body, PASTE(name, idx), 5) \ + F10(body, PASTE(name, idx), 6) F10(body, PASTE(name, idx), 7) F10(body, PASTE(name, idx), 8) \ + F10(body, PASTE(name, idx), 9) + +#define DEF(name, idx) int __attribute__((weak)) NAME(name, idx)(int depth) { return 1 + uprobe_mediator(depth); } +#define REF(name, idx) &NAME(name, idx), + +static int uprobe_mediator(int depth); + +/* define a bunch of uprobe functions */ +F100(DEF, uprobe_, 0); + +typedef int (*uprobe_fn)(int); + +static uprobe_fn uprobe_fns[] = { + F100(REF, uprobe_, 0) +}; + +static int uprobe_mediator(int depth) { + int fn_idx; + + if (depth <= 0) + return 0; + + fn_idx = rand_num(env.uprobe_fn_cnt); + return uprobe_fns[fn_idx](depth - 1); +} + +static long uprobe_offs[ARRAY_SIZE(uprobe_fns)]; + +static long get_uprobe_offset(const void *addr) +{ + size_t start, end, base; + char buf[256]; + FILE *f; + + f = fopen("/proc/self/maps", "r"); + if (!f) + return -errno; + + while (fscanf(f, "%zx-%zx %s %zx %*[^\n]\n", &start, &end, buf, &base) == 4) { + if (buf[2] == 'x' && (uintptr_t)addr >= start && (uintptr_t)addr < end) { + fclose(f); + return (uintptr_t)addr - start + base; + } + } + + fclose(f); + return -ESRCH; +} + +static void *trig_thread(void *ctx) +{ + struct trig_stats *stats = calloc(1, sizeof(*stats)); + int thread_idx = (long)ctx; + int depth; + + env.trig_stats[thread_idx] = stats; + + while (!exiting) { + depth = rand_num(env.uprobe_max_depth); + if (depth) + atomic_add(&stats->total_calls, uprobe_mediator(depth)); + } + + return stats; +} + +struct attach_state { + int thread_idx; + + struct bpf_link **links; + int link_cnt; + + unsigned long *offsets; +}; + +static void attacher_attach(struct attach_state *state, struct attach_stats *stats) +{ + int i, idx, err; + LIBBPF_OPTS(bpf_uprobe_multi_opts, opts); + struct bpf_link *link; + + if (state->link_cnt >= env.attach_max_links_per_thread) + return; + + /* attach to random number of uprobes/uretprobes */ + opts.cnt = rand_num(env.attach_max_probes_per_link); + if (opts.cnt == 0) + return; + + opts.retprobe = rand_num(2) == 1; + + for (i = 0; i < opts.cnt; i++) { + idx = rand_num(env.uprobe_fn_cnt); + state->offsets[i] = uprobe_offs[idx]; + } + opts.offsets = state->offsets; + + link = bpf_program__attach_uprobe_multi( + opts.retprobe ? env.skel->progs.uretprobe : env.skel->progs.uprobe, + rand_num(2) == 1 ? -1 : getpid(), + "/proc/self/exe", + NULL, + &opts); + + if (!link) { + err = -errno; + fprintf(stderr, "Attacher #%d: failed to attach %s with %zu probes: %d\n", + state->thread_idx, opts.retprobe ? "uretprobe" : "uprobes", opts.cnt, err); + exit(1); + } + + atomic_inc(&stats->total_links); + if (opts.retprobe) + atomic_add(&stats->total_uretprobes, opts.cnt); + else + atomic_add(&stats->total_uprobes, opts.cnt); + + state->links[state->link_cnt++] = link; +} + +static void attacher_detach(struct attach_state *state, struct attach_stats *stats) +{ + int idx; + + if (state->link_cnt <= 0) + return; + + /* detach random link */ + idx = rand_num(state->link_cnt); + bpf_link__destroy(state->links[idx]); + + state->links[idx] = state->links[state->link_cnt - 1]; + state->link_cnt--; +} + +static void attacher_sleep(struct attach_state *state, struct attach_stats *stats) +{ + usleep(rand_num(env.attach_max_sleep_us)); +} + +static void *attach_thread(void *ctx) +{ + int thread_idx = (long)ctx, i; + struct attach_stats *stats = calloc(1, sizeof(*stats)); + struct attach_state *state = calloc(1, sizeof(*state)); + + env.attach_stats[thread_idx] = stats; + + state->thread_idx = thread_idx; + state->links = calloc(env.attach_max_links_per_thread, sizeof(*state->links)); + state->offsets = calloc(env.attach_max_probes_per_link, sizeof(*state->offsets)); + + while (!exiting) { + switch (rand_num(3)) { + case 0: attacher_attach(state, stats); break; + case 1: attacher_detach(state, stats); break; + case 2: attacher_sleep(state, stats); break; + default: fprintf(stderr, "ATTACH BOOM!\n"); exit(1); + } + } + + for (i = 0; i < state->link_cnt; i++) { + bpf_link__destroy(state->links[i]); + } + + return stats; +} + +struct mmap_state { + void **mmap_addrs; + size_t *mmap_sizes; + int mmap_cnt; +}; + +static void *mmap_thread(void *ctx) +{ + int thread_idx = (long)ctx; + struct mmap_stats *stats = calloc(1, sizeof(*stats)); + struct mmap_state *state = calloc(1, sizeof(*state)); + int page_sz = sysconf(_SC_PAGESIZE); + int fd, err, i; + void *addr; + long size; + + env.mmap_stats[thread_idx] = stats; + + state->mmap_addrs = calloc(env.mmap_max_mmaps_per_thread, sizeof(*state->mmap_addrs)); + state->mmap_sizes = calloc(env.mmap_max_mmaps_per_thread, sizeof(*state->mmap_sizes)); + + fd = open("/proc/self/exe", O_RDONLY); + if (fd < 0) { + err = -errno; + fprintf(stderr, "Mmaper #%d: failed to open() /proc/self/exe: %d\n", + thread_idx, err); + exit(1); + } + + while (!exiting) { + switch (rand_num(3)) { + case 0: { /* MMAP */ + long off, s1, s2, e1, e2; + int idx1, idx2; + + if (state->mmap_cnt >= env.mmap_max_mmaps_per_thread) + continue; + + idx1 = rand_num(env.uprobe_fn_cnt); + idx2 = rand_num(env.uprobe_fn_cnt); + s1 = uprobe_offs[idx1] / page_sz * page_sz; + s2 = uprobe_offs[idx2] / page_sz * page_sz; + e1 = (uprobe_offs[idx1] + page_sz - 1) / page_sz * page_sz; + e2 = (uprobe_offs[idx2] + page_sz - 1) / page_sz * page_sz; + s1 = s1 < s2 ? s1 : s2; + e1 = e1 > e2 ? e1 : e2; + size = e1 - s1; + off = e1; + + addr = mmap(NULL, size, PROT_EXEC | PROT_READ, MAP_PRIVATE, fd, off); + if (addr == MAP_FAILED) { + err = -errno; + fprintf(stderr, "Mmaper #%d: failed to mmap() /proc/self/exe with size %ld at offset %ld: %d\n", + thread_idx, size, off, err); + exit(1); + } + + state->mmap_addrs[state->mmap_cnt] = addr; + state->mmap_sizes[state->mmap_cnt] = size; + state->mmap_cnt++; + + atomic_inc(&stats->total_mmaps); + + break; + } + case 1: { /* MUNMAP */ + int idx; + + if (state->mmap_cnt <= 0) + continue; + + idx = rand_num(state->mmap_cnt); + addr = state->mmap_addrs[idx]; + size = state->mmap_sizes[idx]; + + err = munmap(addr, size); + if (err) { + err = -errno; + fprintf(stderr, "Mmaper #%d: failed to munmap() at addr %p with size %ld: %d\n", + thread_idx, addr, size, err); + exit(1); + } + + state->mmap_addrs[idx] = state->mmap_addrs[state->mmap_cnt - 1]; + state->mmap_sizes[idx] = state->mmap_sizes[state->mmap_cnt - 1]; + state->mmap_cnt--; + + break; + } + case 2: /* SLEEP */ + usleep(rand_num(env.mmap_max_sleep_us)); + break; + default: fprintf(stderr, "MMAP BOOM!\n"); exit(1); + } + } + + for (i = 0; i < state->mmap_cnt; i++) { + (void)munmap(state->mmap_addrs[i], state->mmap_sizes[i]); + } + + return stats; +} + +struct fork_state { + int *child_pids; + int child_cnt; +}; + +static void *fork_thread(void *ctx) +{ + int thread_idx = (long)ctx; + struct fork_stats *stats = calloc(1, sizeof(*stats)); + struct fork_state *state = calloc(1, sizeof(*state)); + int pid, i, idx, err, child_run_time_ms; + + env.fork_stats[thread_idx] = stats; + + state->child_pids = calloc(env.fork_max_forks_per_thread, sizeof(*state->child_pids)); + + while (!exiting) { + switch (rand_num(3)) { + case 0: /* FORK */ + if (state->child_cnt >= env.fork_max_forks_per_thread) + continue; + + child_run_time_ms = rand_num(env.fork_max_run_time_ms); + + pid = fork(); + if (pid < 0) { + err = -errno; + fprintf(stderr, "Forker #%d: failed to fork(): %d\n", + thread_idx, err); + exit(1); + } else if (pid > 0) { /* parent */ + state->child_pids[state->child_cnt++] = pid; + atomic_inc(&stats->total_forks); + } else if (pid == 0) { /* child */ + char buf[32]; + char *argv[] = { "./uprobe-stress", "--child", NULL, NULL }; + + snprintf(buf, sizeof(buf), "%d", child_run_time_ms); + argv[2] = buf; + + exit(execve("/proc/self/exe", argv, NULL)); + } + break; + case 1: /* WAIT */ + if (state->child_cnt <= 0) + continue; + + idx = rand_num(state->child_cnt); + pid = state->child_pids[idx]; + + waitpid(pid, NULL, 0); + + state->child_pids[idx] = state->child_pids[state->child_cnt - 1]; + state->child_cnt--; + + break; + case 2: /* SLEEP */ + usleep(rand_num(env.attach_max_sleep_us)); + break; + default: fprintf(stderr, "FORK BOOM!\n"); exit(1); + } + } + + for (i = 0; i < state->child_cnt; i++) { + waitpid(state->child_pids[i], NULL, 0); + } + + return stats; +} + +static void emit_stats(struct all_stats *stats, const char *desc) +{ + printf("%s:\n" + "%-20s %10ld\n" + "%-20s %10ld\n" + "%-20s %10ld\n" + "%-20s %10ld\n" + "%-20s %10ld\n" + "%-20s %10ld\n" + "%-20s %10ld\n" + "%-20s %10ld\n", + desc, + "FUNC CALLS", stats->trig.total_calls, + "UPROBE HITS", stats->uprobe_hits, + "URETPROBE HITS", stats->uretprobe_hits, + "ATTACHED LINKS", stats->attach.total_links, + "ATTACHED UPROBES", stats->attach.total_uprobes, + "ATTACHED URETPROBES", stats->attach.total_uretprobes, + "MMAP CALLS", stats->mmap.total_mmaps, + "FORKS CALLS", stats->fork.total_forks); +} + +static void collect_stats(struct all_stats *stats) +{ + int i; + + memset(stats, 0, sizeof(*stats)); + + for (i = 0; i < env.trig_thread_cnt; i++) { + struct trig_stats *s = env.trig_stats[i]; + + stats->trig.total_calls += atomic_load(&s->total_calls); + } + for (i = 0; i < env.attach_thread_cnt; i++) { + struct attach_stats *s = env.attach_stats[i]; + + stats->attach.total_links += atomic_load(&s->total_links); + stats->attach.total_uprobes += atomic_load(&s->total_uprobes); + stats->attach.total_uretprobes += atomic_load(&s->total_uretprobes); + } + for (i = 0; i < env.mmap_thread_cnt; i++) { + struct mmap_stats *s = env.mmap_stats[i]; + + stats->mmap.total_mmaps += atomic_load(&s->total_mmaps); + } + for (i = 0; i < env.fork_thread_cnt; i++) { + struct fork_stats *s = env.fork_stats[i]; + + stats->fork.total_forks += atomic_load(&s->total_forks); + } + for (i = 0; i < MAX_CPUS; i++) { + stats->uprobe_hits += atomic_load(&env.skel->bss->enter_hits[i].value); + stats->uretprobe_hits += atomic_load(&env.skel->bss->exit_hits[i].value); + } +} + +static void *stats_thread(void *ctx) +{ + struct all_stats prev = {}, cur = {}, diff; + unsigned long long last_ts = time_now_ns(), cur_ts; + long period = 0; + char buf[128]; + + while (!exiting) { + cur_ts = time_now_ns(); + if (cur_ts - last_ts < env.stats_period_ms * 1000000ULL) { + usleep(1000); + continue; + } + last_ts = cur_ts; + period++; + + collect_stats(&cur); + + diff.trig.total_calls = cur.trig.total_calls - prev.trig.total_calls; + + diff.attach.total_links = cur.attach.total_links - prev.attach.total_links; + diff.attach.total_uprobes = cur.attach.total_uprobes - prev.attach.total_uprobes; + diff.attach.total_uretprobes = cur.attach.total_uretprobes - prev.attach.total_uretprobes; + + diff.mmap.total_mmaps = cur.mmap.total_mmaps - prev.mmap.total_mmaps; + + diff.fork.total_forks = cur.fork.total_forks - prev.fork.total_forks; + + diff.uprobe_hits = cur.uprobe_hits - prev.uprobe_hits; + diff.uretprobe_hits = cur.uretprobe_hits - prev.uretprobe_hits; + + snprintf(buf, sizeof(buf), "\nPERIOD #%ld STATS", period); + + emit_stats(&diff, buf); + + prev = cur; + } + + return NULL; +} + +int main(int argc, char **argv) +{ + int i, err; + struct all_stats stats; + + env.uprobe_fn_cnt = ARRAY_SIZE(uprobe_fns); + srand(time(NULL)); + + /* Parse command line arguments */ + err = argp_parse(&argp, argc, argv, 0, NULL, NULL); + if (err) + return err; + + if (env.child_mode) { + unsigned long long start_ts = time_now_ns(); + + while (time_now_ns() - start_ts < env.child_run_time_ms * 1000000ULL) { + uprobe_mediator(rand_num(env.uprobe_max_depth)); + } + return 0; + } + + /* Set up libbpf errors and debug info callback */ + libbpf_set_print(libbpf_print_fn); + + /* Cleaner handling of Ctrl-C */ + signal(SIGINT, sig_handler); + signal(SIGTERM, sig_handler); + + /* Load and verify BPF application */ + env.skel = uprobe_stress_bpf__open_and_load(); + if (!env.skel) { + fprintf(stderr, "Failed to open and load BPF skeleton\n"); + return 1; + } + + + for (i = 0; i < ARRAY_SIZE(uprobe_fns); i++) { + uprobe_offs[i] = get_uprobe_offset(uprobe_fns[i]); + if (uprobe_offs[i] < 0) { + fprintf(stderr, "Failed to calculate uprobe #%d offset: %ld\n", i, uprobe_offs[i]); + exit(1); + } + } + + env.trig_threads = calloc(env.trig_thread_cnt, sizeof(*env.trig_threads)); + env.trig_stats = calloc(env.trig_thread_cnt, sizeof(*env.trig_stats)); + for (i = 0; i < env.trig_thread_cnt; i++) { + err = pthread_create(&env.trig_threads[i], NULL, trig_thread, (void *)(long)i); + if (err) { + fprintf(stderr, "Failed to create trigger thread #%d\n", i); + exit(1); + } + } + + env.attach_threads = calloc(env.attach_thread_cnt, sizeof(*env.attach_threads)); + env.attach_stats = calloc(env.attach_thread_cnt, sizeof(*env.attach_stats)); + for (i = 0; i < env.attach_thread_cnt; i++) { + err = pthread_create(&env.attach_threads[i], NULL, attach_thread, (void *)(long)i); + if (err) { + fprintf(stderr, "Failed to create attach thread #%d\n", i); + exit(1); + } + } + + env.mmap_threads = calloc(env.mmap_thread_cnt, sizeof(*env.mmap_threads)); + env.mmap_stats = calloc(env.mmap_thread_cnt, sizeof(*env.mmap_stats)); + for (i = 0; i < env.mmap_thread_cnt; i++) { + err = pthread_create(&env.mmap_threads[i], NULL, mmap_thread, (void *)(long)i); + if (err) { + fprintf(stderr, "Failed to create mmaping thread #%d\n", i); + exit(1); + } + } + + env.fork_threads = calloc(env.fork_thread_cnt, sizeof(*env.fork_threads)); + env.fork_stats = calloc(env.fork_thread_cnt, sizeof(*env.fork_stats)); + for (i = 0; i < env.fork_thread_cnt; i++) { + err = pthread_create(&env.fork_threads[i], NULL, fork_thread, (void *)(long)i); + if (err) { + fprintf(stderr, "Failed to create forking thread #%d\n", i); + exit(1); + } + } + + err = pthread_create(&env.stats_thread, NULL, stats_thread, NULL); + if (err) { + fprintf(stderr, "Failed to create stats thread\n"); + exit(1); + } + + printf("WORKING HARD!..\n"); + while (!exiting) + usleep(100000); + printf("\nEXITING...\n"); + + for (i = 0; i < env.trig_thread_cnt; i++) { + pthread_join(env.trig_threads[i], NULL); + } + for (i = 0; i < env.attach_thread_cnt; i++) { + pthread_join(env.attach_threads[i], NULL); + } + for (i = 0; i < env.mmap_thread_cnt; i++) { + pthread_join(env.mmap_threads[i], NULL); + } + for (i = 0; i < env.fork_thread_cnt; i++) { + pthread_join(env.fork_threads[i], NULL); + } + pthread_join(env.stats_thread, NULL); + + collect_stats(&stats); + emit_stats(&stats, "\nFINAL STATS"); + + uprobe_stress_bpf__destroy(env.skel); + + return err < 0 ? -err : 0; +} diff --git a/examples/c/uprobe-stress.h b/examples/c/uprobe-stress.h new file mode 100644 index 00000000..d5737852 --- /dev/null +++ b/examples/c/uprobe-stress.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ +#ifndef __UPROBE_STRESS_H +#define __UPROBE_STRESS_H + +#define CPU_MASK 255 +#define MAX_CPUS (CPU_MASK + 1) + +struct counter { + long value; +} __attribute__((aligned(128))); + +#endif /* __UPROBE_STRESS_H */