Sasha Goldshtein | 2495698 | 2017-02-09 06:21:43 -0500 | [diff] [blame] | 1 | #!/usr/bin/python |
| 2 | # |
| 3 | # dbstat Display a histogram of MySQL and PostgreSQL query latencies. |
| 4 | # |
| 5 | # USAGE: dbstat [-v] [-p PID [PID ...]] [-m THRESHOLD] [-u] |
| 6 | # [-i INTERVAL] {mysql,postgres} |
| 7 | # |
| 8 | # This tool uses USDT probes, which means it needs MySQL and PostgreSQL built |
| 9 | # with USDT (DTrace) support. |
| 10 | # |
Paul Chaignon | 956ca1c | 2017-03-04 20:07:56 +0100 | [diff] [blame] | 11 | # Copyright 2017, Sasha Goldshtein |
Sasha Goldshtein | 2495698 | 2017-02-09 06:21:43 -0500 | [diff] [blame] | 12 | # Licensed under the Apache License, Version 2.0 |
| 13 | # |
| 14 | # 15-Feb-2017 Sasha Goldshtein Created this. |
| 15 | |
| 16 | from bcc import BPF, USDT |
| 17 | import argparse |
| 18 | import subprocess |
| 19 | from time import sleep, strftime |
| 20 | |
| 21 | examples = """ |
| 22 | dbstat postgres # display a histogram of PostgreSQL query latencies |
| 23 | dbstat mysql -v # display MySQL latencies and print the BPF program |
| 24 | dbstat mysql -u # display query latencies in microseconds (default: ms) |
| 25 | dbstat mysql -m 5 # trace only queries slower than 5ms |
| 26 | dbstat mysql -p 408 # trace queries in a specific process |
| 27 | """ |
| 28 | parser = argparse.ArgumentParser( |
| 29 | description="", |
| 30 | formatter_class=argparse.RawDescriptionHelpFormatter, |
| 31 | epilog=examples) |
| 32 | parser.add_argument("-v", "--verbose", action="store_true", |
| 33 | help="print the BPF program") |
| 34 | parser.add_argument("db", choices=["mysql", "postgres"], |
| 35 | help="the database engine to use") |
| 36 | parser.add_argument("-p", "--pid", type=int, nargs='*', |
| 37 | dest="pids", metavar="PID", help="the pid(s) to trace") |
| 38 | parser.add_argument("-m", "--threshold", type=int, default=0, |
| 39 | help="trace queries slower than this threshold (ms)") |
| 40 | parser.add_argument("-u", "--microseconds", action="store_true", |
| 41 | help="display query latencies in microseconds (default: milliseconds)") |
| 42 | parser.add_argument("-i", "--interval", type=int, default=99999999999, |
| 43 | help="print summary at this interval (seconds)") |
| 44 | args = parser.parse_args() |
| 45 | |
| 46 | if not args.pids or len(args.pids) == 0: |
| 47 | if args.db == "mysql": |
| 48 | args.pids = map(int, subprocess.check_output( |
| 49 | "pidof mysqld".split()).split()) |
| 50 | elif args.db == "postgres": |
| 51 | args.pids = map(int, subprocess.check_output( |
| 52 | "pidof postgres".split()).split()) |
| 53 | |
| 54 | program = """ |
Sasha Goldshtein | aa124dd | 2017-03-31 17:16:45 -0400 | [diff] [blame] | 55 | #include <uapi/linux/ptrace.h> |
Sasha Goldshtein | 2495698 | 2017-02-09 06:21:43 -0500 | [diff] [blame] | 56 | |
| 57 | BPF_HASH(temp, u64, u64); |
| 58 | BPF_HISTOGRAM(latency); |
| 59 | |
| 60 | int probe_start(struct pt_regs *ctx) { |
| 61 | u64 timestamp = bpf_ktime_get_ns(); |
| 62 | u64 pid = bpf_get_current_pid_tgid(); |
| 63 | temp.update(&pid, ×tamp); |
| 64 | return 0; |
| 65 | } |
| 66 | |
| 67 | int probe_end(struct pt_regs *ctx) { |
| 68 | u64 *timestampp; |
| 69 | u64 pid = bpf_get_current_pid_tgid(); |
| 70 | timestampp = temp.lookup(&pid); |
| 71 | if (!timestampp) |
| 72 | return 0; |
| 73 | |
| 74 | u64 delta = bpf_ktime_get_ns() - *timestampp; |
| 75 | FILTER |
| 76 | delta /= SCALE; |
| 77 | latency.increment(bpf_log2l(delta)); |
| 78 | temp.delete(&pid); |
| 79 | return 0; |
| 80 | } |
| 81 | """ |
| 82 | program = program.replace("SCALE", str(1000 if args.microseconds else 1000000)) |
Paul Chaignon | 956ca1c | 2017-03-04 20:07:56 +0100 | [diff] [blame] | 83 | program = program.replace("FILTER", "" if args.threshold == 0 else |
Sasha Goldshtein | 2495698 | 2017-02-09 06:21:43 -0500 | [diff] [blame] | 84 | "if (delta / 1000000 < %d) { return 0; }" % args.threshold) |
| 85 | |
| 86 | usdts = map(lambda pid: USDT(pid=pid), args.pids) |
| 87 | for usdt in usdts: |
| 88 | usdt.enable_probe("query__start", "probe_start") |
| 89 | usdt.enable_probe("query__done", "probe_end") |
| 90 | |
Sasha Goldshtein | 2495698 | 2017-02-09 06:21:43 -0500 | [diff] [blame] | 91 | if args.verbose: |
| 92 | print('\n'.join(map(lambda u: u.get_text(), usdts))) |
| 93 | print(program) |
| 94 | |
Sasha Goldshtein | da1c607 | 2017-03-31 17:17:38 -0400 | [diff] [blame^] | 95 | bpf = BPF(text=program, usdt_contexts=usdts) |
| 96 | |
Sasha Goldshtein | 2495698 | 2017-02-09 06:21:43 -0500 | [diff] [blame] | 97 | print("Tracing database queries for pids %s slower than %d ms..." % |
| 98 | (', '.join(map(str, args.pids)), args.threshold)) |
| 99 | |
| 100 | latencies = bpf["latency"] |
| 101 | |
| 102 | def print_hist(): |
| 103 | print("[%s]" % strftime("%H:%M:%S")) |
| 104 | latencies.print_log2_hist("query latency (%s)" % |
| 105 | ("us" if args.microseconds else "ms")) |
| 106 | print("") |
| 107 | latencies.clear() |
| 108 | |
| 109 | while True: |
| 110 | try: |
| 111 | sleep(args.interval) |
| 112 | print_hist() |
| 113 | except KeyboardInterrupt: |
| 114 | print_hist() |
| 115 | break |