File size: 3,287 Bytes
ac156d2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
"""Cold start: launch FalkorDB (embedded if no server is reachable), then build both graphs."""
import os
import subprocess
import threading
import time
from pathlib import Path

STATUS = {"ready": False, "phase": "starting", "log": [], "started": time.strftime("%Y-%m-%d %H:%M UTC", time.gmtime()),
          "ops_scale": None, "ram_gb": None}


def log(msg):
    STATUS["phase"] = msg
    STATUS["log"].append(f"{time.strftime('%H:%M:%S')} {msg}")
    print(msg, flush=True)


def _reachable():
    try:
        from core.graph import db
        db().connection.ping()
        return True
    except Exception:
        return False


def _embedded_binaries():
    """redis-server + falkordb.so shipped inside the falkordblite wheel, or FALKOR_BIN_DIR."""
    d = os.getenv("FALKOR_BIN_DIR")
    if d:
        return Path(d) / "redis-server", Path(d) / "falkordb.so"
    import redislite
    b = Path(redislite.__file__).parent / "bin"
    return b / "redis-server", b / "falkordb.so"


def start_falkordb():
    if _reachable():
        log("FalkorDB server found")
        return
    server, module = _embedded_binaries()
    data = Path(os.getenv("FALKOR_DATA", "/tmp/falkordb"))
    data.mkdir(parents=True, exist_ok=True)
    subprocess.Popen([str(server), "--port", "6379", "--bind", "127.0.0.1", "--save", "", "--appendonly", "no",
                      "--dir", str(data), "--loadmodule", str(module)], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
    from core.graph import db
    db.cache_clear()
    for _ in range(60):
        if _reachable():
            log("Embedded FalkorDB started")
            return
        time.sleep(0.5)
    raise RuntimeError("FalkorDB did not start")


def ops_scale():
    if os.getenv("CORTEX_OPS_SCALE"):
        return float(os.getenv("CORTEX_OPS_SCALE"))
    try:
        import psutil
        gb = psutil.virtual_memory().total / 1e9
    except Exception:
        gb = 8
    STATUS["ram_gb"] = round(gb, 1)
    return 1.0 if gb >= 12 else 0.5 if gb >= 6 else 0.2


def build():
    try:
        start_falkordb()
        from core.graph import KG, OPS, db
        existing = set(db().list_graphs())
        if os.getenv("CORTEX_REBUILD") or KG not in existing:
            log("Building knowledge graph from 73 PDFs (LangChain + embeddings)")
            from ingest.pipeline import build_kg
            build_kg()
        if os.getenv("CORTEX_REBUILD") or OPS not in existing:
            scale = ops_scale()
            STATUS["ops_scale"] = scale
            log(f"Generating and loading operational graph (scale {scale})")
            from ingest.load_ops import load_ops
            load_ops(scale)
        from core.scenarios import perf
        STATUS["perf"] = perf(runs=3)  # warms caches; cached for the overview tiles
        log("Ready")
        STATUS["ready"] = True
        if os.getenv("CORTEX_COMPARE", "1") == "1":  # Neo4j vs FalkorDB runs after the app is up (own thread)
            from compare.service import start_background as start_compare
            start_compare()
    except Exception as e:  # surface the failure on the status page instead of crashing silently
        log(f"Startup failed: {type(e).__name__}: {e}")


def start_background():
    threading.Thread(target=build, daemon=True).start()