1#!/usr/bin/env bcc-lua
2--[[
3Copyright 2016 Marek Vavrusa <mvavrusa@cloudflare.com>
4
5Licensed under the Apache License, Version 2.0 (the "License");
6you may not use this file except in compliance with the License.
7You may obtain a copy of the License at
8
9http://www.apache.org/licenses/LICENSE-2.0
10
11Unless required by applicable law or agreed to in writing, software
12distributed under the License is distributed on an "AS IS" BASIS,
13WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14See the License for the specific language governing permissions and
15limitations under the License.
16]]
17-- Summarize off-CPU time by stack trace
18-- Related tool: https://github.com/iovisor/bcc/blob/master/tools/offcputime.py
19local ffi = require('ffi')
20local bpf = require('bpf')
21local S = require('syscall')
22-- Create BPF maps
23-- TODO: made smaller to fit default memory limits
24local key_t = 'struct { char name[16]; int32_t stack_id; }'
25local starts = assert(bpf.map('hash', 128, ffi.typeof('uint32_t'), ffi.typeof('uint64_t')))
26local counts = assert(bpf.map('hash', 128, ffi.typeof(key_t), ffi.typeof('uint64_t')))
27local stack_traces = assert(bpf.map('stack_trace', 16))
28-- Open tracepoint and attach BPF program
29-- The 'arg' parses tracepoint format automatically
30local tp = bpf.tracepoint('sched/sched_switch', function (arg)
31	-- Update previous thread sleep time
32	local pid = arg.prev_pid
33	local now = time()
34	starts[pid] = now
35	-- Calculate current thread's delta time
36	pid = arg.next_pid
37	local from = starts[pid]
38	if not from then
39		return 0
40	end
41	local delta = (now - from) / 1000
42	starts[pid] = nil
43	-- Check if the delta is below 1us
44	if delta < 1 then
45		return
46	end
47	-- Create key for this thread
48	local key = ffi.new(key_t)
49	comm(key.name)
50	key.stack_id = stack_id(stack_traces, BPF.F_FAST_STACK_CMP)
51	-- Update current thread off cpu time with delta
52	local val = counts[key]
53	if not val then
54		counts[key] = 0
55	end
56	xadd(counts[key], delta)
57end, 0, -1)
58-- Helper: load kernel symbols
59ffi.cdef 'unsigned long long strtoull(const char *, char **, int);'
60local ksyms = {}
61for l in io.lines('/proc/kallsyms') do
62	local addr, sym = l:match '(%w+) %w (%S+)'
63	if addr then ksyms[ffi.C.strtoull(addr, nil, 16)] = sym end
64end
65-- User-space part of the program
66while true do
67	for k,v in counts.pairs,counts,nil do
68		local s = ''
69		local traces = stack_traces[k.stack_id]
70		if traces then
71			for i, ip in ipairs(traces) do
72				s = s .. string.format("    %-16p %s", ip, ksyms[ip])
73			end
74		end
75		s = s .. string.format("    %-16s %s", "-", ffi.string(k.name))
76		s = s .. string.format("        %d", tonumber(v))
77		print(s)
78	end
79	S.sleep(1)
80end
81