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