Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
96 changes: 54 additions & 42 deletions das-cli/src/commands/system/system_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,16 @@ class SystemStatus(Command):
default=False,
required=False,
is_flag=True,
),
CommandOption(
["--cooldown", "-c"],
help="Sets how many seconds of cooldown before updating the metrics again.",
default=2,
required=False,
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
]


@inject
def __init__(
self,
Expand All @@ -55,10 +62,35 @@ def __init__(

super().__init__()

def run(
self,
stream: bool = False,
cooldown: int = 2,
) -> None:

self._settings.validate_configuration_file()

if stream:

if cooldown < 2:
raise ValueError("Cooldown value cannot be smaller than 2 seconds")

self._run_stream(cooldown)
return

# Solo snapshot
system_info = self._collect_snapshot()
self.stdout(
system_info,
stdout_type=StdoutType.MACHINE_READABLE,
)

self._format_info_for_display(system_info)

def _collect_snapshot(self) -> dict:

machine_info = {
"CPUInfo": self._sysinfo.get_cpu_info(),
"CPUInfo": self._sysinfo.get_cpu_info(interval=2),
"MemoryInfo": self._sysinfo.get_memory_info(),
"DisksInfo": self._sysinfo.get_disks_info(),
}
Expand Down Expand Up @@ -86,20 +118,20 @@ def _format_info_for_display(

machine_rows = [
{
"CPU (%)": cpu_info.get("cpuUsage", 0),
"CPU (Machine load / %)": cpu_info.get("cpuUsage", 0),
"CPU CORES": cpu_info.get("cpuTotalCores", 0),
"MEM USED (MB)": memory_info.get("usedMemory", 0),
"MEM TOTAL (MB)": memory_info.get("totalMemory", 0),
"MEM USED (GB)": memory_info.get("usedMemory", 0),
"MEM TOTAL (GB)": memory_info.get("totalMemory", 0),
}
]

print_table(
machine_rows,
columns=[
"CPU (%)",
"CPU (Machine load / %)",
"CPU CORES",
"MEM USED (MB)",
"MEM TOTAL (MB)",
"MEM USED (GB)",
"MEM TOTAL (GB)",
],
stdout=self.stdout,
)
Expand All @@ -114,8 +146,8 @@ def _format_info_for_display(
{
"DEVICE": disk.get("disk_device", "-"),
"MOUNT": disk.get("disk_mntpoint", "-"),
"USED (MB)": disk.get("disk_used_space", 0),
"TOTAL (MB)": disk.get("disk_total_space", 0),
"USED (GB)": disk.get("disk_used_space", 0),
"TOTAL (GB)": disk.get("disk_total_space", 0),
}
)

Expand All @@ -124,8 +156,8 @@ def _format_info_for_display(
columns=[
"DEVICE",
"MOUNT",
"USED (MB)",
"TOTAL (MB)",
"USED (GB)",
"TOTAL (GB)",
],
stdout=self.stdout,
)
Expand All @@ -142,8 +174,8 @@ def _format_info_for_display(
"CONTAINER INFO": info.get("image", "-"),
"PORT": info.get("port", "-"),
"AGE": info.get("age", "-"),
"CPU (% / Core)": info.get("cpu_percent", 0),
"MEMORY(MB)": info.get("memory_mb", 0),
"CPU (Container %)": info.get("cpu_percent", 0),
"MEMORY(GB)": info.get("memory_mb", 0),
"CONTAINER STATUS": info.get("status", "-"),
"SERVICE HEALTH": info.get("service_health", "-"),
}
Expand All @@ -156,15 +188,15 @@ def _format_info_for_display(
"CONTAINER INFO",
"PORT",
"AGE",
"CPU (% / Core)",
"MEMORY(MB)",
"CPU (Container %)",
"MEMORY(GB)",
"CONTAINER STATUS",
"SERVICE HEALTH",
],
stdout=self.stdout,
)

def _run_stream(self) -> None:
def _run_stream(self, cooldown) -> None:
latest_machine: dict[str, str] = {}
latest_services: dict[str, str] = {}

Expand All @@ -177,7 +209,7 @@ def machine_loop():
try:

data = {
"CPUInfo": self._sysinfo.get_cpu_info(),
"CPUInfo": self._sysinfo.get_cpu_info(cooldown),
"MemoryInfo": self._sysinfo.get_memory_info(),
"DisksInfo": self._sysinfo.get_disks_info(),
}
Expand All @@ -186,11 +218,10 @@ def machine_loop():
latest_machine.clear()
latest_machine.update(data)


except Exception as e:
print(f"[machine_loop] {e}")

time.sleep(2)

def docker_loop():

while True:
Expand All @@ -203,18 +234,19 @@ def docker_loop():
print(f"[docker_loop] {e}")

threading.Thread(
target=machine_loop,
target=docker_loop,
daemon=True,
).start()

threading.Thread(
target=docker_loop,
target=machine_loop,
daemon=True,
).start()

try:

while True:

with lock:
system_info = {
"machineInfo": dict(latest_machine),
Expand All @@ -225,31 +257,11 @@ def docker_loop():

self.stdout(system_info, stdout_type=StdoutType.MACHINE_READABLE, stream_mode=True)
self._format_info_for_display(system_info)
time.sleep(2)
time.sleep(cooldown)

except KeyboardInterrupt:
return

def run(
self,
stream: bool = False,
) -> None:

self._settings.validate_configuration_file()

if stream:
self._run_stream()
return

system_info = self._collect_snapshot()

self.stdout(
system_info,
stdout_type=StdoutType.MACHINE_READABLE,
)

self._format_info_for_display(system_info)


class SystemCli(CommandGroup):

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,9 @@ def _parse_container_stats(self, stats: dict) -> dict:
cpu_percent = self._calculate_cpu_percent(stats)

memory_usage = stats.get("memory_stats", {}).get("usage", 0)

memory_mb = round(
memory_usage / (1024 * 1024),
memory_usage / (1024 ** 3),
2,
)

Expand Down
16 changes: 8 additions & 8 deletions das-cli/src/common/systemutils/sys_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ def __init__(self):
"/var/lib/kubelet",
)

def get_cpu_info(self):
cpuUsagePercent = psutil.cpu_percent()
def get_cpu_info(self, interval):
cpuUsagePercent = psutil.cpu_percent(interval=interval)
cpuTotalCores = psutil.cpu_count()

return {"cpuUsage": cpuUsagePercent, "cpuTotalCores": cpuTotalCores}
Expand All @@ -30,8 +30,8 @@ def get_memory_info(self):
memoryInfo = psutil.virtual_memory()

return {
"totalMemory": round(memoryInfo.total / 1024**2),
"usedMemory": round(memoryInfo.used / 1024**2),
"totalMemory": f'{(memoryInfo.total / 1024**3):.1f}',
"usedMemory": f'{memoryInfo.used / 1024 ** 3:.2f}',
}

def get_disks_info(self):
Expand All @@ -47,15 +47,15 @@ def get_disks_info(self):
partition_mntpoint = partition.mountpoint

generalDiskInfo = shutil.disk_usage(partition.mountpoint)
diskTotalMB = round(generalDiskInfo.total / 1024**2)
diskUsedMB = round(generalDiskInfo.used / 1024**2)
diskTotalGB = f'{(generalDiskInfo.total / 1024**3):.1f}'
diskUsedGB = f'{generalDiskInfo.used / 1024**3:.2f}'

formatted_partitions.append(
{
"disk_device": partition_device,
"disk_mntpoint": partition_mntpoint,
"disk_total_space": diskTotalMB,
"disk_used_space": diskUsedMB,
"disk_total_space": diskTotalGB,
"disk_used_space": diskUsedGB,
}
)

Expand Down
5 changes: 3 additions & 2 deletions das-cli/tests/integration/test_system.bats
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,9 @@ teardown() {

for header in \
"MACHINE INFO" \
"CPU (%)" \
"MEM USED (MB)" \
"CPU (Machine load / %)" \
"CPU CORES" \
"MEM USED (GB)" \
"DISKS" \
"DEVICE" \
"SERVICES" \
Expand Down
87 changes: 68 additions & 19 deletions das-dashboard/README.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,78 @@
### DAS UI for Configuration/Setup
# DAS UI for Configuration & Setup

This is a simple and intuitive web interface, created to make das/das-cli's setup easier to do and to visualize.
The DAS UI is a simple and intuitive web interface designed to simplify the configuration, deployment, and monitoring of DAS environments through a visual workflow.

### Requirements:
## Requirements

- Node version (From version 22.0 and up)
(You can verify your node version by typing 'node -v')
* Node.js **22.0.0 or later**

- If node isn't version 22 or higher. We recommend that you run this command:
You can verify your installed Node.js version with:

> 'nvm install node --lts'
```bash
node -v
```

(Make sure you have the NVM tool, in case you don't have it, read [this](https://github.com/nvm-sh/nvm#install--update-script))

### Running the interface locally:
1. If you are in the root folder of the DAS project, change directory to './das-dashboard'
2. Run 'npm install'
3. Run 'npm run dev'
If your version is below 22, we recommend installing the latest LTS release:

### Running locally via docker:
```bash
nvm install node --lts
```

1. If you are in the root folder of the DAS project, change directory to './das-dashboard'
2. On your terminal, run 'docker build -t das-dashboard .'
3. Then run 'docker run -d --network host --name das-dashboard das-dashboard:latest' and you'll have a node server hosting the interface locally.
Make sure that NVM is installed on your system. If it is not, follow the installation instructions available in the NVM repository.

PS. Server will start by default on port 5173 (default vite port)
## Prerequisites

That's all, you should be able to run the web app perfectly and use its full features.
Before using the dashboard, ensure that:

* You are running the latest version of **das-cli**.
* All machines that will participate in the DAS architecture are running the latest version of **das-cli**.
* All machines in the architecture have a shared SSH key configured and properly authorized.

## Starting the Interface

Run the following script:

```bash
./scripts/start_interface.sh
```

## Recommended Usage Flow

### 1. Create an SSH Profile

Navigate to the **Profile** page and configure an SSH profile.

A profile consists of:

* **Username**
* **SSH Private Key** (used for all remote operations performed by the dashboard)

### 2. Configure DAS Services

Navigate to the **Configuration** page.

This page allows you to define the settings, parameters, and deployment options for all Distributed AtomSpace components, including:

* AtomDB
* Agents (such as Query Agents and Link Creation Agents)
* Brokers
* Component-specific configuration parameters

If you already have a DAS configuration file, you can import it by clicking **Load** and selecting a valid configuration JSON file.

### 3. Save Your Configuration

After customizing the configuration according to your environment, click **Save** to persist the settings.

### 4. Open the Dashboard

Navigate to the **Dashboard** page.

The dashboard automatically loads the previously saved configuration and discovers the configured machines based on the architecture definition.

Once loaded, the dashboard will:

* Connect to each configured machine.
* Collect and display runtime metrics.
* Monitor the status of deployed services.
* Provide visibility into resource utilization across the architecture.
8 changes: 4 additions & 4 deletions das-dashboard/backend/controllers/config_controllers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import Any, Optional

from fastapi import APIRouter, HTTPException
from fastapi import APIRouter
from fastapi.concurrency import run_in_threadpool
from fastapi.responses import JSONResponse
from pydantic import BaseModel

Expand Down Expand Up @@ -37,11 +38,10 @@ async def load_config(nested_config: dict[str, Any]):

@router.get("/hosts")
async def get_config_hosts():
hosts = await CONFIG_SERVICES.sync_dashboard_config()

await run_in_threadpool(WEB_CONFIG.load_config_dictionary)
return JSONResponse(
status_code=200,
content={"hosts": hosts},
content={"hosts": WEB_CONFIG.map_dashboard_hosts()},
)


Expand Down
Loading
Loading