73 lines
3.5 KiB
Python
73 lines
3.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Trims a full `java ... -jar target/x.jar` run log down to the part worth committing as
|
|
docs/output/01-oom-console.txt: a handful of progress lines leading up to the real
|
|
OutOfMemoryError, the "Dumping heap" / "Heap dump file created" lines, and (if present) one
|
|
real OutOfMemoryError stack trace thrown from application code on a worker thread.
|
|
|
|
Usage: extract-oom-console.py <module-dir> <full-log-file> <java-command-used>
|
|
"""
|
|
import sys
|
|
import os
|
|
|
|
|
|
def main(mod_dir: str, log_path: str, command: str) -> None:
|
|
lines = open(log_path, encoding="utf-8", errors="replace").read().splitlines()
|
|
# drop the sandbox's own JAVA_TOOL_OPTIONS proxy banner -- environment noise, not output
|
|
lines = [l for l in lines if not l.startswith("Picked up JAVA_TOOL_OPTIONS")]
|
|
|
|
first_oom = next((i for i, l in enumerate(lines) if "OutOfMemoryError: Java heap space" in l), None)
|
|
if first_oom is None:
|
|
raise SystemExit("no OutOfMemoryError found in " + log_path)
|
|
|
|
start = max(0, first_oom - 5)
|
|
# The dump-triggering OOM line itself, plus "Dumping heap to ..." right after it if present.
|
|
core = [lines[first_oom]]
|
|
for k in range(first_oom + 1, min(first_oom + 4, len(lines))):
|
|
if lines[k].startswith("Dumping heap to"):
|
|
core.append(lines[k])
|
|
break
|
|
# "Heap dump file created" can appear much later in the raw log, because writing a ~150-300
|
|
# MB dump takes a second or two during which OTHER worker threads keep printing progress
|
|
# lines concurrently. Rather than include that whole noisy, interleaved stretch, skip straight
|
|
# to that one line wherever it is.
|
|
created_line = next(
|
|
(l for l in lines[first_oom + 1:] if l.startswith("Heap dump file created")), None
|
|
)
|
|
if created_line:
|
|
core.append(created_line)
|
|
end = first_oom + 1 # "extra" below scans forward from here, independent of core
|
|
|
|
# If a *second*, worker-thread OutOfMemoryError with a real stack trace follows shortly
|
|
# after (common when more than one thread hits the wall around the same time), keep the
|
|
# cleanest such trace -- it is often more informative than the dump-triggering one alone.
|
|
# With several worker threads printing at once, most candidates have another thread's
|
|
# progress line spliced into the middle of the stack trace; rather than include that
|
|
# interleaving (confusing to a reader) or paper over it (no longer verbatim), look at every
|
|
# candidate in the window and keep whichever has the longest *uninterrupted* run of real
|
|
# "\tat " frames immediately after its "Exception in thread" line, truncating there.
|
|
best = []
|
|
search_end = min(end + 300, len(lines))
|
|
for i in range(end, search_end):
|
|
line = lines[i]
|
|
if 'Exception in thread "' in line and "OutOfMemoryError" in line:
|
|
trace = [line]
|
|
j = i + 1
|
|
while j < len(lines) and lines[j].startswith("\tat "):
|
|
trace.append(lines[j])
|
|
j += 1
|
|
if len(trace) > len(best):
|
|
best = trace
|
|
extra = best
|
|
|
|
out_lines = [f"$ {command}", ""] + lines[start:first_oom] + core + ([""] + extra if extra else [])
|
|
out_path = os.path.join(mod_dir, "docs", "output", "01-oom-console.txt")
|
|
with open(out_path, "w") as f:
|
|
f.write("\n".join(out_lines) + "\n")
|
|
print("wrote", out_path)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if len(sys.argv) != 4:
|
|
raise SystemExit("usage: extract-oom-console.py <module-dir> <full-log-file> <java-command-used>")
|
|
main(sys.argv[1], sys.argv[2], sys.argv[3])
|