From c8f752f72727ae031f53b70d2a5694c1cbdd9935 Mon Sep 17 00:00:00 2001 From: Sasha Goldshtein Date: Mon, 17 Oct 2016 02:18:43 -0700 Subject: [PATCH] argdist: STRCMP helper function argdist filter expressions can now use the STRCMP helper function to compare strings. The first string must be a compile-time constant literal string, and the second string can be determined at runtime. This is a workaround until BPF introduces a kernel builtin for strcmp. Example: ``` argdist -H 'r:c:open(char *file):u64:$latency:STRCMP("test.txt",file)' ``` --- man/man8/argdist.8 | 4 ++++ tools/argdist.py | 27 ++++++++++++++++++++++++++- tools/argdist_example.txt | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/man/man8/argdist.8 b/man/man8/argdist.8 index a24302f..b0a539f 100644 --- a/man/man8/argdist.8 +++ b/man/man8/argdist.8 @@ -111,6 +111,10 @@ Only parameter values that pass the filter will be collected. This is any valid C expression that refers to the parameter values, such as "fd == 1 && length > 16". The $entry, $retval, and $latency variables can be used here as well, in return probes. +The filter expression may also use the STRCMP pseudo-function to compare +a predefined string to a string argument. For example: STRCMP("test.txt", file). +The order of arguments is important: the first argument MUST be a quoted +literal string, and the second argument can be a runtime string. .TP .B [label] The label that will be displayed when printing the probed values. By default, diff --git a/tools/argdist.py b/tools/argdist.py index 9f41bde..2e3aad5 100755 --- a/tools/argdist.py +++ b/tools/argdist.py @@ -19,6 +19,7 @@ import sys class Probe(object): next_probe_index = 0 + streq_index = 0 aliases = {"$PID": "bpf_get_current_pid_tgid()"} def _substitute_aliases(self, expr): @@ -174,6 +175,7 @@ u64 __time = bpf_ktime_get_ns(); def __init__(self, tool, type, specifier): self.usdt_ctx = None + self.streq_functions = "" self.pid = tool.args.pid self.cumulative = tool.args.cumulative or False self.raw_spec = specifier @@ -242,9 +244,32 @@ u64 __time = bpf_ktime_get_ns(); self.usdt_ctx.enable_probe( self.function, self.probe_func_name) + def _generate_streq_function(self, string): + fname = "streq_%d" % Probe.streq_index + Probe.streq_index += 1 + self.streq_functions += """ +static inline bool %s(char const *ignored, char const *str) { + char needle[] = %s; + char haystack[sizeof(needle)]; + bpf_probe_read(&haystack, sizeof(haystack), (void *)str); + for (int i = 0; i < sizeof(needle); ++i) { + if (needle[i] != haystack[i]) { + return false; + } + } + return true; +} + """ % (fname, string) + return fname + def _substitute_exprs(self): def repl(expr): expr = self._substitute_aliases(expr) + matches = re.finditer('STRCMP\\(("[^"]+\\")', expr) + for match in matches: + string = match.group(1) + fname = self._generate_streq_function(string) + expr = expr.replace("STRCMP", fname, 1) return expr.replace("$retval", "PT_REGS_RC(ctx)") for i in range(0, len(self.exprs)): self.exprs[i] = repl(self.exprs[i]) @@ -370,7 +395,7 @@ DATA_DECL program = program.replace("COLLECT", collect) program = program.replace("PREFIX", prefix) - return program + return self.streq_functions + program def _attach_u(self): libpath = BPF.find_library(self.library) diff --git a/tools/argdist_example.txt b/tools/argdist_example.txt index 71ee238..44cf922 100644 --- a/tools/argdist_example.txt +++ b/tools/argdist_example.txt @@ -277,6 +277,45 @@ t:net:net_dev_start_xmit():u16:args->protocol Note that to discover the format of the net:net_dev_start_xmit tracepoint, you use the tplist tool (tplist -v net:net_dev_start_xmit). + +Occasionally, it is useful to filter certain expressions by string. This is not +trivially supported by BPF, but argdist provides a STRCMP helper you can use in +filter expressions. For example, to get a histogram of latencies opening a +specific file, run this: + +# argdist -c -H 'r:c:open(char *file):u64:$latency/1000:STRCMP("test.txt",$entry(file))' +[02:16:38] +[02:16:39] +[02:16:40] + $latency/1000 : count distribution + 0 -> 1 : 0 | | + 2 -> 3 : 0 | | + 4 -> 7 : 0 | | + 8 -> 15 : 0 | | + 16 -> 31 : 2 |****************************************| +[02:16:41] + $latency/1000 : count distribution + 0 -> 1 : 0 | | + 2 -> 3 : 0 | | + 4 -> 7 : 0 | | + 8 -> 15 : 1 |********** | + 16 -> 31 : 4 |****************************************| +[02:16:42] + $latency/1000 : count distribution + 0 -> 1 : 0 | | + 2 -> 3 : 0 | | + 4 -> 7 : 0 | | + 8 -> 15 : 1 |******** | + 16 -> 31 : 5 |****************************************| +[02:16:43] + $latency/1000 : count distribution + 0 -> 1 : 0 | | + 2 -> 3 : 0 | | + 4 -> 7 : 0 | | + 8 -> 15 : 1 |******** | + 16 -> 31 : 5 |****************************************| + + Here's a final example that finds how many write() system calls are performed by each process on the system: -- 2.7.4