Skip to content

Commit 69e361a

Browse files
goldshtn4ast
authored andcommitted
Fix argdist, trace, tplist to use the libbcc USDT support (#698)
* Allow argdist to enable USDT probes without a pid The current code would only pass the pid to the USDT class, thereby not allowing USDT probes to be enabled from the binary path only. If the probe doesn't have a semaphore, it can actually be enabled for all processes in a uniform fashion -- which is now supported. * Reintroduce USDT support into tplist To print USDT probe information, tplist needs an API to return the probe data, including the number of arguments and locations for each probe. This commit introduces this API, called bcc_usdt_foreach, and invokes it from the revised tplist implementation. Although the result is not 100% identical to the original tplist, which could also print the probe argument information, this is not strictly required for users of the argdist and trace tools, which is why it was omitted for now. * Fix trace.py tracepoint support Somehow, the import of the Perf class was omitted from tracepoint.py, which would cause failures when trace enables kernel tracepoints. * trace: Native bcc USDT support trace now works again by using the new bcc USDT support instead of the home-grown Python USDT parser. This required an additional change in the BPF Python API to allow multiple USDT context objects to be passed to the constructor in order to support multiple USDT probes in a single invocation of trace. Otherwise, the USDT-related code in trace was greatly simplified, and uses the `bpf_usdt_readarg` macros to obtain probe argument values. One minor inconvenience that was introduced in the bcc USDT API is that USDT probes with multiple locations that reside in a shared object *must* have a pid specified to enable, even if they don't have an associated semaphore. The reason is that the bcc USDT code figures out which location invoked the probe by inspecting `ctx->ip`, which, for shared objects, can only be determined when the specific process context is available to figure out where the shared object was loaded. This limitation did not previously exist, because instead of looking at `ctx->ip`, the Python USDT reader generated separate code for each probe location with an incrementing identifier. It's not a very big deal because it only means that some probes can't be enabled without specifying a process id, which is almost always desired anyway for USDT probes. argdist has not yet been retrofitted with support for multiple USDT probes, and needs to be updated in a separate commit. * argdist: Support multiple USDT probes argdist now supports multiple USDT probes, as it did before the transition to the native bcc USDT support. This requires aggregating the USDT objects from each probe and passing them together to the BPF constructor when the probes are initialized and attached. Also add a more descriptive exception message to the USDT class when it fails to enable a probe.
1 parent 6644186 commit 69e361a

File tree

11 files changed

+178
-110
lines changed

11 files changed

+178
-110
lines changed

src/cc/bcc_usdt.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,18 @@ void *bcc_usdt_new_frompid(int pid);
2626
void *bcc_usdt_new_frompath(const char *path);
2727
void bcc_usdt_close(void *usdt);
2828

29+
struct bcc_usdt {
30+
const char *provider;
31+
const char *name;
32+
const char *bin_path;
33+
uint64_t semaphore;
34+
int num_locations;
35+
int num_arguments;
36+
};
37+
38+
typedef void (*bcc_usdt_cb)(struct bcc_usdt *);
39+
void bcc_usdt_foreach(void *usdt, bcc_usdt_cb callback);
40+
2941
int bcc_usdt_enable_probe(void *, const char *, const char *);
3042
const char *bcc_usdt_genargs(void *);
3143

src/cc/usdt.cc

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "bcc_proc.h"
2525
#include "usdt.h"
2626
#include "vendor/tinyformat.hpp"
27+
#include "bcc_usdt.h"
2728

2829
namespace USDT {
2930

@@ -255,6 +256,19 @@ bool Context::enable_probe(const std::string &probe_name,
255256
return p && p->enable(fn_name);
256257
}
257258

259+
void Context::each(each_cb callback) {
260+
for (const auto &probe : probes_) {
261+
struct bcc_usdt info = {0};
262+
info.provider = probe->provider().c_str();
263+
info.bin_path = probe->bin_path().c_str();
264+
info.name = probe->name().c_str();
265+
info.semaphore = probe->semaphore();
266+
info.num_locations = probe->num_locations();
267+
info.num_arguments = probe->num_arguments();
268+
callback(&info);
269+
}
270+
}
271+
258272
void Context::each_uprobe(each_uprobe_cb callback) {
259273
for (auto &p : probes_) {
260274
if (!p->enabled())
@@ -288,7 +302,6 @@ Context::~Context() {
288302
}
289303

290304
extern "C" {
291-
#include "bcc_usdt.h"
292305

293306
void *bcc_usdt_new_frompid(int pid) {
294307
USDT::Context *ctx = new USDT::Context(pid);
@@ -331,6 +344,11 @@ const char *bcc_usdt_genargs(void *usdt) {
331344
return storage_.c_str();
332345
}
333346

347+
void bcc_usdt_foreach(void *usdt, bcc_usdt_cb callback) {
348+
USDT::Context *ctx = static_cast<USDT::Context *>(usdt);
349+
ctx->each(callback);
350+
}
351+
334352
void bcc_usdt_foreach_uprobe(void *usdt, bcc_usdt_uprobe_cb callback) {
335353
USDT::Context *ctx = static_cast<USDT::Context *>(usdt);
336354
ctx->each_uprobe(callback);

src/cc/usdt.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
#include "syms.h"
2424
#include "vendor/optional.hpp"
2525

26+
struct bcc_usdt;
27+
2628
namespace USDT {
2729

2830
using std::experimental::optional;
@@ -148,6 +150,7 @@ class Probe {
148150

149151
size_t num_locations() const { return locations_.size(); }
150152
size_t num_arguments() const { return locations_.front().arguments_.size(); }
153+
uint64_t semaphore() const { return semaphore_; }
151154

152155
uint64_t address(size_t n = 0) const { return locations_[n].address_; }
153156
bool usdt_getarg(std::ostream &stream);
@@ -194,6 +197,9 @@ class Context {
194197
bool enable_probe(const std::string &probe_name, const std::string &fn_name);
195198
bool generate_usdt_args(std::ostream &stream);
196199

200+
typedef void (*each_cb)(struct bcc_usdt *);
201+
void each(each_cb callback);
202+
197203
typedef void (*each_uprobe_cb)(const char *, const char *, uint64_t, int);
198204
void each_uprobe(each_uprobe_cb callback);
199205
};

src/python/bcc/__init__.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ def is_exe(fpath):
149149
return None
150150

151151
def __init__(self, src_file="", hdr_file="", text=None, cb=None, debug=0,
152-
cflags=[], usdt=None):
152+
cflags=[], usdt_contexts=[]):
153153
"""Create a a new BPF module with the given source code.
154154
155155
Note:
@@ -179,7 +179,15 @@ def __init__(self, src_file="", hdr_file="", text=None, cb=None, debug=0,
179179
self.tables = {}
180180
cflags_array = (ct.c_char_p * len(cflags))()
181181
for i, s in enumerate(cflags): cflags_array[i] = s.encode("ascii")
182-
if usdt and text: text = usdt.get_text() + text
182+
if text:
183+
for usdt_context in usdt_contexts:
184+
usdt_text = usdt_context.get_text()
185+
if usdt_text is None:
186+
raise Exception("can't generate USDT probe arguments; " +
187+
"possible cause is missing pid when a " +
188+
"probe in a shared object has multiple " +
189+
"locations")
190+
text = usdt_context.get_text() + text
183191

184192
if text:
185193
self.module = lib.bpf_module_create_c_from_string(text.encode("ascii"),
@@ -197,7 +205,8 @@ def __init__(self, src_file="", hdr_file="", text=None, cb=None, debug=0,
197205
if not self.module:
198206
raise Exception("Failed to compile BPF module %s" % src_file)
199207

200-
if usdt: usdt.attach_uprobes(self)
208+
for usdt_context in usdt_contexts:
209+
usdt_context.attach_uprobes(self)
201210

202211
# If any "kprobe__" or "tracepoint__" prefixed functions were defined,
203212
# they will be loaded and attached here.

src/python/bcc/libbcc.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,23 @@ class bcc_symbol(ct.Structure):
157157
lib.bcc_usdt_genargs.restype = ct.c_char_p
158158
lib.bcc_usdt_genargs.argtypes = [ct.c_void_p]
159159

160-
_USDT_CB = ct.CFUNCTYPE(None, ct.c_char_p, ct.c_char_p, ct.c_ulonglong, ct.c_int)
160+
class bcc_usdt(ct.Structure):
161+
_fields_ = [
162+
('provider', ct.c_char_p),
163+
('name', ct.c_char_p),
164+
('bin_path', ct.c_char_p),
165+
('semaphore', ct.c_ulonglong),
166+
('num_locations', ct.c_int),
167+
('num_arguments', ct.c_int),
168+
]
169+
170+
_USDT_CB = ct.CFUNCTYPE(None, ct.POINTER(bcc_usdt))
171+
172+
lib.bcc_usdt_foreach.restype = None
173+
lib.bcc_usdt_foreach.argtypes = [ct.c_void_p, _USDT_CB]
174+
175+
_USDT_PROBE_CB = ct.CFUNCTYPE(None, ct.c_char_p, ct.c_char_p,
176+
ct.c_ulonglong, ct.c_int)
161177

162178
lib.bcc_usdt_foreach_uprobe.restype = None
163-
lib.bcc_usdt_foreach_uprobe.argtypes = [ct.c_void_p, _USDT_CB]
179+
lib.bcc_usdt_foreach_uprobe.argtypes = [ct.c_void_p, _USDT_PROBE_CB]

src/python/bcc/tracepoint.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import multiprocessing
1717
import os
1818
import re
19+
from .perf import Perf
1920

2021
class Tracepoint(object):
2122
enabled_tracepoints = []

src/python/bcc/usdt.py

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,34 +12,67 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
from .libbcc import lib, _USDT_CB
15+
from .libbcc import lib, _USDT_CB, _USDT_PROBE_CB
16+
17+
class USDTProbe(object):
18+
def __init__(self, usdt):
19+
self.provider = usdt.provider
20+
self.name = usdt.name
21+
self.bin_path = usdt.bin_path
22+
self.semaphore = usdt.semaphore
23+
self.num_locations = usdt.num_locations
24+
self.num_arguments = usdt.num_arguments
25+
26+
def __str__(self):
27+
return "%s %s:%s [sema 0x%x]\n %d location(s)\n %d argument(s)" % \
28+
(self.bin_path, self.provider, self.name, self.semaphore,
29+
self.num_locations, self.num_arguments)
30+
31+
def short_name(self):
32+
return "%s:%s" % (self.provider, self.name)
1633

1734
class USDT(object):
1835
def __init__(self, pid=None, path=None):
19-
if pid:
36+
if pid and pid != -1:
2037
self.pid = pid
2138
self.context = lib.bcc_usdt_new_frompid(pid)
2239
if self.context == None:
23-
raise Exception("USDT failed to instrument PID %d" % pid)
40+
raise Exception("USDT failed to instrument PID %d" % pid)
2441
elif path:
2542
self.path = path
2643
self.context = lib.bcc_usdt_new_frompath(path)
2744
if self.context == None:
28-
raise Exception("USDT failed to instrument path %s" % path)
45+
raise Exception("USDT failed to instrument path %s" % path)
46+
else:
47+
raise Exception("either a pid or a binary path must be specified")
2948

3049
def enable_probe(self, probe, fn_name):
3150
if lib.bcc_usdt_enable_probe(self.context, probe, fn_name) != 0:
32-
raise Exception("failed to enable probe '%s'" % probe)
51+
raise Exception(("failed to enable probe '%s'; a possible cause " +
52+
"can be that the probe requires a pid to enable") %
53+
probe)
3354

3455
def get_text(self):
3556
return lib.bcc_usdt_genargs(self.context)
3657

58+
def enumerate_probes(self):
59+
probes = []
60+
def _add_probe(probe):
61+
probes.append(USDTProbe(probe.contents))
62+
63+
lib.bcc_usdt_foreach(self.context, _USDT_CB(_add_probe))
64+
return probes
65+
66+
# This is called by the BPF module's __init__ when it realizes that there
67+
# is a USDT context and probes need to be attached.
3768
def attach_uprobes(self, bpf):
3869
probes = []
3970
def _add_probe(binpath, fn_name, addr, pid):
4071
probes.append((binpath, fn_name, addr, pid))
4172

42-
lib.bcc_usdt_foreach_uprobe(self.context, _USDT_CB(_add_probe))
73+
lib.bcc_usdt_foreach_uprobe(self.context, _USDT_PROBE_CB(_add_probe))
4374

4475
for (binpath, fn_name, addr, pid) in probes:
45-
bpf.attach_uprobe(name=binpath, fn_name=fn_name, addr=addr, pid=pid)
76+
bpf.attach_uprobe(name=binpath, fn_name=fn_name,
77+
addr=addr, pid=pid)
78+

tools/argdist.py

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,9 @@ def _parse_exprs(self, exprs):
175175
self._bail("no exprs specified")
176176
self.exprs = exprs.split(',')
177177

178-
def __init__(self, bpf, type, specifier):
179-
self.pid = bpf.args.pid
178+
def __init__(self, tool, type, specifier):
179+
self.usdt_ctx = None
180+
self.pid = tool.args.pid
180181
self.raw_spec = specifier
181182
self._validate_specifier()
182183

@@ -200,8 +201,7 @@ def __init__(self, bpf, type, specifier):
200201
self.library = parts[1]
201202
self.probe_func_name = "%s_probe%d" % \
202203
(self.function, Probe.next_probe_index)
203-
bpf.enable_usdt_probe(self.function,
204-
fn_name=self.probe_func_name)
204+
self._enable_usdt_probe()
205205
else:
206206
self.library = parts[1]
207207
self.is_user = len(self.library) > 0
@@ -242,8 +242,10 @@ def check(expr):
242242
(self.function, Probe.next_probe_index)
243243
Probe.next_probe_index += 1
244244

245-
def close(self):
246-
pass
245+
def _enable_usdt_probe(self):
246+
self.usdt_ctx = USDT(path=self.library, pid=self.pid)
247+
self.usdt_ctx.enable_probe(
248+
self.function, self.probe_func_name)
247249

248250
def _substitute_exprs(self):
249251
def repl(expr):
@@ -262,12 +264,17 @@ def _generate_hash_field(self, i):
262264
else:
263265
return "%s v%d;\n" % (self.expr_types[i], i)
264266

267+
def _generate_usdt_arg_assignment(self, i):
268+
expr = self.exprs[i]
269+
if self.probe_type == "u" and expr[0:3] == "arg":
270+
return (" u64 %s = 0;\n" +
271+
" bpf_usdt_readarg(%s, ctx, &%s);\n") % \
272+
(expr, expr[3], expr)
273+
else:
274+
return ""
275+
265276
def _generate_field_assignment(self, i):
266-
text = ""
267-
if self.probe_type == "u" and self.exprs[i][0:3] == "arg":
268-
text = (" u64 %s;\n" +
269-
" bpf_usdt_readarg(%s, ctx, &%s);\n") % \
270-
(self.exprs[i], self.exprs[i][3], self.exprs[i])
277+
text = self._generate_usdt_arg_assignment(i)
271278
if self._is_string(self.expr_types[i]):
272279
return (text + " bpf_probe_read(&__key.v%d.s," +
273280
" sizeof(__key.v%d.s), (void *)%s);\n") % \
@@ -291,8 +298,9 @@ def _generate_hash_decl(self):
291298

292299
def _generate_key_assignment(self):
293300
if self.type == "hist":
294-
return "%s __key = %s;\n" % \
295-
(self.expr_types[0], self.exprs[0])
301+
return self._generate_usdt_arg_assignment(0) + \
302+
("%s __key = %s;\n" % \
303+
(self.expr_types[0], self.exprs[0]))
296304
else:
297305
text = "struct %s_key_t __key = {};\n" % \
298306
self.probe_hash_name
@@ -590,11 +598,6 @@ def _create_probes(self):
590598
print("at least one specifier is required")
591599
exit()
592600

593-
def enable_usdt_probe(self, probe_name, fn_name):
594-
if not self.usdt_ctx:
595-
self.usdt_ctx = USDT(pid=self.args.pid)
596-
self.usdt_ctx.enable_probe(probe_name, fn_name)
597-
598601
def _generate_program(self):
599602
bpf_source = """
600603
struct __string_t { char s[%d]; };
@@ -610,9 +613,13 @@ def _generate_program(self):
610613
for probe in self.probes:
611614
bpf_source += probe.generate_text()
612615
if self.args.verbose:
613-
if self.usdt_ctx: print(self.usdt_ctx.get_text())
616+
for text in [probe.usdt_ctx.get_text() \
617+
for probe in self.probes if probe.usdt_ctx]:
618+
print(text)
614619
print(bpf_source)
615-
self.bpf = BPF(text=bpf_source, usdt=self.usdt_ctx)
620+
usdt_contexts = [probe.usdt_ctx
621+
for probe in self.probes if probe.usdt_ctx]
622+
self.bpf = BPF(text=bpf_source, usdt_contexts=usdt_contexts)
616623

617624
def _attach(self):
618625
Tracepoint.attach(self.bpf)
@@ -637,12 +644,6 @@ def _main_loop(self):
637644
count_so_far >= self.args.count:
638645
exit()
639646

640-
def _close_probes(self):
641-
for probe in self.probes:
642-
probe.close()
643-
if self.args.verbose:
644-
print("closed probe: " + str(probe))
645-
646647
def run(self):
647648
try:
648649
self._create_probes()
@@ -654,7 +655,6 @@ def run(self):
654655
traceback.print_exc()
655656
elif sys.exc_info()[0] is not SystemExit:
656657
print(sys.exc_info()[1])
657-
self._close_probes()
658658

659659
if __name__ == "__main__":
660660
Tool().run()

0 commit comments

Comments
 (0)