blob: fb306e31d01885276faa07bac5ba1cf6a5b959a0 [file] [log] [blame]
Sasha Goldshteine725b142016-10-26 12:52:06 -07001#!/usr/bin/python
2# @lint-avoid-python-3-compatibility-imports
3#
4# uflow Trace method execution flow in high-level languages.
5# For Linux, uses BCC, eBPF.
6#
Marko Myllynen9162be42018-09-04 19:45:16 +03007# USAGE: uflow [-C CLASS] [-M METHOD] [-v] {java,perl,php,python,ruby} pid
Sasha Goldshteine725b142016-10-26 12:52:06 -07008#
9# Copyright 2016 Sasha Goldshtein
10# Licensed under the Apache License, Version 2.0 (the "License")
11#
12# 27-Oct-2016 Sasha Goldshtein Created this.
13
14from __future__ import print_function
15import argparse
Paul Chaignon4bb6d7f2017-03-30 19:05:40 +020016from bcc import BPF, USDT, utils
Sasha Goldshteine725b142016-10-26 12:52:06 -070017import ctypes as ct
18import time
Paul Chaignon4bb6d7f2017-03-30 19:05:40 +020019import os
20
Marko Myllynen9162be42018-09-04 19:45:16 +030021languages = ["java", "perl", "php", "python", "ruby"]
Sasha Goldshteine725b142016-10-26 12:52:06 -070022
23examples = """examples:
Paul Chaignon4bb6d7f2017-03-30 19:05:40 +020024 ./uflow -l java 185 # trace Java method calls in process 185
25 ./uflow -l ruby 134 # trace Ruby method calls in process 134
26 ./uflow -M indexOf -l java 185 # trace only 'indexOf'-prefixed methods
27 ./uflow -C '<stdin>' -l python 180 # trace only REPL-defined methods
Sasha Goldshteine725b142016-10-26 12:52:06 -070028"""
29parser = argparse.ArgumentParser(
30 description="Trace method execution flow in high-level languages.",
31 formatter_class=argparse.RawDescriptionHelpFormatter,
32 epilog=examples)
Paul Chaignon4bb6d7f2017-03-30 19:05:40 +020033parser.add_argument("-l", "--language", choices=languages,
Sasha Goldshteine725b142016-10-26 12:52:06 -070034 help="language to trace")
35parser.add_argument("pid", type=int, help="process id to attach to")
36parser.add_argument("-M", "--method",
37 help="trace only calls to methods starting with this prefix")
38parser.add_argument("-C", "--class", dest="clazz",
39 help="trace only calls to classes starting with this prefix")
40parser.add_argument("-v", "--verbose", action="store_true",
41 help="verbose mode: print the BPF program (for debugging purposes)")
Marko Myllynen27e7aea2018-09-26 20:09:07 +030042parser.add_argument("--ebpf", action="store_true",
43 help=argparse.SUPPRESS)
Sasha Goldshteine725b142016-10-26 12:52:06 -070044args = parser.parse_args()
45
46usdt = USDT(pid=args.pid)
47
48program = """
49struct call_t {
50 u64 depth; // first bit is direction (0 entry, 1 return)
51 u64 pid; // (tgid << 32) + pid from bpf_get_current...
52 u64 timestamp; // ns
53 char clazz[80];
54 char method[80];
55};
56
57BPF_PERF_OUTPUT(calls);
58BPF_HASH(entry, u64, u64);
59"""
60
61prefix_template = """
62static inline bool prefix_%s(char *actual) {
63 char expected[] = "%s";
64 for (int i = 0; i < sizeof(expected) - 1; ++i) {
65 if (expected[i] != actual[i]) {
66 return false;
67 }
68 }
69 return true;
70}
71"""
72
73if args.clazz:
74 program += prefix_template % ("class", args.clazz)
75if args.method:
76 program += prefix_template % ("method", args.method)
77
78trace_template = """
79int NAME(struct pt_regs *ctx) {
80 u64 *depth, zero = 0, clazz = 0, method = 0 ;
81 struct call_t data = {};
82
83 READ_CLASS
84 READ_METHOD
85 bpf_probe_read(&data.clazz, sizeof(data.clazz), (void *)clazz);
86 bpf_probe_read(&data.method, sizeof(data.method), (void *)method);
87
88 FILTER_CLASS
89 FILTER_METHOD
90
91 data.pid = bpf_get_current_pid_tgid();
92 data.timestamp = bpf_ktime_get_ns();
93 depth = entry.lookup_or_init(&data.pid, &zero);
94 data.depth = DEPTH;
95 UPDATE
96
97 calls.perf_submit(ctx, &data, sizeof(data));
98 return 0;
99}
100"""
101
102def enable_probe(probe_name, func_name, read_class, read_method, is_return):
103 global program, trace_template, usdt
104 depth = "*depth + 1" if not is_return else "*depth | (1ULL << 63)"
Paul Chaignon956ca1c2017-03-04 20:07:56 +0100105 update = "++(*depth);" if not is_return else "if (*depth) --(*depth);"
Sasha Goldshteine725b142016-10-26 12:52:06 -0700106 filter_class = "if (!prefix_class(data.clazz)) { return 0; }" \
107 if args.clazz else ""
108 filter_method = "if (!prefix_method(data.method)) { return 0; }" \
109 if args.method else ""
110 program += trace_template.replace("NAME", func_name) \
111 .replace("READ_CLASS", read_class) \
112 .replace("READ_METHOD", read_method) \
113 .replace("FILTER_CLASS", filter_class) \
114 .replace("FILTER_METHOD", filter_method) \
115 .replace("DEPTH", depth) \
116 .replace("UPDATE", update)
Sasha Goldshteindc3a57c2017-02-08 16:02:11 -0500117 usdt.enable_probe_or_bail(probe_name, func_name)
Sasha Goldshteine725b142016-10-26 12:52:06 -0700118
119usdt = USDT(pid=args.pid)
120
Paul Chaignon4bb6d7f2017-03-30 19:05:40 +0200121language = args.language
122if not language:
123 language = utils.detect_language(languages, args.pid)
124
125if language == "java":
Sasha Goldshteine725b142016-10-26 12:52:06 -0700126 enable_probe("method__entry", "java_entry",
127 "bpf_usdt_readarg(2, ctx, &clazz);",
128 "bpf_usdt_readarg(4, ctx, &method);", is_return=False)
129 enable_probe("method__return", "java_return",
130 "bpf_usdt_readarg(2, ctx, &clazz);",
131 "bpf_usdt_readarg(4, ctx, &method);", is_return=True)
Marko Myllynen9162be42018-09-04 19:45:16 +0300132elif language == "perl":
133 enable_probe("sub__entry", "perl_entry",
134 "bpf_usdt_readarg(2, ctx, &clazz);",
135 "bpf_usdt_readarg(1, ctx, &method);", is_return=False)
136 enable_probe("sub__return", "perl_return",
137 "bpf_usdt_readarg(2, ctx, &clazz);",
138 "bpf_usdt_readarg(1, ctx, &method);", is_return=True)
139elif language == "php":
140 enable_probe("function__entry", "php_entry",
141 "bpf_usdt_readarg(4, ctx, &clazz);",
142 "bpf_usdt_readarg(1, ctx, &method);", is_return=False)
143 enable_probe("function__return", "php_return",
144 "bpf_usdt_readarg(4, ctx, &clazz);",
145 "bpf_usdt_readarg(1, ctx, &method);", is_return=True)
Paul Chaignon4bb6d7f2017-03-30 19:05:40 +0200146elif language == "python":
Sasha Goldshteine725b142016-10-26 12:52:06 -0700147 enable_probe("function__entry", "python_entry",
148 "bpf_usdt_readarg(1, ctx, &clazz);", # filename really
149 "bpf_usdt_readarg(2, ctx, &method);", is_return=False)
150 enable_probe("function__return", "python_return",
151 "bpf_usdt_readarg(1, ctx, &clazz);", # filename really
152 "bpf_usdt_readarg(2, ctx, &method);", is_return=True)
Paul Chaignon4bb6d7f2017-03-30 19:05:40 +0200153elif language == "ruby":
Sasha Goldshteine725b142016-10-26 12:52:06 -0700154 enable_probe("method__entry", "ruby_entry",
155 "bpf_usdt_readarg(1, ctx, &clazz);",
156 "bpf_usdt_readarg(2, ctx, &method);", is_return=False)
157 enable_probe("method__return", "ruby_return",
158 "bpf_usdt_readarg(1, ctx, &clazz);",
159 "bpf_usdt_readarg(2, ctx, &method);", is_return=True)
160 enable_probe("cmethod__entry", "ruby_centry",
161 "bpf_usdt_readarg(1, ctx, &clazz);",
162 "bpf_usdt_readarg(2, ctx, &method);", is_return=False)
163 enable_probe("cmethod__return", "ruby_creturn",
164 "bpf_usdt_readarg(1, ctx, &clazz);",
165 "bpf_usdt_readarg(2, ctx, &method);", is_return=True)
Paul Chaignon4bb6d7f2017-03-30 19:05:40 +0200166else:
167 print("No language detected; use -l to trace a language.")
168 exit(1)
Sasha Goldshteine725b142016-10-26 12:52:06 -0700169
Marko Myllynen27e7aea2018-09-26 20:09:07 +0300170if args.ebpf or args.verbose:
171 if args.verbose:
172 print(usdt.get_text())
Sasha Goldshteine725b142016-10-26 12:52:06 -0700173 print(program)
Marko Myllynen27e7aea2018-09-26 20:09:07 +0300174 if args.ebpf:
175 exit()
Sasha Goldshteine725b142016-10-26 12:52:06 -0700176
177bpf = BPF(text=program, usdt_contexts=[usdt])
178print("Tracing method calls in %s process %d... Ctrl-C to quit." %
Paul Chaignon4bb6d7f2017-03-30 19:05:40 +0200179 (language, args.pid))
Sasha Goldshteine725b142016-10-26 12:52:06 -0700180print("%-3s %-6s %-6s %-8s %s" % ("CPU", "PID", "TID", "TIME(us)", "METHOD"))
181
182class CallEvent(ct.Structure):
183 _fields_ = [
184 ("depth", ct.c_ulonglong),
185 ("pid", ct.c_ulonglong),
186 ("timestamp", ct.c_ulonglong),
187 ("clazz", ct.c_char * 80),
188 ("method", ct.c_char * 80)
189 ]
190
191start_ts = time.time()
192
193def print_event(cpu, data, size):
194 event = ct.cast(data, ct.POINTER(CallEvent)).contents
195 depth = event.depth & (~(1 << 63))
196 direction = "<- " if event.depth & (1 << 63) else "-> "
197 print("%-3d %-6d %-6d %-8.3f %-40s" % (cpu, event.pid >> 32,
198 event.pid & 0xFFFFFFFF, time.time() - start_ts,
199 (" " * (depth - 1)) + direction + event.clazz + "." + event.method))
200
201bpf["calls"].open_perf_buffer(print_event)
202while 1:
Teng Qindbf00292018-02-28 21:47:50 -0800203 bpf.perf_buffer_poll()