Skip to content

Commit e0a00b7

Browse files
committed
feat: enhance system info display with user info, tree formatting, and PYTHONPATH validation
- Add UserInfo node showing username, uid, gid below OS section - Implement part_of_previous metadata for sub-category tree rendering with │ connector - Add PYTHONPATH validation with red highlighting for invalid paths - Update Framework section to only show existing frameworks - Fix metadata type annotation to support Any values - Remove unused variable to pass linting - Update example documentation for all new features Signed-off-by: Keiven Chang <[email protected]>
1 parent 3eedf92 commit e0a00b7

File tree

1 file changed

+68
-38
lines changed

1 file changed

+68
-38
lines changed

deploy/dynamo_check.py

Lines changed: 68 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
3434
System info (hostname=jensen-linux, IP=10.111.122.133)
3535
├─ OS Ubuntu 24.04.1 LTS (Noble Numbat) (Linux 6.11.0-28-generic x86_64), Memory=26.7/125.5 GiB, Cores=32
36+
├─ User info: user=ubuntu, uid=1000, gid=1000
3637
├─ ✅ NVIDIA GPU NVIDIA RTX 6000 Ada Generation, driver 570.133.07, CUDA 12.8, Power=26.14/300.00 W, Memory=289/49140 MiB
3738
├─ File Permissions
3839
│ ├─ ✅ Dynamo workspace ($HOME/dynamo) writable
@@ -52,20 +53,20 @@
5253
│ ├─ ✅ PyTorch 2.7.1+cu128, ✅torch.cuda.is_available
5354
│ └─ PYTHONPATH $HOME/dynamo/components/frontend/src:$HOME/dynamo/components/planner/src:$HOME/dynamo/components/backends/vllm/src:$HOME/dynamo/components/backends/sglang/src:$HOME/dynamo/components/backends/trtllm/src:$HOME/dynamo/components/backends/llama_cpp/src:$HOME/dynamo/components/backends/mocker/src
5455
├─ 🤖Framework
55-
│ ├─ ✅ vllm 0.10.1.1, module=/opt/vllm/vllm/__init__.py, exec=/opt/dynamo/venv/bin/vllm
56-
│ ├─ ❓ sglang -
57-
│ └─ ❓ tensorrt_llm -
56+
│ ├─ ✅ vLLM: 0.10.1.1, module=/opt/vllm/vllm/__init__.py, exec=/opt/dynamo/venv/bin/vllm
57+
│ └─ ✅ Sglang: 0.3.0, module=/opt/sglang/sglang/__init__.py
5858
└─ Dynamo $HOME/dynamo, SHA: a03d29066, Date: 2025-08-30 16:22:29 PDT
5959
├─ ✅ Runtime components ai-dynamo-runtime 0.4.1
60-
├─ /opt/dynamo/venv/lib/python3.12/site-packages/ai_dynamo_runtime-0.4.1.dist-info created=2025-08-30 19:14:29 PDT
61-
├─ /opt/dynamo/venv/lib/python3.12/site-packages/ai_dynamo_runtime.pth modified=2025-08-30 19:14:29 PDT
62-
│ │ └─ → $HOME/dynamo/lib/bindings/python/src
60+
/opt/dynamo/venv/lib/python3.12/site-packages/ai_dynamo_runtime-0.4.1.dist-info: created=2025-08-30 19:14:29 PDT
61+
/opt/dynamo/venv/lib/python3.12/site-packages/ai_dynamo_runtime.pth: modified=2025-08-30 19:14:29 PDT
62+
│ │ └─ →: $HOME/dynamo/lib/bindings/python/src
6363
│ ├─ ✅ dynamo._core $HOME/dynamo/lib/bindings/python/src/dynamo/_core.cpython-312-x86_64-linux-gnu.so, modified=2025-08-30 19:14:29 PDT
6464
│ ├─ ✅ dynamo.logits_processing $HOME/dynamo/lib/bindings/python/src/dynamo/logits_processing/__init__.py
6565
│ ├─ ✅ dynamo.nixl_connect $HOME/dynamo/lib/bindings/python/src/dynamo/nixl_connect/__init__.py
6666
│ ├─ ✅ dynamo.llm $HOME/dynamo/lib/bindings/python/src/dynamo/llm/__init__.py
6767
│ └─ ✅ dynamo.runtime $HOME/dynamo/lib/bindings/python/src/dynamo/runtime/__init__.py
6868
└─ ✅ Framework components ai-dynamo (via PYTHONPATH)
69+
│ /opt/dynamo/venv/lib/python3.12/site-packages/ai_dynamo-0.5.0.dist-info: created=2025-09-05 16:20:35 PDT
6970
├─ ✅ dynamo.frontend $HOME/dynamo/components/frontend/src/dynamo/frontend/__init__.py
7071
├─ ✅ dynamo.llama_cpp $HOME/dynamo/components/backends/llama_cpp/src/dynamo/llama_cpp/__init__.py
7172
├─ ✅ dynamo.mocker $HOME/dynamo/components/backends/mocker/src/dynamo/mocker/__init__.py
@@ -116,7 +117,7 @@ class NodeInfo:
116117
status: NodeStatus = NodeStatus.NONE # Status indicator
117118

118119
# Additional metadata as key-value pairs
119-
metadata: Dict[str, str] = field(default_factory=dict)
120+
metadata: Dict[str, Any] = field(default_factory=dict)
120121

121122
# Tree structure
122123
children: List["NodeInfo"] = field(default_factory=list)
@@ -142,7 +143,11 @@ def render(
142143

143144
# Determine the connector
144145
if not is_root:
145-
connector = "└─" if is_last else "├─"
146+
# Check if this is a sub-category item
147+
if self.metadata and self.metadata.get("part_of_previous"):
148+
connector = "│"
149+
else:
150+
connector = "└─" if is_last else "├─"
146151
current_prefix = prefix + connector + " "
147152
else:
148153
current_prefix = ""
@@ -171,8 +176,10 @@ def render(
171176
if self.metadata:
172177
metadata_items = []
173178
for k, v in self.metadata.items():
174-
# Format all metadata consistently as "key=value"
175-
metadata_items.append(f"{k}={v}")
179+
# Skip internal metadata that shouldn't be displayed
180+
if k != "part_of_previous":
181+
# Format all metadata consistently as "key=value"
182+
metadata_items.append(f"{k}={v}")
176183

177184
if metadata_items:
178185
# Use consistent separator (comma) for all metadata
@@ -286,14 +293,14 @@ def __init__(self, hostname: Optional[str] = None, thorough_check: bool = False)
286293
# Add OS info
287294
self.add_child(OSInfo())
288295

296+
# Add user info
297+
self.add_child(UserInfo())
298+
289299
# Add GPU info
290300
gpu_info = GPUInfo()
291301
# Always add GPU info so we can see errors like "nvidia-smi not found"
292302
self.add_child(gpu_info)
293303

294-
# Add user info
295-
self.add_child(UserInfo())
296-
297304
# Add file permissions check
298305
self.add_child(FilePermissionsInfo(thorough_check=self.thorough_check))
299306

@@ -1488,6 +1495,8 @@ def __init__(self):
14881495
("tensorrt_llm", "tensorRT LLM"),
14891496
]
14901497

1498+
frameworks_found = 0
1499+
14911500
for module_name, display_name in frameworks_to_check:
14921501
# Special handling for TensorRT-LLM to avoid NVML crashes
14931502
if module_name == "tensorrt_llm":
@@ -1498,14 +1507,13 @@ def __init__(self):
14981507
f"/usr/lib/python{python_version}/dist-packages",
14991508
]
15001509

1501-
found_in_system = False
15021510
for pkg_path in system_packages:
15031511
if os.path.exists(pkg_path):
15041512
tensorrt_dirs = [
15051513
d for d in os.listdir(pkg_path) if "tensorrt_llm" in d
15061514
]
15071515
if tensorrt_dirs:
1508-
found_in_system = True
1516+
frameworks_found += 1
15091517
# Try to get version safely
15101518
try:
15111519
result = subprocess.run(
@@ -1549,20 +1557,14 @@ def __init__(self):
15491557
self.add_child(package_info)
15501558
break
15511559

1552-
if not found_in_system:
1553-
package_info = PythonPackageInfo(
1554-
package_name=display_name,
1555-
version="-",
1556-
is_framework=True,
1557-
is_installed=False,
1558-
)
1559-
self.add_child(package_info)
1560+
# Don't add anything if not found in system
15601561
continue
15611562

15621563
# Regular import for other frameworks
15631564
try:
15641565
module = __import__(module_name)
15651566
version = getattr(module, "__version__", "installed")
1567+
frameworks_found += 1
15661568

15671569
# Get module path
15681570
module_path = None
@@ -1585,14 +1587,18 @@ def __init__(self):
15851587
)
15861588
self.add_child(package_info)
15871589
except (ImportError, Exception):
1588-
# Framework not installed - show with "-"
1589-
package_info = PythonPackageInfo(
1590-
package_name=display_name,
1591-
version="-",
1592-
is_framework=True,
1593-
is_installed=False,
1594-
)
1595-
self.add_child(package_info)
1590+
# Framework not installed - don't add it
1591+
pass
1592+
1593+
# If no frameworks found, set status to ERROR (X) and show what's missing
1594+
if frameworks_found == 0:
1595+
self.status = NodeStatus.ERROR
1596+
# List all the frameworks that were checked but not found
1597+
missing_frameworks = []
1598+
for module_name, display_name in frameworks_to_check:
1599+
missing_frameworks.append(f"no {module_name}")
1600+
missing_text = ", ".join(missing_frameworks)
1601+
self.desc = missing_text
15961602

15971603

15981604
class PythonPackageInfo(NodeInfo):
@@ -1646,9 +1652,22 @@ def __init__(self, pythonpath: str):
16461652
if pythonpath:
16471653
# Split by colon and replace home in each path
16481654
paths = pythonpath.split(":")
1649-
display_paths = [self._replace_home_with_var(p) for p in paths]
1655+
display_paths = []
1656+
has_invalid_paths = False
1657+
1658+
for p in paths:
1659+
display_path = self._replace_home_with_var(p)
1660+
# Check if path exists and is accessible
1661+
if not os.path.exists(p) or not os.access(p, os.R_OK):
1662+
display_paths.append(
1663+
f"\033[38;5;196m{display_path}\033[0m"
1664+
) # Bright red path
1665+
has_invalid_paths = True
1666+
else:
1667+
display_paths.append(display_path)
1668+
16501669
display_pythonpath = ":".join(display_paths)
1651-
status = NodeStatus.INFO
1670+
status = NodeStatus.WARNING if has_invalid_paths else NodeStatus.INFO
16521671
else:
16531672
display_pythonpath = "not set"
16541673
status = NodeStatus.WARNING # Show warning when PYTHONPATH is not set
@@ -1794,12 +1813,17 @@ def _find_dist_info(self) -> Optional[NodeInfo]:
17941813
stat = os.stat(path)
17951814
timestamp = self._format_timestamp_pdt(stat.st_ctime)
17961815
return NodeInfo(
1797-
label=display_path,
1816+
label=f" {display_path}",
17981817
desc=f"created={timestamp}",
17991818
status=NodeStatus.INFO,
1819+
metadata={"part_of_previous": True},
18001820
)
18011821
except Exception:
1802-
return NodeInfo(label=display_path, status=NodeStatus.INFO)
1822+
return NodeInfo(
1823+
label=f" {display_path}",
1824+
status=NodeStatus.INFO,
1825+
metadata={"part_of_previous": True},
1826+
)
18031827
return None
18041828

18051829
def _find_pth_file(self) -> Optional[NodeInfo]:
@@ -1814,9 +1838,10 @@ def _find_pth_file(self) -> Optional[NodeInfo]:
18141838
stat = os.stat(pth_path)
18151839
timestamp = self._format_timestamp_pdt(stat.st_mtime)
18161840
node = NodeInfo(
1817-
label=display_path,
1841+
label=f" {display_path}",
18181842
desc=f"modified={timestamp}",
18191843
status=NodeStatus.INFO,
1844+
metadata={"part_of_previous": True},
18201845
)
18211846

18221847
# Read where it points to
@@ -1873,13 +1898,18 @@ def __init__(self, workspace_dir: str, thorough_check: bool = False):
18731898
stat = os.stat(path)
18741899
timestamp = self._format_timestamp_pdt(stat.st_ctime)
18751900
dist_node = NodeInfo(
1876-
label=display_path,
1901+
label=f" {display_path}",
18771902
desc=f"created={timestamp}",
18781903
status=NodeStatus.INFO,
1904+
metadata={"part_of_previous": True},
18791905
)
18801906
self.add_child(dist_node)
18811907
except Exception:
1882-
dist_node = NodeInfo(label=display_path, status=NodeStatus.INFO)
1908+
dist_node = NodeInfo(
1909+
label=f" {display_path}",
1910+
status=NodeStatus.INFO,
1911+
metadata={"part_of_previous": True},
1912+
)
18831913
self.add_child(dist_node)
18841914
break
18851915

0 commit comments

Comments
 (0)