okami.mom/albedo

git clone https://git.nkp.pet/okami.mom/albedo

nekomimi.petpoint inspect_daemon.py usage and albedo_inspect docs at the renamed tool414af8e

main
2.8 KiB86 linesraw
1"""Run albedo_inspect against the live daemon.
2
3Start the daemon with ALBEDO_INSPECT=1 first. Then:
4  python3 test/manual/inspect_daemon.py cpu [seconds]    where the VM's work goes (default 30)
5  python3 test/manual/inspect_daemon.py report           memory by process, binary and session
6  python3 test/manual/inspect_daemon.py stacks          where every albedo process is waiting now
7  python3 test/manual/inspect_daemon.py peak [ms]        memory high-water mark (default 5000)
8  python3 test/manual/inspect_daemon.py eval EXPRESSION  any expression run on the daemon; it is
9                                                  printed with ~p unless it is iodata
10
11$ALBEDO_HOME (default ~/.albedo) says which daemon.
12"""
13
14import json
15import os
16from pathlib import Path
17import re
18import subprocess
19import sys
20import uuid
21
22HOME = Path(os.environ.get("ALBEDO_HOME", Path.home() / ".albedo"))
23
24
25def node() -> str:
26    pid = json.loads((HOME / "daemon.json").read_text())["pid"]
27    log = (HOME / "daemon.log").read_text(errors="replace")
28    found = re.findall(rf"inspect: node (albedo_{pid}@\S+)", log)
29    if not found:
30        sys.exit(f"daemon {pid} is not inspectable; restart it with ALBEDO_INSPECT=1")
31    return found[-1]
32
33
34def expression(argv: list[str]) -> tuple[str, int]:
35    """The call to make on the daemon, and how long to wait for it in seconds."""
36    match argv:
37        case ["cpu", *rest]:
38            seconds = int(rest[0]) if rest else 30
39            return f"albedo_inspect:cpu({seconds})", seconds + 30
40        case ["report"]:
41            return "albedo_inspect:report()", 60
42        case ["stacks"]:
43            return "albedo_inspect:stacks()", 60
44        case ["peak", *rest]:
45            ms = int(rest[0]) if rest else 5000
46            return f"albedo_inspect:peak({ms})", ms // 1000 + 30
47        case ["eval", text]:
48            return text, 120
49    sys.exit(__doc__)
50
51
52def main() -> None:
53    call, timeout = expression(sys.argv[1:])
54    target = node()
55    cookie = (HOME / "inspect.cookie").read_text().strip()
56    program = (
57        f"Value = case rpc:call('{target}', erlang, apply, [fun() -> {call} end, []], "
58        f'{timeout * 1000}) of {{badrpc, Reason}} -> io_lib:format("badrpc ~p~n", [Reason]); '
59        "V -> V end, "
60        'io:put_chars(try iolist_to_binary(Value) catch _:_ -> io_lib:format("~p~n", [Value]) end), '
61        "halt()."
62    )
63    result = subprocess.run(
64        [
65            "erl",
66            "+S",
67            "1:1",
68            "-sname",
69            f"probe_{uuid.uuid4().hex[:12]}",
70            "-setcookie",
71            cookie,
72            "-noshell",
73            "-eval",
74            program,
75        ],
76        capture_output=True,
77        text=True,
78        timeout=timeout + 15,
79    )
80    sys.stdout.write(result.stdout)
81    sys.stderr.write(result.stderr)
82    sys.exit(result.returncode)
83
84
85if __name__ == "__main__":
86    main()