Skip to content

Commit 37f7228

Browse files
authored
Workaround eBPF errors (#1350)
The kernel is raising errors related to the variable `@every`. We workaround this problem by replacing it with an integer literal (expanded from Python template variable `$EVERY`) instead. The USDT `process_root_nodes` may not exist in the compiled .so file of the VM binding because the methods that contain this USDT are generic methods that are instantiated on demand. VMs that do not use object-enqueuing tracing (such as OpenJDK) may not have this USDT. We added the `--no-root-nodes` option to skip that USDT. Added some debugging options to `capture.py`, including `-d` and `-v`.
1 parent 31a78a4 commit 37f7228

File tree

3 files changed

+56
-21
lines changed

3 files changed

+56
-21
lines changed

tools/tracing/timeline/README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Suite.
1919
Run the following command with a **normal** user (*not* as `root` or using `sudo`):
2020

2121
```shell
22-
./capture.py -e 50 -m /path/to/libmmtk_openjdk.so
22+
./capture.py -e 50 -m /path/to/libmmtk_openjdk.so --no-root-nodes
2323
```
2424

2525
`-e 50` means we only capture one GC in every 50 GCs because otherwise it will have to print too
@@ -30,6 +30,8 @@ post-processing. If one single GC still produces too much log and overruns the
3030
should consider setting the `BPFTRACE_PERF_RB_PAGES` environment variable. See the man page of
3131
`bpftrace`.)
3232

33+
`--no-root-nodes` skips the `process_root_nodes` USDT which does not exist in `libmmtk_openjdk.so`.
34+
3335
Replace `/path/to/libmmtk_openjdk.so` with the actual path to the `.so` that contains MMTk and its
3436
binding.
3537

@@ -84,7 +86,7 @@ This means things are working properly. Now re-run `./capture.py` again, but pi
8486
file.
8587

8688
```
87-
./capture.py -m /path/to/libmmtk_openjdk.so > mybenchmark.log
89+
./capture.py -m /path/to/libmmtk_openjdk.so --no-root-nodes > mybenchmark.log
8890
```
8991

9092
Type the root password if prompted.

tools/tracing/timeline/capture.bt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
BEGIN {
2-
@every = $EVERY;
32
@harness = $HARNESS;
43

54
@gc_count = 0;
@@ -33,7 +32,7 @@ usdt:$MMTK:mmtk:gc_start {
3332
printf("GC,B,%d,%lu\n", tid, nsecs);
3433
@gc_count += 1;
3534
// bpftrace warns that signed `%` operator may have undefiend behavior.
36-
if ((uint64)@gc_count % (uint64)@every == 0 && @stats_enabled) {
35+
if ((uint64)@gc_count % $EVERY == 0 && @stats_enabled) {
3736
@enable_print = 1;
3837
} else {
3938
@enable_print = 0;
@@ -87,11 +86,13 @@ usdt:$MMTK:mmtk:roots {
8786
}
8887
}
8988

89+
//////// BEGIN:PROCESS_ROOT_NODES
9090
usdt:$MMTK:mmtk:process_root_nodes {
9191
if (@enable_print) {
9292
printf("process_root_nodes,meta,%d,%lu,%lu,%lu\n", tid, nsecs, arg0, arg1);
9393
}
9494
}
95+
//////// END:PROCESS_ROOT_NODES
9596

9697
usdt:$MMTK:mmtk:process_slots {
9798
if (@enable_print) {

tools/tracing/timeline/capture.py

Lines changed: 49 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,32 @@ def get_args():
2727
help="Path of the MMTk binary")
2828
parser.add_argument("-H", "--harness", action="store_true",
2929
help="Only collect data for the timing iteration (harness_begin/harness_end)")
30-
parser.add_argument("-p", "--print-script", action="store_true",
31-
help="Print the content of the bpftrace script")
3230
parser.add_argument("-e", "--every", metavar="N", type=int, default=1,
3331
help="Only capture every N-th GC"),
3432
parser.add_argument("-x", "--extra", metavar="S", type=str, action="append",
3533
help="Append script S after 'capture.bt'. Use this option multiple times to append multiple scripts."),
34+
parser.add_argument("--no-root-nodes", action="store_true",
35+
help="Do not add 'process_root_nodes' probe." +
36+
" This USDT is conditionally generated by Rust generics, and may not exist in some VM bindings.")
37+
parser.add_argument("-p", "--print-script", action="store_true",
38+
help="Print the content of the bpftrace script")
39+
parser.add_argument("-d", "--dry-run", action="store_true",
40+
help="Print the bpftrace command to be executed, but does not actually execute the command. Useful with -p")
41+
parser.add_argument("-v", "--verbose", action="store_true",
42+
help="Add -v to the bpftrace command. Useful for debugging"),
3643
return parser.parse_args()
3744

45+
def delete_lines_between(lines, begin, end):
46+
begin_index, end_index = None, None
47+
for i, line in enumerate(lines):
48+
if begin in line and begin_index is None:
49+
begin_index = i
50+
if begin_index is not None and end in line:
51+
end_index = i
52+
break
53+
else:
54+
raise Exception(f"Cannot find {begin} and {end} in the script. {begin_index} {end_index}")
55+
del lines[begin_index:end_index + 1]
3856

3957
def main():
4058
args = get_args()
@@ -48,12 +66,15 @@ def main():
4866
if args.extra is not None:
4967
for extra_script in args.extra:
5068
script_paths.append(Path(extra_script))
51-
script_texts = []
69+
scripts_lines = []
5270
for script_path in script_paths:
53-
script_text = script_path.read_text()
54-
script_texts.append(script_text)
71+
script_lines = script_path.read_text().splitlines()
72+
scripts_lines.extend(script_lines)
5573

56-
merged_script = "\n".join(script_texts)
74+
if args.no_root_nodes:
75+
delete_lines_between(scripts_lines, "BEGIN:PROCESS_ROOT_NODES", "END:PROCESS_ROOT_NODES")
76+
77+
merged_script = "\n".join(scripts_lines)
5778

5879
template = Template(merged_script)
5980
with tempfile.NamedTemporaryFile(mode="w+t") as tmp:
@@ -66,17 +87,28 @@ def main():
6687
print(content)
6788
tmp.write(content)
6889
tmp.flush()
69-
# We use execvp to replace the current process instead of creating
70-
# a subprocess (or sh -c). This is so that when users invoke this from
71-
# the command line, Ctrl-C will be captured by bpftrace instead of the
72-
# outer Python script. The temporary file can then be cleaned up by
73-
# the END probe in bpftrace.
74-
#
75-
# In theory, you can implement this via pty, but it is very finicky
76-
# and doesn't work reliably.
77-
# See also https://github.com/anupli/running-ng/commit/b74e3a13f56dd97f73432d8a391e1d6cd9db8663
78-
os.execvp("sudo", ["sudo", args.bpftrace,
79-
"--unsafe", tmp.name])
90+
91+
extra_options = []
92+
if args.verbose:
93+
extra_options.append("-v")
94+
95+
command_line = ["sudo", args.bpftrace] + extra_options + ["--unsafe", tmp.name]
96+
97+
if args.dry_run:
98+
print("Dry run. Command to execute:")
99+
print(" ".join(f"'{c}'" for c in command_line))
100+
# tempfile will be deleted at the end of `with`.
101+
else:
102+
# We use execvp to replace the current process instead of creating
103+
# a subprocess (or sh -c). This is so that when users invoke this from
104+
# the command line, Ctrl-C will be captured by bpftrace instead of the
105+
# outer Python script. The temporary file can then be cleaned up by
106+
# the END probe in bpftrace.
107+
#
108+
# In theory, you can implement this via pty, but it is very finicky
109+
# and doesn't work reliably.
110+
# See also https://github.com/anupli/running-ng/commit/b74e3a13f56dd97f73432d8a391e1d6cd9db8663
111+
os.execvp("sudo", command_line)
80112

81113

82114
if __name__ == "__main__":

0 commit comments

Comments
 (0)