blob: 1d459176e934113ee942b9adf8616f6e8ca7caf9 [file] [log] [blame]
Alexey Ivanovcc01a9c2019-01-16 09:50:46 -08001#!/usr/bin/python
Sasha Goldshteindd37d632017-02-09 05:44:43 -05002#
3# dbslower Trace MySQL and PostgreSQL queries slower than a threshold.
4#
Paul Chaignon46017922017-10-07 11:06:28 +02005# USAGE: dbslower [-v] [-p PID [PID ...]] [-b PATH_TO_BINARY] [-m THRESHOLD]
6# {mysql,postgres}
Sasha Goldshteindd37d632017-02-09 05:44:43 -05007#
8# By default, a threshold of 1ms is used. Set the threshold to 0 to trace all
Paul Chaignon46017922017-10-07 11:06:28 +02009# queries (verbose).
10#
11# Script works in two different modes:
12# 1) USDT probes, which means it needs MySQL and PostgreSQL built with
Igor Mazur5f7035e2017-07-18 10:06:34 +030013# USDT (DTrace) support.
Paul Chaignon46017922017-10-07 11:06:28 +020014# 2) uprobe and uretprobe on exported function of binary specified by
Igor Mazur5f7035e2017-07-18 10:06:34 +030015# PATH_TO_BINARY parameter. (At the moment only MySQL support)
Paul Chaignon46017922017-10-07 11:06:28 +020016#
Igor Mazur5f7035e2017-07-18 10:06:34 +030017# If no PID or PATH_TO_BINARY is provided, the script attempts to discover
18# all MySQL or PostgreSQL database processes and uses USDT probes.
Sasha Goldshteindd37d632017-02-09 05:44:43 -050019#
20# Strongly inspired by Brendan Gregg's work on the mysqld_qslower script.
21#
Paul Chaignon956ca1c2017-03-04 20:07:56 +010022# Copyright 2017, Sasha Goldshtein
Sasha Goldshteindd37d632017-02-09 05:44:43 -050023# Licensed under the Apache License, Version 2.0
24#
25# 15-Feb-2017 Sasha Goldshtein Created this.
26
27from bcc import BPF, USDT
28import argparse
Igor Mazur5f7035e2017-07-18 10:06:34 +030029import re
Sasha Goldshteindd37d632017-02-09 05:44:43 -050030import subprocess
31
32examples = """examples:
33 dbslower postgres # trace PostgreSQL queries slower than 1ms
34 dbslower postgres -p 188 322 # trace specific PostgreSQL processes
35 dbslower mysql -p 480 -m 30 # trace MySQL queries slower than 30ms
Paul Chaignon956ca1c2017-03-04 20:07:56 +010036 dbslower mysql -p 480 -v # trace MySQL queries & print the BPF program
Igor Mazur5f7035e2017-07-18 10:06:34 +030037 dbslower mysql -x $(which mysqld) # trace MySQL queries with uprobes
Sasha Goldshteindd37d632017-02-09 05:44:43 -050038"""
39parser = argparse.ArgumentParser(
40 description="",
41 formatter_class=argparse.RawDescriptionHelpFormatter,
42 epilog=examples)
43parser.add_argument("-v", "--verbose", action="store_true",
44 help="print the BPF program")
45parser.add_argument("db", choices=["mysql", "postgres"],
46 help="the database engine to use")
47parser.add_argument("-p", "--pid", type=int, nargs='*',
48 dest="pids", metavar="PID", help="the pid(s) to trace")
Igor Mazur5f7035e2017-07-18 10:06:34 +030049parser.add_argument("-x", "--exe", type=str,
50 dest="path", metavar="PATH", help="path to binary")
Sasha Goldshteindd37d632017-02-09 05:44:43 -050051parser.add_argument("-m", "--threshold", type=int, default=1,
52 help="trace queries slower than this threshold (ms)")
Nathan Scottcf0792f2018-02-02 16:56:50 +110053parser.add_argument("--ebpf", action="store_true",
54 help=argparse.SUPPRESS)
Sasha Goldshteindd37d632017-02-09 05:44:43 -050055args = parser.parse_args()
56
Sasha Goldshteindd37d632017-02-09 05:44:43 -050057threshold_ns = args.threshold * 1000000
58
Igor Mazur5f7035e2017-07-18 10:06:34 +030059mode = "USDT"
60if args.path and not args.pids:
61 if args.db == "mysql":
Paul Chaignon46017922017-10-07 11:06:28 +020062 regex = "\\w+dispatch_command\\w+"
63 symbols = BPF.get_user_functions_and_addresses(args.path, regex)
Igor Mazur5f7035e2017-07-18 10:06:34 +030064
65 if len(symbols) == 0:
66 print("Can't find function 'dispatch_command' in %s" % (args.path))
67 exit(1)
Paul Chaignon46017922017-10-07 11:06:28 +020068
Igor Mazur5f7035e2017-07-18 10:06:34 +030069 (mysql_func_name, addr) = symbols[0]
70
Jerome Marchandbffd94f2018-12-17 17:04:07 +010071 if mysql_func_name.find(b'COM_DATA') >= 0:
Igor Mazur5f7035e2017-07-18 10:06:34 +030072 mode = "MYSQL57"
73 else:
74 mode = "MYSQL56"
75 else:
76 # Placeholder for PostrgeSQL
Paul Chaignon46017922017-10-07 11:06:28 +020077 # Look on functions initStringInfo, pgstat_report_activity, EndCommand,
78 # NullCommand
Igor Mazur5f7035e2017-07-18 10:06:34 +030079 print("Sorry at the moment PostgreSQL supports only USDT")
80 exit(1)
81
Sasha Goldshteindd37d632017-02-09 05:44:43 -050082program = """
Sasha Goldshteinaa124dd2017-03-31 17:16:45 -040083#include <uapi/linux/ptrace.h>
Sasha Goldshteindd37d632017-02-09 05:44:43 -050084
Igor Mazur5f7035e2017-07-18 10:06:34 +030085DEFINE_THRESHOLD
86DEFINE_USDT
87DEFINE_MYSQL56
88DEFINE_MYSQL57
89
Sasha Goldshteindd37d632017-02-09 05:44:43 -050090struct temp_t {
91 u64 timestamp;
Igor Mazur5f7035e2017-07-18 10:06:34 +030092#ifdef USDT
Sasha Goldshteindd37d632017-02-09 05:44:43 -050093 char *query;
Igor Mazur5f7035e2017-07-18 10:06:34 +030094#else
95 /*
96 MySQL clears query packet before uretprobe call - so copy query in advance
97 */
98 char query[256];
99#endif //USDT
Sasha Goldshteindd37d632017-02-09 05:44:43 -0500100};
101
102struct data_t {
103 u64 pid;
104 u64 timestamp;
105 u64 duration;
106 char query[256];
107};
108
109BPF_HASH(temp, u64, struct temp_t);
110BPF_PERF_OUTPUT(events);
111
Igor Mazur5f7035e2017-07-18 10:06:34 +0300112int query_start(struct pt_regs *ctx) {
113
114#if defined(MYSQL56) || defined(MYSQL57)
115/*
Paul Chaignon46017922017-10-07 11:06:28 +0200116Trace only packets with enum_server_command == COM_QUERY
Igor Mazur5f7035e2017-07-18 10:06:34 +0300117*/
118 #ifdef MYSQL56
119 u64 command = (u64) PT_REGS_PARM1(ctx);
120 #else //MYSQL57
121 u64 command = (u64) PT_REGS_PARM3(ctx);
122 #endif
123 if (command != 3) return 0;
124#endif
125
Sasha Goldshteindd37d632017-02-09 05:44:43 -0500126 struct temp_t tmp = {};
127 tmp.timestamp = bpf_ktime_get_ns();
Igor Mazur5f7035e2017-07-18 10:06:34 +0300128
129#if defined(MYSQL56)
Sumanth Korikkar023154c2020-04-20 05:54:57 -0500130 bpf_probe_read_user(&tmp.query, sizeof(tmp.query), (void*) PT_REGS_PARM3(ctx));
Igor Mazur5f7035e2017-07-18 10:06:34 +0300131#elif defined(MYSQL57)
132 void* st = (void*) PT_REGS_PARM2(ctx);
133 char* query;
Sumanth Korikkar023154c2020-04-20 05:54:57 -0500134 bpf_probe_read_user(&query, sizeof(query), st);
135 bpf_probe_read_user(&tmp.query, sizeof(tmp.query), query);
Igor Mazur5f7035e2017-07-18 10:06:34 +0300136#else //USDT
Sasha Goldshteindd37d632017-02-09 05:44:43 -0500137 bpf_usdt_readarg(1, ctx, &tmp.query);
Igor Mazur5f7035e2017-07-18 10:06:34 +0300138#endif
139
Sasha Goldshteindd37d632017-02-09 05:44:43 -0500140 u64 pid = bpf_get_current_pid_tgid();
141 temp.update(&pid, &tmp);
142 return 0;
143}
144
Igor Mazur5f7035e2017-07-18 10:06:34 +0300145int query_end(struct pt_regs *ctx) {
Sasha Goldshteindd37d632017-02-09 05:44:43 -0500146 struct temp_t *tempp;
147 u64 pid = bpf_get_current_pid_tgid();
148 tempp = temp.lookup(&pid);
149 if (!tempp)
150 return 0;
151
152 u64 delta = bpf_ktime_get_ns() - tempp->timestamp;
Igor Mazur5f7035e2017-07-18 10:06:34 +0300153#ifdef THRESHOLD
154 if (delta >= THRESHOLD) {
Paul Chaignon46017922017-10-07 11:06:28 +0200155#endif //THRESHOLD
Sasha Goldshteindd37d632017-02-09 05:44:43 -0500156 struct data_t data = {};
157 data.pid = pid >> 32; // only process id
158 data.timestamp = tempp->timestamp;
159 data.duration = delta;
Sumanth Korikkar023154c2020-04-20 05:54:57 -0500160#if defined(MYSQL56) || defined(MYSQL57)
Sumanth Korikkar7f6066d2020-05-20 10:49:56 -0500161 // We already copied string to the bpf stack. Hence use bpf_probe_read_kernel()
162 bpf_probe_read_kernel(&data.query, sizeof(data.query), tempp->query);
Sumanth Korikkar023154c2020-04-20 05:54:57 -0500163#else
164 // USDT - we didnt copy string to the bpf stack before.
165 bpf_probe_read_user(&data.query, sizeof(data.query), tempp->query);
166#endif
Sasha Goldshteindd37d632017-02-09 05:44:43 -0500167 events.perf_submit(ctx, &data, sizeof(data));
Igor Mazur5f7035e2017-07-18 10:06:34 +0300168#ifdef THRESHOLD
Sasha Goldshteindd37d632017-02-09 05:44:43 -0500169 }
Igor Mazur5f7035e2017-07-18 10:06:34 +0300170#endif //THRESHOLD
Sasha Goldshteindd37d632017-02-09 05:44:43 -0500171 temp.delete(&pid);
172 return 0;
Igor Mazur5f7035e2017-07-18 10:06:34 +0300173};
174""".replace("DEFINE_USDT", "#define USDT" if mode == "USDT" else "") \
175 .replace("DEFINE_MYSQL56", "#define MYSQL56" if mode == "MYSQL56" else "") \
176 .replace("DEFINE_MYSQL57", "#define MYSQL57" if mode == "MYSQL57" else "") \
Paul Chaignon46017922017-10-07 11:06:28 +0200177 .replace("DEFINE_THRESHOLD",
178 "#define THRESHOLD %d" % threshold_ns if threshold_ns > 0 else "")
Sasha Goldshteindd37d632017-02-09 05:44:43 -0500179
Igor Mazur5f7035e2017-07-18 10:06:34 +0300180if mode.startswith("MYSQL"):
181 # Uprobes mode
182 bpf = BPF(text=program)
Paul Chaignon46017922017-10-07 11:06:28 +0200183 bpf.attach_uprobe(name=args.path, sym=mysql_func_name,
184 fn_name="query_start")
185 bpf.attach_uretprobe(name=args.path, sym=mysql_func_name,
186 fn_name="query_end")
Igor Mazur5f7035e2017-07-18 10:06:34 +0300187else:
188 # USDT mode
189 if not args.pids or len(args.pids) == 0:
190 if args.db == "mysql":
191 args.pids = map(int, subprocess.check_output(
192 "pidof mysqld".split()).split())
193 elif args.db == "postgres":
194 args.pids = map(int, subprocess.check_output(
195 "pidof postgres".split()).split())
196
Jerome Marchandc9805f42020-06-10 18:29:11 +0200197 usdts = list(map(lambda pid: USDT(pid=pid), args.pids))
Igor Mazur5f7035e2017-07-18 10:06:34 +0300198 for usdt in usdts:
199 usdt.enable_probe("query__start", "query_start")
200 usdt.enable_probe("query__done", "query_end")
201 if args.verbose:
202 print('\n'.join(map(lambda u: u.get_text(), usdts)))
203
204 bpf = BPF(text=program, usdt_contexts=usdts)
Sasha Goldshteindd37d632017-02-09 05:44:43 -0500205
Nathan Scottcf0792f2018-02-02 16:56:50 +1100206if args.verbose or args.ebpf:
Sasha Goldshteindd37d632017-02-09 05:44:43 -0500207 print(program)
Nathan Scottcf0792f2018-02-02 16:56:50 +1100208 if args.ebpf:
209 exit()
Sasha Goldshteindd37d632017-02-09 05:44:43 -0500210
Sasha Goldshteindd37d632017-02-09 05:44:43 -0500211start = BPF.monotonic_time()
212
213def print_event(cpu, data, size):
Xiaozhou Liu51d62d32019-02-15 13:03:05 +0800214 event = bpf["events"].event(data)
xingfeng251003e49482022-03-17 13:07:16 +0800215 print("%-14.6f %-7d %8.3f %s" % (
Sasha Goldshteindd37d632017-02-09 05:44:43 -0500216 float(event.timestamp - start) / 1000000000,
swj21f24632019-04-30 20:34:45 +0800217 event.pid, float(event.duration) / 1000000, event.query))
Sasha Goldshteindd37d632017-02-09 05:44:43 -0500218
Igor Mazur5f7035e2017-07-18 10:06:34 +0300219if mode.startswith("MYSQL"):
220 print("Tracing database queries for application %s slower than %d ms..." %
221 (args.path, args.threshold))
222else:
223 print("Tracing database queries for pids %s slower than %d ms..." %
224 (', '.join(map(str, args.pids)), args.threshold))
225
xingfeng251003e49482022-03-17 13:07:16 +0800226print("%-14s %-7s %8s %s" % ("TIME(s)", "PID", "MS", "QUERY"))
Sasha Goldshteindd37d632017-02-09 05:44:43 -0500227
Mark Drayton5f5687e2017-02-20 18:13:03 +0000228bpf["events"].open_perf_buffer(print_event, page_cnt=64)
Sasha Goldshteindd37d632017-02-09 05:44:43 -0500229while True:
Jerome Marchand51671272018-12-19 01:57:24 +0100230 try:
231 bpf.perf_buffer_poll()
232 except KeyboardInterrupt:
233 exit()