Skip to content

Commit 49e2761

Browse files
committed
python leaking memory test
1 parent 646dc80 commit 49e2761

File tree

1 file changed

+131
-0
lines changed

1 file changed

+131
-0
lines changed
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
#!/usr/bin/env python3
2+
"""
3+
memory_leak.py
4+
5+
Starts with sensible defaults if no CLI args are provided.
6+
Usage:
7+
python3 memory_leak.py # runs with defaults
8+
python3 memory_leak.py -c 20M # override chunk size, etc.
9+
"""
10+
11+
import argparse
12+
import time
13+
import platform
14+
15+
try:
16+
import psutil
17+
except Exception:
18+
psutil = None
19+
20+
_leak_store = []
21+
22+
def parse_size(s: str) -> int:
23+
"""Parse sizes like '10M', '512K', '1G' into bytes."""
24+
s = str(s).strip().upper()
25+
if s.endswith("G"):
26+
return int(float(s[:-1]) * 1024**3)
27+
if s.endswith("M"):
28+
return int(float(s[:-1]) * 1024**2)
29+
if s.endswith("K"):
30+
return int(float(s[:-1]) * 1024)
31+
return int(s)
32+
33+
def rss_bytes() -> int:
34+
"""Return current process RSS in bytes (best-effort)."""
35+
if psutil:
36+
return psutil.Process().memory_info().rss
37+
if platform.system() == "Linux":
38+
try:
39+
with open("/proc/self/statm", "r") as f:
40+
parts = f.read().split()
41+
if len(parts) >= 2:
42+
pages = int(parts[1])
43+
import os
44+
return pages * os.sysconf("SC_PAGE_SIZE")
45+
except Exception:
46+
pass
47+
return 0
48+
49+
def human_readable(n: int) -> str:
50+
for unit in ["B", "K", "M", "G", "T"]:
51+
if n < 1024.0:
52+
return f"{n:.1f}{unit}"
53+
n /= 1024.0
54+
return f"{n:.1f}P"
55+
56+
def run_leak(chunk_size: int, interval: float, max_bytes: int, max_iters: int, verbose: bool, report_interval: float):
57+
total_allocated = 0
58+
iters = 0
59+
last_report = time.time()
60+
61+
try:
62+
while True:
63+
# Stop conditions
64+
if max_bytes and total_allocated >= max_bytes:
65+
if verbose:
66+
print(f"[info] reached max-bytes {human_readable(max_bytes)}; stopping allocation.")
67+
break
68+
if max_iters and iters >= max_iters:
69+
if verbose:
70+
print(f"[info] reached max-iters {max_iters}; stopping allocation.")
71+
break
72+
73+
try:
74+
chunk = bytearray(b"\x41") * chunk_size
75+
except MemoryError:
76+
print("[error] MemoryError during allocation — exiting allocation loop.")
77+
break
78+
79+
_leak_store.append(chunk)
80+
total_allocated += chunk_size
81+
iters += 1
82+
83+
now = time.time()
84+
if verbose and (now - last_report >= report_interval):
85+
rss = rss_bytes()
86+
print(f"[alloc #{iters}] chunk={human_readable(chunk_size)}, total_alloc={human_readable(total_allocated)}, RSS={human_readable(rss)}")
87+
last_report = now
88+
89+
if interval:
90+
time.sleep(interval)
91+
92+
except KeyboardInterrupt:
93+
print("\n[stopped] KeyboardInterrupt received — stopping allocations.")
94+
95+
rss = rss_bytes()
96+
print("\n=== Summary ===")
97+
print(f"iterations: {iters}")
98+
print(f"total allocated (kept references): {human_readable(total_allocated)}")
99+
print(f"final RSS: {human_readable(rss)}")
100+
print(f"stored objects in leak store: {len(_leak_store)}")
101+
102+
def main():
103+
parser = argparse.ArgumentParser(description="Simple memory leak generator for testing (runs with defaults if no args).")
104+
parser.add_argument("--chunk-size", "-c", default="10M", help="Size per allocation chunk (e.g. 10M). Default 10M.")
105+
parser.add_argument("--interval", "-i", type=float, default=0.1, help="Seconds between allocations. Default 0.1s.")
106+
parser.add_argument("--max-bytes", "-m", default=None, help="Stop after allocating this many bytes (e.g. 200M). Optional.")
107+
parser.add_argument("--max-iters", type=int, default=0, help="Stop after N allocations (0 = unlimited).")
108+
parser.add_argument("--report-interval", type=float, default=1.0, help="How often (s) to print allocation status when verbose.")
109+
parser.add_argument("--quiet", action="store_true", help="Do not print allocation messages.")
110+
args = parser.parse_args()
111+
112+
chunk_size = parse_size(args.chunk_size)
113+
max_bytes = parse_size(args.max_bytes) if args.max_bytes else 0
114+
max_iters = args.max_iters if args.max_iters > 0 else 0
115+
interval = float(args.interval)
116+
verbose = not args.quiet
117+
118+
print("memory_leak starting (defaults used when no args provided):")
119+
print(f" chunk_size = {human_readable(chunk_size)}")
120+
print(f" interval = {interval} s")
121+
if max_bytes:
122+
print(f" max_bytes = {human_readable(max_bytes)}")
123+
if max_iters:
124+
print(f" max_iters = {max_iters}")
125+
print(" verbose =", verbose)
126+
print("Press Ctrl-C to stop manually.\n")
127+
128+
run_leak(chunk_size=chunk_size, interval=interval, max_bytes=max_bytes, max_iters=max_iters, verbose=verbose, report_interval=args.report_interval)
129+
130+
if __name__ == "__main__":
131+
main()

0 commit comments

Comments
 (0)