spring-boot-demo-oom: five real memory-leak demos with captured OOM + MAT evidence
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
#!/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])
|
||||
Reference in New Issue
Block a user