File size: 2,726 Bytes
e2e6c43
 
 
 
 
aae9a70
e2e6c43
aae9a70
e2e6c43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
aae9a70
e2e6c43
 
 
 
 
 
 
 
 
aae9a70
e2e6c43
 
 
 
 
 
 
 
 
 
 
 
 
 
f1303ff
 
aae9a70
 
 
e2e6c43
 
 
 
 
 
 
 
aae9a70
 
e2e6c43
 
 
 
aae9a70
e2e6c43
aae9a70
e2e6c43
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#!/usr/bin/env python3
"""Sample process-tree RSS/swap while running a command."""

import argparse
import json
import signal
import subprocess
import sys
import time


def process_tree(root):
    output = subprocess.check_output(["ps", "-e", "-o", "pid=,ppid="], text=True)
    children = {}
    for line in output.splitlines():
        pid, ppid = map(int, line.split())
        children.setdefault(ppid, []).append(pid)
    result, todo = [], [root]
    while todo:
        pid = todo.pop()
        result.append(pid)
        todo.extend(children.get(pid, ()))
    return result


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--interval", type=float, default=0.25)
    parser.add_argument("--timeout", type=float, default=7200)
    parser.add_argument("--rss-limit-gib", type=float)
    parser.add_argument("command", nargs=argparse.REMAINDER)
    args = parser.parse_args()
    command = args.command[1:] if args.command[:1] == ["--"] else args.command
    if not command:
        parser.error("missing command")

    started = time.monotonic()
    process = subprocess.Popen(command)
    peak_rss = peak_swap = 0
    exceeded = False
    while process.poll() is None:
        rss = swap = 0
        for pid in process_tree(process.pid):
            try:
                with open(f"/proc/{pid}/status", encoding="ascii") as status:
                    for line in status:
                        if line.startswith("VmRSS:"):
                            rss += int(line.split()[1])
                        elif line.startswith("VmSwap:"):
                            swap += int(line.split()[1])
            except (FileNotFoundError, ProcessLookupError):
                pass
        peak_rss = max(peak_rss, rss)
        peak_swap = max(peak_swap, swap)
        # VmRSS 以 kB 為單位,門檻須換算成 kB(1 GiB = 1024 * 1024 kB)
        if args.rss_limit_gib is not None and rss > args.rss_limit_gib * (1024 ** 2):
            exceeded = True
            process.send_signal(signal.SIGKILL)
            break
        if time.monotonic() - started > args.timeout:
            process.kill()
            break
        time.sleep(args.interval)
    result = process.wait()
    print("RUNMEM " + json.dumps({
        "peak_kb": peak_rss,
        "peak_gib": round(peak_rss / 1048576, 3),
        "limit_gib": args.rss_limit_gib,
        "limit_exceeded": exceeded,
        "swap_kb": peak_swap,
        "wall_s": round(time.monotonic() - started, 2),
        "exit": result,
        "cmd": command,
        "method": "procfs-rss-watchdog",
    }))
    return 0 if args.rss_limit_gib is None else (1 if exceeded else result)


if __name__ == "__main__":
    raise SystemExit(main())