File size: 2,343 Bytes
e317359
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
#!/usr/bin/env python3
"""Copy a stable operator snapshot without stopping or locking the evaluator."""
from __future__ import annotations

import argparse
import json
from pathlib import Path
import shutil
import sys
import tempfile
import time

ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "src"))
from tsfm_bench.remote_state import HANDOFF_ROOTS, utc_now


def inventory(root):
    result = {}
    for prefix in HANDOFF_ROOTS:
        for path in (root / prefix).rglob("*"):
            if path.is_symlink():
                raise ValueError(f"Refusing symlink: {path.relative_to(root)}")
            if path.is_file():
                stat = path.stat()
                result[path.relative_to(root)] = (stat.st_size, stat.st_mtime_ns, stat.st_ino)
    return result


def snapshot(root, destination):
    if destination.exists():
        raise ValueError("Snapshot destination must not exist")
    before = inventory(root)
    with tempfile.TemporaryDirectory(prefix="livehouse-copy-", dir=destination.parent) as temp:
        stage = Path(temp) / "snapshot"
        stage.mkdir()
        for rel in sorted(before):
            target = stage / rel
            target.parent.mkdir(parents=True, exist_ok=True)
            shutil.copy2(root / rel, target)
        if inventory(root) != before:
            raise RuntimeError("Operator changed during copying; retry between evaluation cycles")
        record = {"created_at":utc_now(), "files":len(before),
                  "source_bytes":sum(stat[0] for stat in before.values())}
        (stage / ".snapshot.json").write_text(json.dumps(record, indent=2) + "\n")
        stage.rename(destination)
    return record


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--destination", type=Path, required=True)
    args = parser.parse_args()
    destination = args.destination.resolve()
    destination.parent.mkdir(parents=True, exist_ok=True)
    for attempt in range(3):
        try:
            print(json.dumps(snapshot(ROOT, destination)), flush=True)
            return
        except (FileNotFoundError, RuntimeError) as exc:
            if attempt == 2:
                raise
            print(f"{exc}; retrying in 20 seconds", flush=True)
            time.sleep(20)


if __name__ == "__main__":
    main()