Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions dream-server/bin/dream-host-agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,24 @@ def docker_compose_action(service_id: str, action: str) -> tuple:
return False, f"Docker compose operation timed out ({timeout}s)"


def _parse_mem_value(s: str) -> float:
"""Parse Docker memory string like '256MiB' or '4GiB' to MB."""
s = s.strip()
multipliers = {"TiB": 1024*1024, "GiB": 1024, "MiB": 1, "KiB": 1/1024, "B": 1/(1024*1024)}
for suffix, mult in multipliers.items():
if s.endswith(suffix):
try:
return float(s[:-len(suffix)].strip()) * mult
except ValueError:
return 0.0
return 0.0


def _iso_now() -> str:
from datetime import datetime, timezone
return datetime.now(timezone.utc).isoformat()


def json_response(handler, code: int, body: dict):
payload = json.dumps(body).encode("utf-8")
handler.send_response(code)
Expand Down Expand Up @@ -217,9 +235,71 @@ def log_message(self, fmt, *args):
def do_GET(self):
if self.path == "/health":
json_response(self, 200, {"status": "ok", "version": VERSION})
elif self.path == "/v1/service/stats":
self._handle_service_stats()
else:
json_response(self, 404, {"error": "Not found"})

def _handle_service_stats(self):
"""Return CPU/memory stats for all Dream-managed containers."""
if not check_auth(self):
return

try:
result = subprocess.run(
["docker", "stats", "--no-stream",
"--format", '{"name":"{{.Name}}","cpu":"{{.CPUPerc}}","mem_usage":"{{.MemUsage}}","mem_percent":"{{.MemPerc}}","pids":"{{.PIDs}}"}'],
capture_output=True, text=True, timeout=10,
)

containers = []
for line in result.stdout.strip().splitlines():
if not line.strip():
continue
try:
raw = json.loads(line)
except json.JSONDecodeError:
continue

name = raw.get("name", "")
if not name.startswith("dream-"):
continue

cpu_str = raw.get("cpu", "0%").rstrip("%")
try:
cpu_percent = float(cpu_str)
except ValueError:
cpu_percent = 0.0

mem_parts = raw.get("mem_usage", "0B / 0B").split("/")
mem_used_mb = _parse_mem_value(mem_parts[0].strip()) if len(mem_parts) >= 1 else 0
mem_limit_mb = _parse_mem_value(mem_parts[1].strip()) if len(mem_parts) >= 2 else 0

mem_pct_str = raw.get("mem_percent", "0%").rstrip("%")
try:
mem_percent = float(mem_pct_str)
except ValueError:
mem_percent = 0.0

service_id = name.removeprefix("dream-")

containers.append({
"service_id": service_id,
"container_name": name,
"cpu_percent": round(cpu_percent, 1),
"memory_used_mb": round(mem_used_mb),
"memory_limit_mb": round(mem_limit_mb),
"memory_percent": round(mem_percent, 1),
"pids": int(raw.get("pids", "0") or "0"),
})

json_response(self, 200, {
"containers": containers,
"timestamp": _iso_now(),
})
except subprocess.TimeoutExpired:
json_response(self, 503, {"error": "docker stats timed out"})

def do_POST(self):
if self.path in ("/v1/extension/start", "/v1/extension/stop"):
action = "start" if self.path.endswith("/start") else "stop"
Expand Down
20 changes: 20 additions & 0 deletions dream-server/extensions/services/dashboard-api/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,26 @@ async def get_all_services() -> list[ServiceStatus]:

# --- System Metrics ---

def dir_size_gb(path: Path) -> float:
"""Calculate total size of a directory in GB. Returns 0.0 if path doesn't exist.

Skips symlinks to avoid following links outside DATA_DIR and double-counting.
"""
if not path.exists():
return 0.0
total = 0
try:
for f in path.rglob("*"):
try:
if f.is_file() and not f.is_symlink():
total += f.stat().st_size
except (PermissionError, OSError):
pass
except (PermissionError, OSError):
pass
return round(total / (1024**3), 2)


def get_disk_usage() -> DiskUsage:
"""Get disk usage for the Dream Server install directory."""
path = INSTALL_DIR if os.path.exists(INSTALL_DIR) else os.path.expanduser("~")
Expand Down
16 changes: 1 addition & 15 deletions dream-server/extensions/services/dashboard-api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
get_disk_usage, get_model_info, get_bootstrap_status,
get_uptime, get_cpu_metrics, get_ram_metrics,
get_llama_metrics, get_loaded_model, get_llama_context_size,
dir_size_gb,
)
from agent_monitor import collect_metrics

Expand Down Expand Up @@ -481,21 +482,6 @@ def _compute_storage():
vector_dir = Path(DATA_DIR) / "qdrant"
data_dir = Path(DATA_DIR)

def dir_size_gb(path: Path) -> float:
if not path.exists():
return 0.0
total = 0
try:
for f in path.rglob("*"):
if f.is_file():
try:
total += f.stat().st_size
except OSError:
pass
except (PermissionError, OSError):
pass
return round(total / (1024**3), 2)

disk_info = get_disk_usage()
models_gb = dir_size_gb(models_dir)
vector_gb = dir_size_gb(vector_dir)
Expand Down
Loading