| #!/usr/bin/env bcc-lua | |
| --[[ | |
| Copyright 2016 GitHub, Inc | |
| Licensed under the Apache License, Version 2.0 (the "License"); | |
| you may not use this file except in compliance with the License. | |
| You may obtain a copy of the License at | |
| http://www.apache.org/licenses/LICENSE-2.0 | |
| Unless required by applicable law or agreed to in writing, software | |
| distributed under the License is distributed on an "AS IS" BASIS, | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| See the License for the specific language governing permissions and | |
| limitations under the License. | |
| ]] | |
| local program = [[ | |
| #include <uapi/linux/ptrace.h> | |
| #include <linux/sched.h> | |
| #define MINBLOCK_US 1 | |
| struct key_t { | |
| char name[TASK_COMM_LEN]; | |
| int stack_id; | |
| }; | |
| BPF_HASH(counts, struct key_t); | |
| BPF_HASH(start, u32); | |
| BPF_STACK_TRACE(stack_traces, 10240); | |
| int oncpu(struct pt_regs *ctx, struct task_struct *prev) { | |
| u32 pid; | |
| u64 ts, *tsp; | |
| // record previous thread sleep time | |
| if (FILTER) { | |
| pid = prev->pid; | |
| ts = bpf_ktime_get_ns(); | |
| start.update(&pid, &ts); | |
| } | |
| // calculate current thread's delta time | |
| pid = bpf_get_current_pid_tgid(); | |
| tsp = start.lookup(&pid); | |
| if (tsp == 0) | |
| return 0; // missed start or filtered | |
| u64 delta = bpf_ktime_get_ns() - *tsp; | |
| start.delete(&pid); | |
| delta = delta / 1000; | |
| if (delta < MINBLOCK_US) | |
| return 0; | |
| // create map key | |
| u64 zero = 0, *val; | |
| struct key_t key = {}; | |
| int stack_flags = BPF_F_REUSE_STACKID; | |
| /* | |
| if (!(prev->flags & PF_KTHREAD)) | |
| stack_flags |= BPF_F_USER_STACK; | |
| */ | |
| bpf_get_current_comm(&key.name, sizeof(key.name)); | |
| key.stack_id = stack_traces.get_stackid(ctx, stack_flags); | |
| val = counts.lookup_or_init(&key, &zero); | |
| (*val) += delta; | |
| return 0; | |
| } | |
| ]] | |
| return function(BPF, utils) | |
| local ffi = require("ffi") | |
| local parser = utils.argparse("offcputime", "Summarize off-cpu time") | |
| parser:flag("-u --user-only") | |
| parser:option("-p --pid"):convert(tonumber) | |
| parser:flag("-f --folded") | |
| parser:option("-d --duration", "duration to trace for", 9999999):convert(tonumber) | |
| local args = parser:parse() | |
| local ksym = BPF.SymbolCache() | |
| local filter = "1" | |
| local MAXDEPTH = 20 | |
| if args.pid then | |
| filter = "pid == %d" % args.pid | |
| elseif args.user_only then | |
| filter = "!(prev->flags & PF_KTHREAD)" | |
| end | |
| local text = program:gsub("FILTER", filter) | |
| local b = BPF:new{text=text} | |
| b:attach_kprobe{event="finish_task_switch", fn_name="oncpu"} | |
| if BPF.num_open_kprobes() == 0 then | |
| print("no functions matched. quitting...") | |
| return | |
| end | |
| print("Sleeping for %d seconds..." % args.duration) | |
| pcall(utils.posix.sleep, args.duration) | |
| print("Tracing...") | |
| local counts = b:get_table("counts") | |
| local stack_traces = b:get_table("stack_traces") | |
| for k, v in counts:items() do | |
| for addr in stack_traces:walk(tonumber(k.stack_id)) do | |
| print(" %-16p %s" % {addr, ksym:resolve(addr)}) | |
| end | |
| print(" %-16s %s" % {"-", ffi.string(k.name)}) | |
| print(" %d\n" % tonumber(v)) | |
| end | |
| end |