1#!/usr/bin/env python 2# 3# argdist Trace a function and display a distribution of its 4# parameter values as a histogram or frequency count. 5# 6# USAGE: argdist [-h] [-p PID] [-z STRING_SIZE] [-i INTERVAL] [-n COUNT] [-v] 7# [-c] [-T TOP] [-C specifier] [-H specifier] [-I header] 8# [-t TID] 9# 10# Licensed under the Apache License, Version 2.0 (the "License") 11# Copyright (C) 2016 Sasha Goldshtein. 12 13from bcc import BPF, USDT, StrcmpRewrite 14from time import sleep, strftime 15import argparse 16import re 17import traceback 18import os 19import sys 20 21class Probe(object): 22 next_probe_index = 0 23 streq_index = 0 24 aliases = {"$PID": "(bpf_get_current_pid_tgid() >> 32)", "$COMM": "&val.name"} 25 26 def _substitute_aliases(self, expr): 27 if expr is None: 28 return expr 29 for alias, subst in Probe.aliases.items(): 30 expr = expr.replace(alias, subst) 31 return expr 32 33 def _parse_signature(self): 34 params = map(str.strip, self.signature.split(',')) 35 self.param_types = {} 36 for param in params: 37 # If the type is a pointer, the * can be next to the 38 # param name. Other complex types like arrays are not 39 # supported right now. 40 index = param.rfind('*') 41 index = index if index != -1 else param.rfind(' ') 42 param_type = param[0:index + 1].strip() 43 param_name = param[index + 1:].strip() 44 self.param_types[param_name] = param_type 45 # Maintain list of user params. Then later decide to 46 # switch to bpf_probe_read_kernel or bpf_probe_read_user. 47 if "__user" in param_type.split(): 48 self.probe_user_list.add(param_name) 49 50 def _generate_entry(self): 51 self.entry_probe_func = self.probe_func_name + "_entry" 52 text = """ 53int PROBENAME(struct pt_regs *ctx SIGNATURE) 54{ 55 u64 __pid_tgid = bpf_get_current_pid_tgid(); 56 u32 __pid = __pid_tgid; // lower 32 bits 57 u32 __tgid = __pid_tgid >> 32; // upper 32 bits 58 PID_FILTER 59 TID_FILTER 60 COLLECT 61 return 0; 62} 63""" 64 text = text.replace("PROBENAME", self.entry_probe_func) 65 text = text.replace("SIGNATURE", 66 "" if len(self.signature) == 0 else ", " + self.signature) 67 text = text.replace("PID_FILTER", self._generate_pid_filter()) 68 text = text.replace("TID_FILTER", self._generate_tid_filter()) 69 collect = "" 70 for pname in self.args_to_probe: 71 param_hash = self.hashname_prefix + pname 72 if pname == "__latency": 73 collect += """ 74u64 __time = bpf_ktime_get_ns(); 75%s.update(&__pid, &__time); 76 """ % param_hash 77 else: 78 collect += "%s.update(&__pid, &%s);\n" % \ 79 (param_hash, pname) 80 text = text.replace("COLLECT", collect) 81 return text 82 83 def _generate_entry_probe(self): 84 # Any $entry(name) expressions result in saving that argument 85 # when entering the function. 86 self.args_to_probe = set() 87 regex = r"\$entry\((\w+)\)" 88 for expr in self.exprs: 89 for arg in re.finditer(regex, expr): 90 self.args_to_probe.add(arg.group(1)) 91 for arg in re.finditer(regex, self.filter): 92 self.args_to_probe.add(arg.group(1)) 93 if any(map(lambda expr: "$latency" in expr, self.exprs)) or \ 94 "$latency" in self.filter: 95 self.args_to_probe.add("__latency") 96 self.param_types["__latency"] = "u64" # nanoseconds 97 for pname in self.args_to_probe: 98 if pname not in self.param_types: 99 raise ValueError("$entry(%s): no such param" % 100 arg) 101 102 self.hashname_prefix = "%s_param_" % self.probe_hash_name 103 text = "" 104 for pname in self.args_to_probe: 105 # Each argument is stored in a separate hash that is 106 # keyed by pid. 107 text += "BPF_HASH(%s, u32, %s);\n" % \ 108 (self.hashname_prefix + pname, 109 self.param_types[pname]) 110 text += self._generate_entry() 111 return text 112 113 def _generate_retprobe_prefix(self): 114 # After we're done here, there are __%s_val variables for each 115 # argument we needed to probe using $entry(name), and they all 116 # have values (which isn't necessarily the case if we missed 117 # the method entry probe). 118 text = "" 119 self.param_val_names = {} 120 for pname in self.args_to_probe: 121 val_name = "__%s_val" % pname 122 text += "%s *%s = %s.lookup(&__pid);\n" % \ 123 (self.param_types[pname], val_name, 124 self.hashname_prefix + pname) 125 text += "if (%s == 0) { return 0 ; }\n" % val_name 126 self.param_val_names[pname] = val_name 127 return text 128 129 def _generate_comm_prefix(self): 130 text = """ 131struct val_t { 132 u32 pid; 133 char name[sizeof(struct __string_t)]; 134}; 135struct val_t val = {.pid = (bpf_get_current_pid_tgid() >> 32) }; 136bpf_get_current_comm(&val.name, sizeof(val.name)); 137 """ 138 return text 139 140 def _replace_entry_exprs(self): 141 for pname, vname in self.param_val_names.items(): 142 if pname == "__latency": 143 entry_expr = "$latency" 144 val_expr = "(bpf_ktime_get_ns() - *%s)" % vname 145 else: 146 entry_expr = "$entry(%s)" % pname 147 val_expr = "(*%s)" % vname 148 for i in range(0, len(self.exprs)): 149 self.exprs[i] = self.exprs[i].replace( 150 entry_expr, val_expr) 151 self.filter = self.filter.replace(entry_expr, 152 val_expr) 153 154 def _attach_entry_probe(self): 155 if self.is_user: 156 self.bpf.attach_uprobe(name=self.library, 157 sym=self.function, 158 fn_name=self.entry_probe_func, 159 pid=self.pid or -1) 160 else: 161 self.bpf.attach_kprobe(event=self.function, 162 fn_name=self.entry_probe_func) 163 164 def _bail(self, error): 165 raise ValueError("error parsing probe '%s': %s" % 166 (self.raw_spec, error)) 167 168 def _validate_specifier(self): 169 # Everything after '#' is the probe label, ignore it 170 spec = self.raw_spec.split('#')[0] 171 parts = spec.strip().split(':') 172 if len(parts) < 3: 173 self._bail("at least the probe type, library, and " + 174 "function signature must be specified") 175 if len(parts) > 6: 176 self._bail("extraneous ':'-separated parts detected") 177 if parts[0] not in ["r", "p", "t", "u"]: 178 self._bail("probe type must be 'p', 'r', 't', or 'u'" + 179 " but got '%s'" % parts[0]) 180 if re.match(r"\S+\(.*\)", parts[2]) is None: 181 self._bail(("function signature '%s' has an invalid " + 182 "format") % parts[2]) 183 184 def _parse_expr_types(self, expr_types): 185 if len(expr_types) == 0: 186 self._bail("no expr types specified") 187 self.expr_types = expr_types.split(',') 188 189 def _parse_exprs(self, exprs): 190 if len(exprs) == 0: 191 self._bail("no exprs specified") 192 self.exprs = exprs.split(',') 193 194 def _make_valid_identifier(self, ident): 195 return re.sub(r'[^A-Za-z0-9_]', '_', ident) 196 197 def __init__(self, tool, type, specifier): 198 self.usdt_ctx = None 199 self.streq_functions = "" 200 self.pid = tool.args.pid 201 self.tid = tool.args.tid 202 self.cumulative = tool.args.cumulative or False 203 self.raw_spec = specifier 204 self.probe_user_list = set() 205 self.bin_cmp = False 206 self._validate_specifier() 207 208 spec_and_label = specifier.split('#') 209 self.label = spec_and_label[1] \ 210 if len(spec_and_label) == 2 else None 211 212 parts = spec_and_label[0].strip().split(':') 213 self.type = type # hist or freq 214 self.probe_type = parts[0] 215 fparts = parts[2].split('(') 216 self.function = fparts[0].strip() 217 if self.probe_type == "t": 218 self.library = "" # kernel 219 self.tp_category = parts[1] 220 self.tp_event = self.function 221 elif self.probe_type == "u": 222 self.library = parts[1] 223 self.probe_func_name = self._make_valid_identifier( 224 "%s_probe%d" % 225 (self.function, Probe.next_probe_index)) 226 self._enable_usdt_probe() 227 else: 228 self.library = parts[1] 229 self.is_user = len(self.library) > 0 230 self.signature = fparts[1].strip()[:-1] 231 self._parse_signature() 232 233 # If the user didn't specify an expression to probe, we probe 234 # the retval in a ret probe, or simply the value "1" otherwise. 235 self.is_default_expr = len(parts) < 5 236 if not self.is_default_expr: 237 self._parse_expr_types(parts[3]) 238 self._parse_exprs(parts[4]) 239 if len(self.exprs) != len(self.expr_types): 240 self._bail("mismatched # of exprs and types") 241 if self.type == "hist" and len(self.expr_types) > 1: 242 self._bail("histograms can only have 1 expr") 243 else: 244 if not self.probe_type == "r" and self.type == "hist": 245 self._bail("histograms must have expr") 246 self.expr_types = \ 247 ["u64" if not self.probe_type == "r" else "int"] 248 self.exprs = \ 249 ["1" if not self.probe_type == "r" else "$retval"] 250 self.filter = "" if len(parts) != 6 else parts[5] 251 self._substitute_exprs() 252 253 # Do we need to attach an entry probe so that we can collect an 254 # argument that is required for an exit (return) probe? 255 def check(expr): 256 keywords = ["$entry", "$latency"] 257 return any(map(lambda kw: kw in expr, keywords)) 258 self.entry_probe_required = self.probe_type == "r" and \ 259 (any(map(check, self.exprs)) or check(self.filter)) 260 261 self.probe_func_name = self._make_valid_identifier( 262 "%s_probe%d" % 263 (self.function, Probe.next_probe_index)) 264 self.probe_hash_name = self._make_valid_identifier( 265 "%s_hash%d" % 266 (self.function, Probe.next_probe_index)) 267 Probe.next_probe_index += 1 268 269 def _enable_usdt_probe(self): 270 self.usdt_ctx = USDT(path=self.library, pid=self.pid) 271 self.usdt_ctx.enable_probe( 272 self.function, self.probe_func_name) 273 274 def _substitute_exprs(self): 275 def repl(expr): 276 expr = self._substitute_aliases(expr) 277 rdict = StrcmpRewrite.rewrite_expr(expr, 278 self.bin_cmp, self.library, 279 self.probe_user_list, self.streq_functions, 280 Probe.streq_index) 281 expr = rdict["expr"] 282 self.streq_functions = rdict["streq_functions"] 283 Probe.streq_index = rdict["probeid"] 284 return expr.replace("$retval", "PT_REGS_RC(ctx)") 285 for i in range(0, len(self.exprs)): 286 self.exprs[i] = repl(self.exprs[i]) 287 self.filter = repl(self.filter) 288 289 def _is_string(self, expr_type): 290 return expr_type == "char*" or expr_type == "char *" 291 292 def _generate_hash_field(self, i): 293 if self._is_string(self.expr_types[i]): 294 return "struct __string_t v%d;\n" % i 295 else: 296 return "%s v%d;\n" % (self.expr_types[i], i) 297 298 def _generate_usdt_arg_assignment(self, i): 299 expr = self.exprs[i] 300 if self.probe_type == "u" and expr[0:3] == "arg": 301 arg_index = int(expr[3]) 302 arg_ctype = self.usdt_ctx.get_probe_arg_ctype( 303 self.function, arg_index - 1) 304 return (" %s %s = 0;\n" + 305 " bpf_usdt_readarg(%s, ctx, &%s);\n") \ 306 % (arg_ctype, expr, expr[3], expr) 307 else: 308 return "" 309 310 def _generate_field_assignment(self, i): 311 text = self._generate_usdt_arg_assignment(i) 312 if self._is_string(self.expr_types[i]): 313 if self.is_user or \ 314 self.exprs[i] in self.probe_user_list: 315 probe_readfunc = "bpf_probe_read_user" 316 else: 317 probe_readfunc = "bpf_probe_read_kernel" 318 return (text + " %s(&__key.v%d.s," + 319 " sizeof(__key.v%d.s), (void *)%s);\n") % \ 320 (probe_readfunc, i, i, self.exprs[i]) 321 else: 322 return text + " __key.v%d = %s;\n" % \ 323 (i, self.exprs[i]) 324 325 def _generate_hash_decl(self): 326 if self.type == "hist": 327 return "BPF_HISTOGRAM(%s, %s);" % \ 328 (self.probe_hash_name, self.expr_types[0]) 329 else: 330 text = "struct %s_key_t {\n" % self.probe_hash_name 331 for i in range(0, len(self.expr_types)): 332 text += self._generate_hash_field(i) 333 text += "};\n" 334 text += "BPF_HASH(%s, struct %s_key_t, u64);\n" % \ 335 (self.probe_hash_name, self.probe_hash_name) 336 return text 337 338 def _generate_key_assignment(self): 339 if self.type == "hist": 340 return self._generate_usdt_arg_assignment(0) + \ 341 ("%s __key = %s;\n" % 342 (self.expr_types[0], self.exprs[0])) 343 else: 344 text = "struct %s_key_t __key = {};\n" % \ 345 self.probe_hash_name 346 for i in range(0, len(self.exprs)): 347 text += self._generate_field_assignment(i) 348 return text 349 350 def _generate_hash_update(self): 351 if self.type == "hist": 352 return "%s.atomic_increment(bpf_log2l(__key));" % \ 353 self.probe_hash_name 354 else: 355 return "%s.atomic_increment(__key);" % \ 356 self.probe_hash_name 357 358 def _generate_pid_filter(self): 359 # Kernel probes need to explicitly filter pid, because the 360 # attach interface doesn't support pid filtering 361 if self.pid is not None and not self.is_user: 362 return "if (__tgid != %d) { return 0; }" % self.pid 363 else: 364 return "" 365 366 def _generate_tid_filter(self): 367 if self.tid is not None and not self.is_user: 368 return "if (__pid != %d) { return 0; }" % self.tid 369 else: 370 return "" 371 372 def generate_text(self): 373 program = "" 374 probe_text = """ 375DATA_DECL 376 """ + ( 377 "TRACEPOINT_PROBE(%s, %s)" % 378 (self.tp_category, self.tp_event) 379 if self.probe_type == "t" 380 else "int PROBENAME(struct pt_regs *ctx SIGNATURE)") + """ 381{ 382 u64 __pid_tgid = bpf_get_current_pid_tgid(); 383 u32 __pid = __pid_tgid; // lower 32 bits 384 u32 __tgid = __pid_tgid >> 32; // upper 32 bits 385 PID_FILTER 386 TID_FILTER 387 PREFIX 388 KEY_EXPR 389 if (!(FILTER)) return 0; 390 COLLECT 391 return 0; 392} 393""" 394 prefix = "" 395 signature = "" 396 397 # If any entry arguments are probed in a ret probe, we need 398 # to generate an entry probe to collect them 399 if self.entry_probe_required: 400 program += self._generate_entry_probe() 401 prefix += self._generate_retprobe_prefix() 402 # Replace $entry(paramname) with a reference to the 403 # value we collected when entering the function: 404 self._replace_entry_exprs() 405 406 if self.probe_type == "p" and len(self.signature) > 0: 407 # Only entry uprobes/kprobes can have user-specified 408 # signatures. Other probes force it to (). 409 signature = ", " + self.signature 410 411 # If COMM is specified prefix with code to get process name 412 if self.exprs.count(self.aliases['$COMM']): 413 prefix += self._generate_comm_prefix() 414 415 program += probe_text.replace("PROBENAME", 416 self.probe_func_name) 417 program = program.replace("SIGNATURE", signature) 418 program = program.replace("PID_FILTER", 419 self._generate_pid_filter()) 420 program = program.replace("TID_FILTER", 421 self._generate_tid_filter()) 422 423 decl = self._generate_hash_decl() 424 key_expr = self._generate_key_assignment() 425 collect = self._generate_hash_update() 426 program = program.replace("DATA_DECL", decl) 427 program = program.replace("KEY_EXPR", key_expr) 428 program = program.replace("FILTER", 429 "1" if len(self.filter) == 0 else self.filter) 430 program = program.replace("COLLECT", collect) 431 program = program.replace("PREFIX", prefix) 432 433 return self.streq_functions + program 434 435 def _attach_u(self): 436 libpath = BPF.find_library(self.library) 437 if libpath is None: 438 libpath = BPF.find_exe(self.library) 439 if libpath is None or len(libpath) == 0: 440 self._bail("unable to find library %s" % self.library) 441 442 if self.probe_type == "r": 443 self.bpf.attach_uretprobe(name=libpath, 444 sym=self.function, 445 fn_name=self.probe_func_name, 446 pid=self.pid or -1) 447 else: 448 self.bpf.attach_uprobe(name=libpath, 449 sym=self.function, 450 fn_name=self.probe_func_name, 451 pid=self.pid or -1) 452 453 def _attach_k(self): 454 if self.probe_type == "t": 455 pass # Nothing to do for tracepoints 456 elif self.probe_type == "r": 457 self.bpf.attach_kretprobe(event=self.function, 458 fn_name=self.probe_func_name) 459 else: 460 self.bpf.attach_kprobe(event=self.function, 461 fn_name=self.probe_func_name) 462 463 def attach(self, bpf): 464 self.bpf = bpf 465 if self.probe_type == "u": 466 return 467 if self.is_user: 468 self._attach_u() 469 else: 470 self._attach_k() 471 if self.entry_probe_required: 472 self._attach_entry_probe() 473 474 def _v2s(self, v): 475 # Most fields can be converted with plain str(), but strings 476 # are wrapped in a __string_t which has an .s field 477 if "__string_t" in type(v).__name__: 478 return str(v.s) 479 return str(v) 480 481 def _display_expr(self, i): 482 # Replace ugly latency calculation with $latency 483 expr = self.exprs[i].replace( 484 "(bpf_ktime_get_ns() - *____latency_val)", "$latency") 485 # Replace alias values back with the alias name 486 for alias, subst in Probe.aliases.items(): 487 expr = expr.replace(subst, alias) 488 # Replace retval expression with $retval 489 expr = expr.replace("PT_REGS_RC(ctx)", "$retval") 490 # Replace ugly (*__param_val) expressions with param name 491 return re.sub(r"\(\*__(\w+)_val\)", r"\1", expr) 492 493 def _display_key(self, key): 494 if self.is_default_expr: 495 if not self.probe_type == "r": 496 return "total calls" 497 else: 498 return "retval = %s" % str(key.v0) 499 else: 500 # The key object has v0, ..., vk fields containing 501 # the values of the expressions from self.exprs 502 def str_i(i): 503 key_i = self._v2s(getattr(key, "v%d" % i)) 504 return "%s = %s" % \ 505 (self._display_expr(i), key_i) 506 return ", ".join(map(str_i, range(0, len(self.exprs)))) 507 508 def display(self, top): 509 data = self.bpf.get_table(self.probe_hash_name) 510 if self.type == "freq": 511 print(self.label or self.raw_spec) 512 print("\t%-10s %s" % ("COUNT", "EVENT")) 513 sdata = sorted(data.items(), key=lambda p: p[1].value) 514 if top is not None: 515 sdata = sdata[-top:] 516 for key, value in sdata: 517 # Print some nice values if the user didn't 518 # specify an expression to probe 519 if self.is_default_expr: 520 if not self.probe_type == "r": 521 key_str = "total calls" 522 else: 523 key_str = "retval = %s" % \ 524 self._v2s(key.v0) 525 else: 526 key_str = self._display_key(key) 527 print("\t%-10s %s" % 528 (str(value.value), key_str)) 529 elif self.type == "hist": 530 label = self.label or (self._display_expr(0) 531 if not self.is_default_expr else "retval") 532 data.print_log2_hist(val_type=label) 533 if not self.cumulative: 534 data.clear() 535 536 def __str__(self): 537 return self.label or self.raw_spec 538 539class Tool(object): 540 examples = """ 541Probe specifier syntax: 542 {p,r,t,u}:{[library],category}:function(signature):type[,type...]:expr[,expr...][:filter]][#label] 543Where: 544 p,r,t,u -- probe at function entry, function exit, kernel 545 tracepoint, or USDT probe 546 in exit probes: can use $retval, $entry(param), $latency 547 library -- the library that contains the function 548 (leave empty for kernel functions) 549 category -- the category of the kernel tracepoint (e.g. net, sched) 550 function -- the function name to trace (or tracepoint name) 551 signature -- the function's parameters, as in the C header 552 type -- the type of the expression to collect (supports multiple) 553 expr -- the expression to collect (supports multiple) 554 filter -- the filter that is applied to collected values 555 label -- the label for this probe in the resulting output 556 557EXAMPLES: 558 559argdist -H 'p::__kmalloc(u64 size):u64:size' 560 Print a histogram of allocation sizes passed to kmalloc 561 562argdist -p 1005 -C 'p:c:malloc(size_t size):size_t:size:size==16' 563 Print a frequency count of how many times process 1005 called malloc 564 with an allocation size of 16 bytes 565 566argdist -C 'r:c:gets():char*:(char*)$retval#snooped strings' 567 Snoop on all strings returned by gets() 568 569argdist -H 'r::__kmalloc(size_t size):u64:$latency/$entry(size)#ns per byte' 570 Print a histogram of nanoseconds per byte from kmalloc allocations 571 572argdist -C 'p::__kmalloc(size_t sz, gfp_t flags):size_t:sz:flags&GFP_ATOMIC' 573 Print frequency count of kmalloc allocation sizes that have GFP_ATOMIC 574 575argdist -p 1005 -C 'p:c:write(int fd):int:fd' -T 5 576 Print frequency counts of how many times writes were issued to a 577 particular file descriptor number, in process 1005, but only show 578 the top 5 busiest fds 579 580argdist -p 1005 -H 'r:c:read()' 581 Print a histogram of results (sizes) returned by read() in process 1005 582 583argdist -C 'r::__vfs_read():u32:$PID:$latency > 100000' 584 Print frequency of reads by process where the latency was >0.1ms 585 586argdist -C 'r::__vfs_read():u32:$COMM:$latency > 100000' 587 Print frequency of reads by process name where the latency was >0.1ms 588 589argdist -H 'r::__vfs_read(void *file, void *buf, size_t count):size_t: 590 $entry(count):$latency > 1000000' 591 Print a histogram of read sizes that were longer than 1ms 592 593argdist -H \\ 594 'p:c:write(int fd, const void *buf, size_t count):size_t:count:fd==1' 595 Print a histogram of buffer sizes passed to write() across all 596 processes, where the file descriptor was 1 (STDOUT) 597 598argdist -C 'p:c:fork()#fork calls' 599 Count fork() calls in libc across all processes 600 Can also use funccount.py, which is easier and more flexible 601 602argdist -H 't:block:block_rq_complete():u32:args->nr_sector' 603 Print histogram of number of sectors in completing block I/O requests 604 605argdist -C 't:irq:irq_handler_entry():int:args->irq' 606 Aggregate interrupts by interrupt request (IRQ) 607 608argdist -C 'u:pthread:pthread_start():u64:arg2' -p 1337 609 Print frequency of function addresses used as a pthread start function, 610 relying on the USDT pthread_start probe in process 1337 611 612argdist -H 'p:c:sleep(u32 seconds):u32:seconds' \\ 613 -H 'p:c:nanosleep(struct timespec *req):long:req->tv_nsec' 614 Print histograms of sleep() and nanosleep() parameter values 615 616argdist -p 2780 -z 120 \\ 617 -C 'p:c:write(int fd, char* buf, size_t len):char*:buf:fd==1' 618 Spy on writes to STDOUT performed by process 2780, up to a string size 619 of 120 characters 620 621argdist -I 'kernel/sched/sched.h' \\ 622 -C 'p::__account_cfs_rq_runtime(struct cfs_rq *cfs_rq):s64:cfs_rq->runtime_remaining' 623 Trace on the cfs scheduling runqueue remaining runtime. The struct cfs_rq is defined 624 in kernel/sched/sched.h which is in kernel source tree and not in kernel-devel 625 package. So this command needs to run at the kernel source tree root directory 626 so that the added header file can be found by the compiler. 627""" 628 629 def __init__(self): 630 parser = argparse.ArgumentParser(description="Trace a " + 631 "function and display a summary of its parameter values.", 632 formatter_class=argparse.RawDescriptionHelpFormatter, 633 epilog=Tool.examples) 634 parser.add_argument("-p", "--pid", type=int, 635 help="id of the process to trace (optional)") 636 parser.add_argument("-t", "--tid", type=int, 637 help="id of the thread to trace (optional)") 638 parser.add_argument("-z", "--string-size", default=80, 639 type=int, 640 help="maximum string size to read from char* arguments") 641 parser.add_argument("-i", "--interval", default=1, type=int, 642 help="output interval, in seconds (default 1 second)") 643 parser.add_argument("-d", "--duration", type=int, 644 help="total duration of trace, in seconds") 645 parser.add_argument("-n", "--number", type=int, dest="count", 646 help="number of outputs") 647 parser.add_argument("-v", "--verbose", action="store_true", 648 help="print resulting BPF program code before executing") 649 parser.add_argument("-c", "--cumulative", action="store_true", 650 help="do not clear histograms and freq counts at " + 651 "each interval") 652 parser.add_argument("-T", "--top", type=int, 653 help="number of top results to show (not applicable to " + 654 "histograms)") 655 parser.add_argument("-H", "--histogram", action="append", 656 dest="histspecifier", metavar="specifier", 657 help="probe specifier to capture histogram of " + 658 "(see examples below)") 659 parser.add_argument("-C", "--count", action="append", 660 dest="countspecifier", metavar="specifier", 661 help="probe specifier to capture count of " + 662 "(see examples below)") 663 parser.add_argument("-I", "--include", action="append", 664 metavar="header", 665 help="additional header files to include in the BPF program " 666 "as either full path, " 667 "or relative to relative to current working directory, " 668 "or relative to default kernel header search path") 669 parser.add_argument("--ebpf", action="store_true", 670 help=argparse.SUPPRESS) 671 self.args = parser.parse_args() 672 self.usdt_ctx = None 673 674 def _create_probes(self): 675 self.probes = [] 676 for specifier in (self.args.countspecifier or []): 677 self.probes.append(Probe(self, "freq", specifier)) 678 for histspecifier in (self.args.histspecifier or []): 679 self.probes.append(Probe(self, "hist", histspecifier)) 680 if len(self.probes) == 0: 681 print("at least one specifier is required") 682 exit(1) 683 684 def _generate_program(self): 685 bpf_source = """ 686struct __string_t { char s[%d]; }; 687 688#include <uapi/linux/ptrace.h> 689 """ % self.args.string_size 690 for include in (self.args.include or []): 691 if include.startswith((".", "/")): 692 include = os.path.abspath(include) 693 bpf_source += "#include \"%s\"\n" % include 694 else: 695 bpf_source += "#include <%s>\n" % include 696 697 bpf_source += BPF.generate_auto_includes( 698 map(lambda p: p.raw_spec, self.probes)) 699 for probe in self.probes: 700 bpf_source += probe.generate_text() 701 if self.args.verbose: 702 for text in [probe.usdt_ctx.get_text() 703 for probe in self.probes 704 if probe.usdt_ctx]: 705 print(text) 706 if self.args.verbose or self.args.ebpf: 707 print(bpf_source) 708 if self.args.ebpf: 709 exit() 710 usdt_contexts = [probe.usdt_ctx 711 for probe in self.probes if probe.usdt_ctx] 712 self.bpf = BPF(text=bpf_source, usdt_contexts=usdt_contexts) 713 714 def _attach(self): 715 for probe in self.probes: 716 probe.attach(self.bpf) 717 if self.args.verbose: 718 print("open uprobes: %s" % list(self.bpf.uprobe_fds.keys())) 719 print("open kprobes: %s" % list(self.bpf.kprobe_fds.keys())) 720 721 def _main_loop(self): 722 count_so_far = 0 723 seconds = 0 724 while True: 725 try: 726 sleep(self.args.interval) 727 seconds += self.args.interval 728 except KeyboardInterrupt: 729 exit() 730 print("[%s]" % strftime("%H:%M:%S")) 731 for probe in self.probes: 732 probe.display(self.args.top) 733 count_so_far += 1 734 if self.args.count is not None and \ 735 count_so_far >= self.args.count: 736 exit() 737 if self.args.duration and \ 738 seconds >= self.args.duration: 739 exit() 740 741 def run(self): 742 try: 743 self._create_probes() 744 self._generate_program() 745 self._attach() 746 self._main_loop() 747 except: 748 exc_info = sys.exc_info() 749 sys_exit = exc_info[0] is SystemExit 750 if self.args.verbose: 751 traceback.print_exc() 752 elif not sys_exit: 753 print(exc_info[1]) 754 exit(0 if sys_exit else 1) 755 756if __name__ == "__main__": 757 Tool().run() 758