xref: /aosp_15_r20/external/bcc/tools/dbslower.py (revision 387f9dfdfa2baef462e92476d413c7bc2470293e)
1*387f9dfdSAndroid Build Coastguard Worker#!/usr/bin/env python
2*387f9dfdSAndroid Build Coastguard Worker#
3*387f9dfdSAndroid Build Coastguard Worker# dbslower      Trace MySQL and PostgreSQL queries slower than a threshold.
4*387f9dfdSAndroid Build Coastguard Worker#
5*387f9dfdSAndroid Build Coastguard Worker# USAGE: dbslower [-v] [-p PID [PID ...]] [-b PATH_TO_BINARY] [-m THRESHOLD]
6*387f9dfdSAndroid Build Coastguard Worker#                 {mysql,postgres}
7*387f9dfdSAndroid Build Coastguard Worker#
8*387f9dfdSAndroid Build Coastguard Worker# By default, a threshold of 1ms is used. Set the threshold to 0 to trace all
9*387f9dfdSAndroid Build Coastguard Worker# queries (verbose).
10*387f9dfdSAndroid Build Coastguard Worker#
11*387f9dfdSAndroid Build Coastguard Worker# Script works in two different modes:
12*387f9dfdSAndroid Build Coastguard Worker# 1) USDT probes, which means it needs MySQL and PostgreSQL built with
13*387f9dfdSAndroid Build Coastguard Worker# USDT (DTrace) support.
14*387f9dfdSAndroid Build Coastguard Worker# 2) uprobe and uretprobe on exported function of binary specified by
15*387f9dfdSAndroid Build Coastguard Worker# PATH_TO_BINARY parameter. (At the moment only MySQL support)
16*387f9dfdSAndroid Build Coastguard Worker#
17*387f9dfdSAndroid Build Coastguard Worker# If no PID or PATH_TO_BINARY is provided, the script attempts to discover
18*387f9dfdSAndroid Build Coastguard Worker# all MySQL or PostgreSQL database processes and uses USDT probes.
19*387f9dfdSAndroid Build Coastguard Worker#
20*387f9dfdSAndroid Build Coastguard Worker# Strongly inspired by Brendan Gregg's work on the mysqld_qslower script.
21*387f9dfdSAndroid Build Coastguard Worker#
22*387f9dfdSAndroid Build Coastguard Worker# Copyright 2017, Sasha Goldshtein
23*387f9dfdSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0
24*387f9dfdSAndroid Build Coastguard Worker#
25*387f9dfdSAndroid Build Coastguard Worker# 15-Feb-2017   Sasha Goldshtein   Created this.
26*387f9dfdSAndroid Build Coastguard Worker
27*387f9dfdSAndroid Build Coastguard Workerfrom bcc import BPF, USDT
28*387f9dfdSAndroid Build Coastguard Workerimport argparse
29*387f9dfdSAndroid Build Coastguard Workerimport re
30*387f9dfdSAndroid Build Coastguard Workerimport subprocess
31*387f9dfdSAndroid Build Coastguard Worker
32*387f9dfdSAndroid Build Coastguard Workerexamples = """examples:
33*387f9dfdSAndroid Build Coastguard Worker    dbslower postgres            # trace PostgreSQL queries slower than 1ms
34*387f9dfdSAndroid Build Coastguard Worker    dbslower postgres -p 188 322 # trace specific PostgreSQL processes
35*387f9dfdSAndroid Build Coastguard Worker    dbslower mysql -p 480 -m 30  # trace MySQL queries slower than 30ms
36*387f9dfdSAndroid Build Coastguard Worker    dbslower mysql -p 480 -v     # trace MySQL queries & print the BPF program
37*387f9dfdSAndroid Build Coastguard Worker    dbslower mysql -x $(which mysqld)  # trace MySQL queries with uprobes
38*387f9dfdSAndroid Build Coastguard Worker"""
39*387f9dfdSAndroid Build Coastguard Workerparser = argparse.ArgumentParser(
40*387f9dfdSAndroid Build Coastguard Worker    description="",
41*387f9dfdSAndroid Build Coastguard Worker    formatter_class=argparse.RawDescriptionHelpFormatter,
42*387f9dfdSAndroid Build Coastguard Worker    epilog=examples)
43*387f9dfdSAndroid Build Coastguard Workerparser.add_argument("-v", "--verbose", action="store_true",
44*387f9dfdSAndroid Build Coastguard Worker    help="print the BPF program")
45*387f9dfdSAndroid Build Coastguard Workerparser.add_argument("db", choices=["mysql", "postgres"],
46*387f9dfdSAndroid Build Coastguard Worker    help="the database engine to use")
47*387f9dfdSAndroid Build Coastguard Workerparser.add_argument("-p", "--pid", type=int, nargs='*',
48*387f9dfdSAndroid Build Coastguard Worker    dest="pids", metavar="PID", help="the pid(s) to trace")
49*387f9dfdSAndroid Build Coastguard Workerparser.add_argument("-x", "--exe", type=str,
50*387f9dfdSAndroid Build Coastguard Worker    dest="path", metavar="PATH", help="path to binary")
51*387f9dfdSAndroid Build Coastguard Workerparser.add_argument("-m", "--threshold", type=int, default=1,
52*387f9dfdSAndroid Build Coastguard Worker    help="trace queries slower than this threshold (ms)")
53*387f9dfdSAndroid Build Coastguard Workerparser.add_argument("--ebpf", action="store_true",
54*387f9dfdSAndroid Build Coastguard Worker    help=argparse.SUPPRESS)
55*387f9dfdSAndroid Build Coastguard Workerargs = parser.parse_args()
56*387f9dfdSAndroid Build Coastguard Worker
57*387f9dfdSAndroid Build Coastguard Workerthreshold_ns = args.threshold * 1000000
58*387f9dfdSAndroid Build Coastguard Worker
59*387f9dfdSAndroid Build Coastguard Workermode = "USDT"
60*387f9dfdSAndroid Build Coastguard Workerif args.path and not args.pids:
61*387f9dfdSAndroid Build Coastguard Worker    if args.db == "mysql":
62*387f9dfdSAndroid Build Coastguard Worker        regex = "\\w+dispatch_command\\w+"
63*387f9dfdSAndroid Build Coastguard Worker        symbols = BPF.get_user_functions_and_addresses(args.path, regex)
64*387f9dfdSAndroid Build Coastguard Worker
65*387f9dfdSAndroid Build Coastguard Worker        if len(symbols) == 0:
66*387f9dfdSAndroid Build Coastguard Worker            print("Can't find function 'dispatch_command' in %s" % (args.path))
67*387f9dfdSAndroid Build Coastguard Worker            exit(1)
68*387f9dfdSAndroid Build Coastguard Worker
69*387f9dfdSAndroid Build Coastguard Worker        (mysql_func_name, addr) = symbols[0]
70*387f9dfdSAndroid Build Coastguard Worker
71*387f9dfdSAndroid Build Coastguard Worker        if mysql_func_name.find(b'COM_DATA') >= 0:
72*387f9dfdSAndroid Build Coastguard Worker            mode = "MYSQL57"
73*387f9dfdSAndroid Build Coastguard Worker        else:
74*387f9dfdSAndroid Build Coastguard Worker            mode = "MYSQL56"
75*387f9dfdSAndroid Build Coastguard Worker    else:
76*387f9dfdSAndroid Build Coastguard Worker        # Placeholder for PostrgeSQL
77*387f9dfdSAndroid Build Coastguard Worker        # Look on functions initStringInfo, pgstat_report_activity, EndCommand,
78*387f9dfdSAndroid Build Coastguard Worker        # NullCommand
79*387f9dfdSAndroid Build Coastguard Worker        print("Sorry at the moment PostgreSQL supports only USDT")
80*387f9dfdSAndroid Build Coastguard Worker        exit(1)
81*387f9dfdSAndroid Build Coastguard Worker
82*387f9dfdSAndroid Build Coastguard Workerprogram = """
83*387f9dfdSAndroid Build Coastguard Worker#include <uapi/linux/ptrace.h>
84*387f9dfdSAndroid Build Coastguard Worker
85*387f9dfdSAndroid Build Coastguard WorkerDEFINE_THRESHOLD
86*387f9dfdSAndroid Build Coastguard WorkerDEFINE_USDT
87*387f9dfdSAndroid Build Coastguard WorkerDEFINE_MYSQL56
88*387f9dfdSAndroid Build Coastguard WorkerDEFINE_MYSQL57
89*387f9dfdSAndroid Build Coastguard Worker
90*387f9dfdSAndroid Build Coastguard Workerstruct temp_t {
91*387f9dfdSAndroid Build Coastguard Worker    u64 timestamp;
92*387f9dfdSAndroid Build Coastguard Worker#ifdef USDT
93*387f9dfdSAndroid Build Coastguard Worker    char *query;
94*387f9dfdSAndroid Build Coastguard Worker#else
95*387f9dfdSAndroid Build Coastguard Worker    /*
96*387f9dfdSAndroid Build Coastguard Worker    MySQL clears query packet before uretprobe call - so copy query in advance
97*387f9dfdSAndroid Build Coastguard Worker    */
98*387f9dfdSAndroid Build Coastguard Worker    char query[256];
99*387f9dfdSAndroid Build Coastguard Worker#endif //USDT
100*387f9dfdSAndroid Build Coastguard Worker};
101*387f9dfdSAndroid Build Coastguard Worker
102*387f9dfdSAndroid Build Coastguard Workerstruct data_t {
103*387f9dfdSAndroid Build Coastguard Worker    u32 pid;
104*387f9dfdSAndroid Build Coastguard Worker    u64 timestamp;
105*387f9dfdSAndroid Build Coastguard Worker    u64 duration;
106*387f9dfdSAndroid Build Coastguard Worker    char query[256];
107*387f9dfdSAndroid Build Coastguard Worker};
108*387f9dfdSAndroid Build Coastguard Worker
109*387f9dfdSAndroid Build Coastguard WorkerBPF_HASH(temp, u64, struct temp_t);
110*387f9dfdSAndroid Build Coastguard WorkerBPF_PERF_OUTPUT(events);
111*387f9dfdSAndroid Build Coastguard Worker
112*387f9dfdSAndroid Build Coastguard Workerint query_start(struct pt_regs *ctx) {
113*387f9dfdSAndroid Build Coastguard Worker
114*387f9dfdSAndroid Build Coastguard Worker#if defined(MYSQL56) || defined(MYSQL57)
115*387f9dfdSAndroid Build Coastguard Worker/*
116*387f9dfdSAndroid Build Coastguard WorkerTrace only packets with enum_server_command == COM_QUERY
117*387f9dfdSAndroid Build Coastguard Worker*/
118*387f9dfdSAndroid Build Coastguard Worker    #ifdef MYSQL56
119*387f9dfdSAndroid Build Coastguard Worker    u64 command  = (u64) PT_REGS_PARM1(ctx);
120*387f9dfdSAndroid Build Coastguard Worker    #else //MYSQL57
121*387f9dfdSAndroid Build Coastguard Worker    u64 command  = (u64) PT_REGS_PARM3(ctx);
122*387f9dfdSAndroid Build Coastguard Worker    #endif
123*387f9dfdSAndroid Build Coastguard Worker    if (command != 3) return 0;
124*387f9dfdSAndroid Build Coastguard Worker#endif
125*387f9dfdSAndroid Build Coastguard Worker
126*387f9dfdSAndroid Build Coastguard Worker    struct temp_t tmp = {};
127*387f9dfdSAndroid Build Coastguard Worker    tmp.timestamp = bpf_ktime_get_ns();
128*387f9dfdSAndroid Build Coastguard Worker
129*387f9dfdSAndroid Build Coastguard Worker#if defined(MYSQL56)
130*387f9dfdSAndroid Build Coastguard Worker    bpf_probe_read_user(&tmp.query, sizeof(tmp.query), (void*) PT_REGS_PARM3(ctx));
131*387f9dfdSAndroid Build Coastguard Worker#elif defined(MYSQL57)
132*387f9dfdSAndroid Build Coastguard Worker    void* st = (void*) PT_REGS_PARM2(ctx);
133*387f9dfdSAndroid Build Coastguard Worker    char* query;
134*387f9dfdSAndroid Build Coastguard Worker    bpf_probe_read_user(&query, sizeof(query), st);
135*387f9dfdSAndroid Build Coastguard Worker    bpf_probe_read_user(&tmp.query, sizeof(tmp.query), query);
136*387f9dfdSAndroid Build Coastguard Worker#else //USDT
137*387f9dfdSAndroid Build Coastguard Worker    bpf_usdt_readarg(1, ctx, &tmp.query);
138*387f9dfdSAndroid Build Coastguard Worker#endif
139*387f9dfdSAndroid Build Coastguard Worker
140*387f9dfdSAndroid Build Coastguard Worker    u64 pid = bpf_get_current_pid_tgid();
141*387f9dfdSAndroid Build Coastguard Worker    temp.update(&pid, &tmp);
142*387f9dfdSAndroid Build Coastguard Worker    return 0;
143*387f9dfdSAndroid Build Coastguard Worker}
144*387f9dfdSAndroid Build Coastguard Worker
145*387f9dfdSAndroid Build Coastguard Workerint query_end(struct pt_regs *ctx) {
146*387f9dfdSAndroid Build Coastguard Worker    struct temp_t *tempp;
147*387f9dfdSAndroid Build Coastguard Worker    u64 pid = bpf_get_current_pid_tgid();
148*387f9dfdSAndroid Build Coastguard Worker    tempp = temp.lookup(&pid);
149*387f9dfdSAndroid Build Coastguard Worker    if (!tempp)
150*387f9dfdSAndroid Build Coastguard Worker        return 0;
151*387f9dfdSAndroid Build Coastguard Worker
152*387f9dfdSAndroid Build Coastguard Worker    u64 delta = bpf_ktime_get_ns() - tempp->timestamp;
153*387f9dfdSAndroid Build Coastguard Worker#ifdef THRESHOLD
154*387f9dfdSAndroid Build Coastguard Worker    if (delta >= THRESHOLD) {
155*387f9dfdSAndroid Build Coastguard Worker#endif //THRESHOLD
156*387f9dfdSAndroid Build Coastguard Worker        struct data_t data = {};
157*387f9dfdSAndroid Build Coastguard Worker        data.pid = pid >> 32;   // only process id
158*387f9dfdSAndroid Build Coastguard Worker        data.timestamp = tempp->timestamp;
159*387f9dfdSAndroid Build Coastguard Worker        data.duration = delta;
160*387f9dfdSAndroid Build Coastguard Worker#if defined(MYSQL56) || defined(MYSQL57)
161*387f9dfdSAndroid Build Coastguard Worker	// We already copied string to the bpf stack. Hence use bpf_probe_read_kernel()
162*387f9dfdSAndroid Build Coastguard Worker        bpf_probe_read_kernel(&data.query, sizeof(data.query), tempp->query);
163*387f9dfdSAndroid Build Coastguard Worker#else
164*387f9dfdSAndroid Build Coastguard Worker	// USDT - we didnt copy string to the bpf stack before.
165*387f9dfdSAndroid Build Coastguard Worker        bpf_probe_read_user(&data.query, sizeof(data.query), tempp->query);
166*387f9dfdSAndroid Build Coastguard Worker#endif
167*387f9dfdSAndroid Build Coastguard Worker        events.perf_submit(ctx, &data, sizeof(data));
168*387f9dfdSAndroid Build Coastguard Worker#ifdef THRESHOLD
169*387f9dfdSAndroid Build Coastguard Worker    }
170*387f9dfdSAndroid Build Coastguard Worker#endif //THRESHOLD
171*387f9dfdSAndroid Build Coastguard Worker    temp.delete(&pid);
172*387f9dfdSAndroid Build Coastguard Worker    return 0;
173*387f9dfdSAndroid Build Coastguard Worker};
174*387f9dfdSAndroid Build Coastguard Worker""".replace("DEFINE_USDT", "#define USDT" if mode == "USDT" else "") \
175*387f9dfdSAndroid Build Coastguard Worker   .replace("DEFINE_MYSQL56", "#define MYSQL56" if mode == "MYSQL56" else "") \
176*387f9dfdSAndroid Build Coastguard Worker   .replace("DEFINE_MYSQL57", "#define MYSQL57" if mode == "MYSQL57" else "") \
177*387f9dfdSAndroid Build Coastguard Worker   .replace("DEFINE_THRESHOLD",
178*387f9dfdSAndroid Build Coastguard Worker            "#define THRESHOLD %d" % threshold_ns if threshold_ns > 0 else "")
179*387f9dfdSAndroid Build Coastguard Worker
180*387f9dfdSAndroid Build Coastguard Workerif mode.startswith("MYSQL"):
181*387f9dfdSAndroid Build Coastguard Worker    # Uprobes mode
182*387f9dfdSAndroid Build Coastguard Worker    bpf = BPF(text=program)
183*387f9dfdSAndroid Build Coastguard Worker    bpf.attach_uprobe(name=args.path, sym=mysql_func_name,
184*387f9dfdSAndroid Build Coastguard Worker                      fn_name="query_start")
185*387f9dfdSAndroid Build Coastguard Worker    bpf.attach_uretprobe(name=args.path, sym=mysql_func_name,
186*387f9dfdSAndroid Build Coastguard Worker                         fn_name="query_end")
187*387f9dfdSAndroid Build Coastguard Workerelse:
188*387f9dfdSAndroid Build Coastguard Worker    # USDT mode
189*387f9dfdSAndroid Build Coastguard Worker    if not args.pids or len(args.pids) == 0:
190*387f9dfdSAndroid Build Coastguard Worker        if args.db == "mysql":
191*387f9dfdSAndroid Build Coastguard Worker            args.pids = map(int, subprocess.check_output(
192*387f9dfdSAndroid Build Coastguard Worker                                            "pidof mysqld".split()).split())
193*387f9dfdSAndroid Build Coastguard Worker        elif args.db == "postgres":
194*387f9dfdSAndroid Build Coastguard Worker            args.pids = map(int, subprocess.check_output(
195*387f9dfdSAndroid Build Coastguard Worker                                            "pidof postgres".split()).split())
196*387f9dfdSAndroid Build Coastguard Worker
197*387f9dfdSAndroid Build Coastguard Worker    usdts = list(map(lambda pid: USDT(pid=pid), args.pids))
198*387f9dfdSAndroid Build Coastguard Worker    for usdt in usdts:
199*387f9dfdSAndroid Build Coastguard Worker        usdt.enable_probe("query__start", "query_start")
200*387f9dfdSAndroid Build Coastguard Worker        usdt.enable_probe("query__done", "query_end")
201*387f9dfdSAndroid Build Coastguard Worker    if args.verbose:
202*387f9dfdSAndroid Build Coastguard Worker        print('\n'.join(map(lambda u: u.get_text(), usdts)))
203*387f9dfdSAndroid Build Coastguard Worker
204*387f9dfdSAndroid Build Coastguard Worker    bpf = BPF(text=program, usdt_contexts=usdts)
205*387f9dfdSAndroid Build Coastguard Worker
206*387f9dfdSAndroid Build Coastguard Workerif args.verbose or args.ebpf:
207*387f9dfdSAndroid Build Coastguard Worker    print(program)
208*387f9dfdSAndroid Build Coastguard Worker    if args.ebpf:
209*387f9dfdSAndroid Build Coastguard Worker        exit()
210*387f9dfdSAndroid Build Coastguard Worker
211*387f9dfdSAndroid Build Coastguard Workerstart = BPF.monotonic_time()
212*387f9dfdSAndroid Build Coastguard Worker
213*387f9dfdSAndroid Build Coastguard Workerdef print_event(cpu, data, size):
214*387f9dfdSAndroid Build Coastguard Worker    event = bpf["events"].event(data)
215*387f9dfdSAndroid Build Coastguard Worker    print("%-14.6f %-7d %8.3f %s" % (
216*387f9dfdSAndroid Build Coastguard Worker        float(event.timestamp - start) / 1000000000,
217*387f9dfdSAndroid Build Coastguard Worker        event.pid, float(event.duration) / 1000000, event.query))
218*387f9dfdSAndroid Build Coastguard Worker
219*387f9dfdSAndroid Build Coastguard Workerif mode.startswith("MYSQL"):
220*387f9dfdSAndroid Build Coastguard Worker    print("Tracing database queries for application %s slower than %d ms..." %
221*387f9dfdSAndroid Build Coastguard Worker        (args.path, args.threshold))
222*387f9dfdSAndroid Build Coastguard Workerelse:
223*387f9dfdSAndroid Build Coastguard Worker    print("Tracing database queries for pids %s slower than %d ms..." %
224*387f9dfdSAndroid Build Coastguard Worker        (', '.join(map(str, args.pids)), args.threshold))
225*387f9dfdSAndroid Build Coastguard Worker
226*387f9dfdSAndroid Build Coastguard Workerprint("%-14s %-7s %8s %s" % ("TIME(s)", "PID", "MS", "QUERY"))
227*387f9dfdSAndroid Build Coastguard Worker
228*387f9dfdSAndroid Build Coastguard Workerbpf["events"].open_perf_buffer(print_event, page_cnt=64)
229*387f9dfdSAndroid Build Coastguard Workerwhile True:
230*387f9dfdSAndroid Build Coastguard Worker    try:
231*387f9dfdSAndroid Build Coastguard Worker        bpf.perf_buffer_poll()
232*387f9dfdSAndroid Build Coastguard Worker    except KeyboardInterrupt:
233*387f9dfdSAndroid Build Coastguard Worker        exit()
234