tools/ebpf/trylock-stats/trylock-stats.py (70 lines of code) (raw):
#!/usr/bin/env python
#
# bcc tool to observe pthread mutex trylock
#
# Based on https://github.com/goldshtn/linux-tracing-workshop/blob/master/lockstat-solution.py
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you 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.
import os
import subprocess
import sys
import itertools
from time import sleep
from bcc import BPF
from argparse import ArgumentParser
BPF_SRC_FILE = "./trylock-stats.bpf.c"
def glibc_version(glibc_path):
major = 0
minor = 0
result = subprocess.run([glibc_path], capture_output=True, text=True)
if result:
version_str = result.stdout.splitlines()[0].split()[-1]
version = version_str.split(".")
major = int(version[0])
minor = int(version[1])
return (major, minor)
def attach(bpf, pid, glibc_path):
libname = "pthread"
# glibc removed "libpthread" from 2.34
(major, minor) = glibc_version(glibc_path)
if major >= 2 and minor >= 34:
libname = "c"
bpf.attach_uprobe(name=libname, sym="pthread_mutex_trylock", fn_name="probe_mutex_lock", pid=pid)
bpf.attach_uretprobe(name=libname, sym="pthread_mutex_trylock", fn_name="probe_mutex_trylock_return", pid=pid)
bpf.attach_uprobe(name=libname, sym="pthread_mutex_unlock", fn_name="probe_mutex_unlock", pid=pid)
def print_frame(bpf, pid, addr):
print("\t\t%16s (%x)" % (bpf.sym(addr, pid, show_module=True, show_offset=True), addr))
def print_stack(bpf, pid, stacks, stack_id):
for addr in stacks.walk(stack_id):
print_frame(bpf, pid, addr)
def run(args):
pid = args.pid
bpf = BPF(src_file=BPF_SRC_FILE)
attach(bpf, pid, args.glibc_path)
init_stacks = bpf["init_stacks"]
stacks = bpf["stacks"]
locks = bpf["locks"]
mutex_lock_hist = bpf["mutex_lock_hist"]
mutex_wait_hist = bpf["mutex_wait_hist"]
sleep(args.duration)
mutex_ids = {}
next_mutex_id = 1
for k, v in init_stacks.items():
mutex_id = "#%d" % next_mutex_id
next_mutex_id += 1
mutex_ids[k.value] = mutex_id
print("init stack for mutex %x (%s)" % (k.value, mutex_id))
print_stack(bpf, pid, stacks, v.value)
print("")
grouper = lambda kv: kv[0].tid
sorted_by_thread = sorted(locks.items(), key=grouper)
locks_by_thread = itertools.groupby(sorted_by_thread, grouper)
for tid, items in locks_by_thread:
print("thread %d" % tid)
for k, v in sorted(items, key=lambda kv: -kv[1].fail_count):
mutex_descr = mutex_ids[k.mtx] if k.mtx in mutex_ids else bpf.sym(k.mtx, pid)
print(
"\tmutex %s ::: wait time %.2fus ::: hold time %.2fus ::: enter count %d ::: try-lock failure count %d" %
(mutex_descr, v.wait_time_ns / 1000.0, v.lock_time_ns / 1000.0, v.enter_count, v.fail_count))
print_stack(bpf, pid, stacks, k.lock_stack_id)
print("")
mutex_wait_hist.print_log2_hist(val_type="wait time (us)")
mutex_lock_hist.print_log2_hist(val_type="hold time (us)")
if __name__ == "__main__":
parser = ArgumentParser()
parser.add_argument("-p", "--pid", dest="pid", help="process id", type=int, required=True)
parser.add_argument("-d", "--duration", dest="duration", help="duration to run", default=10, type=float)
parser.add_argument("-l", "--glibc", dest="glibc_path", help="path to the glibc", default="/lib64/libc.so.6")
run(parser.parse_args())