| #!/usr/bin/env bcc-lua |
| --[[ |
| Copyright 2016 Marek Vavrusa <mvavrusa@cloudflare.com> |
| |
| 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. |
| ]] |
| -- Summarize off-CPU time by stack trace |
| -- Related tool: https://github.com/iovisor/bcc/blob/master/tools/offcputime.py |
| local ffi = require('ffi') |
| local bpf = require('bpf') |
| local S = require('syscall') |
| -- Create BPF maps |
| -- TODO: made smaller to fit default memory limits |
| local key_t = 'struct { char name[16]; int32_t stack_id; }' |
| local starts = assert(bpf.map('hash', 128, ffi.typeof('uint32_t'), ffi.typeof('uint64_t'))) |
| local counts = assert(bpf.map('hash', 128, ffi.typeof(key_t), ffi.typeof('uint64_t'))) |
| local stack_traces = assert(bpf.map('stack_trace', 16)) |
| -- Open tracepoint and attach BPF program |
| -- The 'arg' parses tracepoint format automatically |
| local tp = bpf.tracepoint('sched/sched_switch', function (arg) |
| -- Update previous thread sleep time |
| local pid = arg.prev_pid |
| local now = time() |
| starts[pid] = now |
| -- Calculate current thread's delta time |
| pid = arg.next_pid |
| local from = starts[pid] |
| if not from then |
| return 0 |
| end |
| local delta = (now - from) / 1000 |
| starts[pid] = nil |
| -- Check if the delta is below 1us |
| if delta < 1 then |
| return |
| end |
| -- Create key for this thread |
| local key = ffi.new(key_t) |
| comm(key.name) |
| key.stack_id = stack_id(stack_traces, BPF.F_FAST_STACK_CMP) |
| -- Update current thread off cpu time with delta |
| local val = counts[key] |
| if not val then |
| counts[key] = 0 |
| end |
| xadd(counts[key], delta) |
| end, 0, -1) |
| -- Helper: load kernel symbols |
| ffi.cdef 'unsigned long long strtoull(const char *, char **, int);' |
| local ksyms = {} |
| for l in io.lines('/proc/kallsyms') do |
| local addr, sym = l:match '(%w+) %w (%S+)' |
| if addr then ksyms[ffi.C.strtoull(addr, nil, 16)] = sym end |
| end |
| -- User-space part of the program |
| while true do |
| for k,v in counts.pairs,counts,nil do |
| local s = '' |
| local traces = stack_traces[k.stack_id] |
| if traces then |
| for i, ip in ipairs(traces) do |
| s = s .. string.format(" %-16p %s", ip, ksyms[ip]) |
| end |
| end |
| s = s .. string.format(" %-16s %s", "-", ffi.string(k.name)) |
| s = s .. string.format(" %d", tonumber(v)) |
| print(s) |
| end |
| S.sleep(1) |
| end |