xref: /aosp_15_r20/external/bcc/tools/argdist.py (revision 387f9dfdfa2baef462e92476d413c7bc2470293e)
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