forked from iovisor/bcc
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
libbpf-tools: convert wakeuptime tool
Signed-off-by: Nicolas Sterchele <[email protected]>
- Loading branch information
1 parent
a92c2bd
commit ad4c8e3
Showing
5 changed files
with
391 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -54,5 +54,6 @@ | |
/tcprtt | ||
/tcpsynbl | ||
/vfsstat | ||
/wakeuptime | ||
/xfsdist | ||
/xfsslower |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -81,6 +81,7 @@ APPS = \ | |
tcprtt \ | ||
tcpsynbl \ | ||
vfsstat \ | ||
wakeuptime \ | ||
$(BZ_APPS) \ | ||
# | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
// SPDX-License-Identifier: GPL-3.0 | ||
// Copyright (c) 2022 Nicolas Sterchele | ||
#include "vmlinux.h" | ||
#include <bpf/bpf_helpers.h> | ||
#include <bpf/bpf_core_read.h> | ||
#include <bpf/bpf_tracing.h> | ||
#include "wakeuptime.h" | ||
#include "maps.bpf.h" | ||
|
||
#define PF_KTHREAD 0x00200000 /* kernel thread */ | ||
|
||
const volatile pid_t targ_pid = 0; | ||
const volatile __u64 max_block_ns = -1; | ||
const volatile __u64 min_block_ns = 1; | ||
const volatile bool user_threads_only = false; | ||
|
||
struct { | ||
__uint(type, BPF_MAP_TYPE_HASH); | ||
__uint(max_entries, MAX_ENTRIES); | ||
__type(key, struct key_t); | ||
__type(value, u64); | ||
} counts SEC(".maps"); | ||
|
||
struct { | ||
__uint(type, BPF_MAP_TYPE_HASH); | ||
__uint(max_entries, MAX_ENTRIES); | ||
__type(key, u32); | ||
__type(value, u64); | ||
} start SEC(".maps"); | ||
|
||
struct { | ||
__uint(type, BPF_MAP_TYPE_STACK_TRACE); | ||
__uint(key_size, sizeof(u32)); | ||
} stackmap SEC(".maps"); | ||
|
||
static int offcpu_sched_switch(struct task_struct *prev) | ||
{ | ||
u64 pid_tgid = bpf_get_current_pid_tgid(); | ||
u32 pid = pid_tgid >> 32; | ||
u32 tid = (u32)pid_tgid; | ||
u64 ts; | ||
|
||
if (targ_pid && targ_pid != pid) | ||
return 0; | ||
|
||
if (user_threads_only && prev->flags & PF_KTHREAD) | ||
return 0; | ||
|
||
ts = bpf_ktime_get_ns(); | ||
bpf_map_update_elem(&start, &tid, &ts, BPF_ANY); | ||
return 0; | ||
} | ||
|
||
static int wakeup(void *ctx, struct task_struct *p) | ||
{ | ||
u32 pid = p->tgid; | ||
u32 tid = p->pid; | ||
u64 delta, *count_key, *tsp; | ||
static const u64 zero; | ||
struct key_t key = {}; | ||
|
||
if (targ_pid && targ_pid != pid) | ||
return 0; | ||
tsp = bpf_map_lookup_elem(&start, &tid); | ||
if (tsp == 0) | ||
return 0; | ||
bpf_map_delete_elem(&start, &tid); | ||
|
||
delta = bpf_ktime_get_ns() - *tsp; | ||
if ((delta < min_block_ns) || (delta > max_block_ns)) | ||
return 0; | ||
|
||
key.w_k_stack_id = bpf_get_stackid(ctx, &stackmap, 0); | ||
bpf_probe_read_kernel(&key.target, sizeof(key.target), p->comm); | ||
bpf_get_current_comm(&key.waker, sizeof(key.waker)); | ||
|
||
count_key = bpf_map_lookup_or_try_init(&counts, &key, &zero); | ||
if (count_key) | ||
__atomic_add_fetch(count_key, delta, __ATOMIC_RELAXED); | ||
|
||
return 0; | ||
} | ||
|
||
|
||
SEC("tp_btf/sched_switch") | ||
int BPF_PROG(sched_switch, bool preempt, struct task_struct *prev, struct task_struct *next) | ||
{ | ||
return offcpu_sched_switch(prev); | ||
} | ||
|
||
SEC("tp_btf/sched_wakeup") | ||
int BPF_PROG(sched_wakeup, struct task_struct *p) | ||
{ | ||
return wakeup(ctx, p); | ||
} | ||
|
||
char LICENSE[] SEC("license") = "GPL"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,277 @@ | ||
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) | ||
// Copyright (c) 2022 Nicolas Sterchele | ||
// | ||
// Based on wakeuptime(8) from BCC by Brendan Gregg | ||
// XX-Jul-2022 Nicolas Sterchele created this. | ||
#include <argp.h> | ||
#include <signal.h> | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
#include <string.h> | ||
#include <time.h> | ||
#include <bpf/libbpf.h> | ||
#include <bpf/bpf.h> | ||
#include "wakeuptime.h" | ||
#include "wakeuptime.skel.h" | ||
#include "trace_helpers.h" | ||
#include <unistd.h> | ||
|
||
static volatile sig_atomic_t exiting = 0; | ||
|
||
struct env { | ||
pid_t pid; | ||
bool user_threads_only; | ||
bool verbose; | ||
int stack_storage_size; | ||
int perf_max_stack_depth; | ||
__u64 min_block_time; | ||
__u64 max_block_time; | ||
int duration; | ||
} env = { | ||
.verbose = false, | ||
.stack_storage_size = 1024, | ||
.perf_max_stack_depth = 127, | ||
.min_block_time = 1, | ||
.max_block_time = -1, | ||
.duration = 99999999, | ||
}; | ||
|
||
const char *argp_program_version = "wakeuptime 0.1"; | ||
const char *argp_program_bug_address = | ||
"https://github.com/iovisor/bcc/tree/master/libbpf-tools"; | ||
const char argp_program_doc[] = | ||
"Summarize sleep to wakeup time by waker kernel stack.\n" | ||
"\n" | ||
"USAGE: wakeuptime [-h] [-p PID | -u] [-v] [-m MIN-BLOCK-TIME] " | ||
"[-M MAX-BLOCK-TIME] ]--perf-max-stack-depth] [--stack-storage-size] [duration]\n" | ||
"EXAMPLES:\n" | ||
" wakeuptime # trace blocked time with waker stacks\n" | ||
" wakeuptime 5 # trace for 5 seconds only\n" | ||
" wakeuptime -u # don't include kernel threads (user only)\n" | ||
" wakeuptime -p 185 # trace for PID 185 only\n"; | ||
|
||
#define OPT_PERF_MAX_STACK_DEPTH 1 /* --pef-max-stack-depth */ | ||
#define OPT_STACK_STORAGE_SIZE 2 /* --stack-storage-size */ | ||
|
||
static const struct argp_option opts[] = { | ||
{ "pid", 'p', "PID", 0, "trace this PID only"}, | ||
{ "verbose", 'v', NULL, 0, "show raw addresses" }, | ||
{ "user-threads-only", 'u', NULL, 0, "user threads only (no kernel threads)" }, | ||
{ "perf-max-stack-depth", OPT_PERF_MAX_STACK_DEPTH, | ||
"PERF-MAX-STACK-DEPTH", 0, "the limit for both kernel and user stack traces (default 127)" }, | ||
{ "stack-storage-size", OPT_STACK_STORAGE_SIZE, "STACK-STORAGE-SIZE", 0, | ||
"the number of unique stack traces that can be stored and displayed (default 1024)" }, | ||
{ "min-block-time", 'm', "MIN-BLOCK-TIME", 0, | ||
"the amount of time in microseconds over which we store traces (default 1)" }, | ||
{ "max-block-time", 'M', "MAX-BLOCK-TIME", 0, | ||
"the amount of time in microseconds under which we store traces (default U64_MAX)" }, | ||
{ NULL, 'h', NULL, OPTION_HIDDEN, "Show the full help" }, | ||
{}, | ||
}; | ||
|
||
static error_t parse_arg(int key, char *arg, struct argp_state *state) | ||
{ | ||
static int pos_args; | ||
int pid; | ||
|
||
switch (key) { | ||
case 'h': | ||
argp_state_help(state, stderr, ARGP_HELP_STD_HELP); | ||
break; | ||
case 'v': | ||
env.verbose = true; | ||
break; | ||
case 'u': | ||
env.user_threads_only = true; | ||
break; | ||
case 'p': | ||
errno = 0; | ||
pid = strtol(arg, NULL, 10); | ||
if (errno || pid <= 0) { | ||
fprintf(stderr, "Invalid PID: %s\n", arg); | ||
argp_usage(state); | ||
} | ||
env.pid = pid; | ||
break; | ||
case OPT_PERF_MAX_STACK_DEPTH: | ||
errno = 0; | ||
env.perf_max_stack_depth = strtol(arg, NULL, 10); | ||
if (errno) { | ||
fprintf(stderr, "invalid perf max stack depth: %s\n", arg); | ||
argp_usage(state); | ||
} | ||
break; | ||
case OPT_STACK_STORAGE_SIZE: | ||
errno = 0; | ||
env.stack_storage_size = strtol(arg, NULL, 10); | ||
if (errno) { | ||
fprintf(stderr, "invalid stack storage size: %s\n", arg); | ||
argp_usage(state); | ||
} | ||
break; | ||
case 'm': | ||
errno = 0; | ||
env.min_block_time = strtoll(arg, NULL, 10); | ||
if (errno) { | ||
fprintf(stderr, "Invalid min block time (in us): %s\n", arg); | ||
argp_usage(state); | ||
} | ||
break; | ||
case 'M': | ||
errno = 0; | ||
env.max_block_time = strtoll(arg, NULL, 10); | ||
if (errno) { | ||
fprintf(stderr, "Invalid min block time (in us): %s\n", arg); | ||
argp_usage(state); | ||
} | ||
break; | ||
case ARGP_KEY_ARG: | ||
errno = 0; | ||
if (pos_args == 0){ | ||
env.duration = strtol(arg, NULL, 10); | ||
if (errno || env.duration <= 0) { | ||
fprintf(stderr, "invalid duration (in s)\n"); | ||
argp_usage(state); | ||
} | ||
} else { | ||
fprintf(stderr, "Unrecognized positional argument: %s\n", arg); | ||
argp_usage(state); | ||
} | ||
break; | ||
default: | ||
return ARGP_ERR_UNKNOWN; | ||
} | ||
return 0; | ||
} | ||
|
||
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 void sig_int(int signo) | ||
{ | ||
exiting = 1; | ||
} | ||
|
||
static void print_map(struct ksyms *ksyms, struct wakeuptime_bpf *obj) | ||
{ | ||
struct key_t lookup_key = {}, next_key; | ||
int err, i, counts_fd, stack_traces_fd, val; | ||
unsigned long *ip; | ||
const struct ksym *ksym; | ||
|
||
ip = calloc(env.perf_max_stack_depth, sizeof(*ip)); | ||
if (!ip) { | ||
fprintf(stderr, "failed to alloc ip\n"); | ||
return; | ||
} | ||
|
||
counts_fd = bpf_map__fd(obj->maps.counts); | ||
stack_traces_fd = bpf_map__fd(obj->maps.stackmap); | ||
|
||
while (!bpf_map_get_next_key(counts_fd, &lookup_key, &next_key)){ | ||
err = bpf_map_lookup_elem(counts_fd, &next_key, &val); | ||
if (err < 0) { | ||
fprintf(stderr, "failed to lookup info: %d\n", err); | ||
free(ip); | ||
return; | ||
} | ||
printf("\n %-16s %s\n", "target:", next_key.target); | ||
lookup_key = next_key; | ||
|
||
err = bpf_map_lookup_elem(stack_traces_fd, &next_key.w_k_stack_id, ip); | ||
if (err < 0) { | ||
fprintf(stderr, "missed kernel stack: %d\n", err); | ||
} | ||
for (i = 0; i < env.perf_max_stack_depth && ip[i]; i++) { | ||
ksym = ksyms__map_addr(ksyms, ip[i]); | ||
printf(" %-16lx %s\n", ip[i], ksym ? ksym->name: "Unknown"); | ||
} | ||
printf(" %16s %s\n","waker:", next_key.waker); | ||
/*to convert val in microseconds*/ | ||
val /= 1000; | ||
printf(" %d\n", val); | ||
} | ||
|
||
free(ip); | ||
} | ||
|
||
int main(int argc, char **argv) | ||
{ | ||
static const struct argp argp = { | ||
.options = opts, | ||
.parser = parse_arg, | ||
.doc = argp_program_doc, | ||
}; | ||
struct wakeuptime_bpf *obj; | ||
struct ksyms *ksyms = NULL; | ||
int err; | ||
|
||
err = argp_parse(&argp, argc, argv, 0, NULL, NULL); | ||
if (err) | ||
return err; | ||
|
||
if (env.min_block_time >= env.max_block_time) { | ||
fprintf(stderr, "min_block_time should be smaller than max_block_time\n"); | ||
return 1; | ||
} | ||
|
||
if (env.user_threads_only && env.pid > 0) { | ||
fprintf(stderr, "use either -u or -p"); | ||
} | ||
|
||
libbpf_set_strict_mode(LIBBPF_STRICT_ALL); | ||
libbpf_set_print(libbpf_print_fn); | ||
|
||
obj = wakeuptime_bpf__open(); | ||
if (!obj) { | ||
fprintf(stderr, "failed to open BPF object\n"); | ||
return 1; | ||
} | ||
|
||
obj->rodata->targ_pid = env.pid; | ||
obj->rodata->min_block_ns = env.min_block_time * 1000; | ||
obj->rodata->max_block_ns = env.max_block_time * 1000; | ||
obj->rodata->user_threads_only = env.user_threads_only; | ||
|
||
bpf_map__set_value_size(obj->maps.stackmap, | ||
env.perf_max_stack_depth * sizeof(unsigned long)); | ||
bpf_map__set_max_entries(obj->maps.stackmap, env.stack_storage_size); | ||
|
||
err = wakeuptime_bpf__load(obj); | ||
if (err) { | ||
fprintf(stderr, "failed to load BPF object: %d\n", err); | ||
goto cleanup; | ||
} | ||
|
||
ksyms = ksyms__load(); | ||
if (!ksyms) { | ||
fprintf(stderr, "failed to load kallsyms\n"); | ||
goto cleanup; | ||
} | ||
|
||
err = wakeuptime_bpf__attach(obj); | ||
if (err) { | ||
fprintf(stderr, "failed to attach BPF programs\n"); | ||
goto cleanup; | ||
} | ||
|
||
if (signal(SIGINT, sig_int) == SIG_ERR) { | ||
fprintf(stderr, "can't set signal handler: %s\n", strerror(errno)); | ||
err = 1; | ||
goto cleanup; | ||
} | ||
|
||
printf("Tracing blocked time (us) by kernel stack\n"); | ||
sleep(env.duration); | ||
print_map(ksyms, obj); | ||
|
||
cleanup: | ||
wakeuptime_bpf__destroy(obj); | ||
ksyms__free(ksyms); | ||
return err != 0; | ||
} | ||
|
Oops, something went wrong.