ronnie
2022-10-23 4bf14332546635f50a1bf7f3df4c0a8e29643280
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#!/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