16 KiB
Resource Monitor - Floating Heads-Up Monitor Bar
Status: Phase 2 frontend + restart flow complete
Component: Web JavaScript Extension (web/js/resource_monitor.js)
Overview
The Resource Monitor extension draws a floating glass "heads-up" bar in the ComfyUI viewport that streams live CPU/RAM/GPU telemetry and exposes an always-visible Restart button. The widget detaches from the legacy dropdown entry in swiss-army-knife.js so that operators can trigger a restart while simultaneously watching resource pressure build in real time.
Key Capabilities:
- Fixed, centered monitor bar injected directly into
document.body(not the top toolbar) - Inline CPU, RAM, temperature, GPU utilization, and VRAM stats rendered with gradient fills
- Automatic discovery of the hardware that is actually available from the backend
- Fully wired restart workflow: confirmation toasts,
/swissarmyknife/restartPOST,/system_statshealth polling, and cache-busting reload - WebSocket live updates via
api.addEventListener("swissarmyknife.monitor", handler)so the UI stays in sync after the initial REST fetch
File Layout
web/
├── js/
│ ├── resource_monitor.js # Floating bar + restart workflow (this document)
│ └── swiss-army-knife.js # Legacy dropdown restart entry (still shipped but redundant)
└── css/
└── restart-button.css # Historical styles, no longer referenced
Extension Registration & Setup Flow
app.registerExtension({ name: "comfyui_swissarmyknife.resource_monitor", setup })wires the feature into ComfyUI.- During
setup()the extension:- Injects the style tag
#swissarmyknife-restart-button-stylesthat defines the floating glassmorphism wrapper, gradient logic, and button micro-interactions. - Performs an initial
fetch("/swissarmyknife/monitor/status")to understand which monitors should render. - Dynamically builds monitor cells through
createMonitorDisplay(label, id)based on backend capabilities (psutil / pynvml availability, detected GPUs, exposed temperatures, etc.). - Appends the Restart button returned from
createRestartButton()after the telemetry cells. - Appends the completed wrapper to
document.bodyso it floats independently of the toolbar DOM hierarchy. - Subscribes to
swissarmyknife.monitorevents on the ComfyUI API bridge, which surface the backend push updates every ~2 seconds.
- Injects the style tag
UI Layout & Styling
The wrapper #swissarmyknife-resource-monitor is positioned fixed at top: 50px and horizontally centered with left: 50% + transform: translateX(-50%). It uses a translucent background, blur filter, rounded 16px corners, and an inset highlight so it reads like a lightweight HUD.
Each telemetry cell (.swissarmyknife-monitor) displays LABEL VALUE pairs:
- CPU % and RAM (GB used) render whenever psutil is available.
- CPU temperature renders when exposed by the backend sensors.
- For every GPU device the backend reports as
available, the UI renders utilization %, VRAM usage, and (optionally) GPU temp rows. Labels include a compact per-GPU suffix (e.g.,GPU 0 (3090Ti),VRAM 0 (A6000)), generated byextractGPUModel(). - Cells call
updateMonitorValue(id, value, percent)whenever new data arrives. The helper paints a linear gradient from left to right using thresholds<50% (green #22c55e),<70% (yellow #eab308),<90% (orange #f97316), and>=90% (red #ef4444)to immediately communicate pressure.
Workflow Execution Glow
When a workflow starts running (execution_start event) the monitor wrapper gains the swissarmyknife-is-executing class. CSS registered via @property --swissarmyknife-gradient-angle and the swissarmyknife-resource-monitor-rotation keyframes animates a rotating multi-color gradient on paired pseudo-elements: ::before renders a soft blurred plume outside the pill and ::after masks the gradient into a thin border ring so the fill stays untouched. Together they produce the subtle border glow pictured in design mocks without overwhelming the metrics. The class is removed on execution_success, execution_error, execution_cancelled, or execution_end so the glow shuts off the instant a workflow finishes or fails.
The Restart button shares the wrapper so the operator can watch the monitors while the restart is underway. It follows the ComfyUI .comfyui-button base class with custom danger colors, disabled state, and a divider pseudo-element (::before) to visually separate it from the monitors.
Restart Workflow
createRestartButton() wires the full restart experience:
- Disable + relabel the button to "Restarting..." and push a Toast (
app.extensionManager.toast.add) describing what is happening. - Fire
fetch("/swissarmyknife/restart", { method: "POST" }). Any network failure is ignored because the backend tears down the HTTP connection during restart. - Wait 2 seconds for shutdown to start, then poll
/system_statsevery second (up to 60s) until a 200 OK proves the server is alive again. - When the health check succeeds, pop a success toast and hard reload
window.location.reload(true)to pick up new JS bundles without a stale cache. - If the timeout expires, re-enable the button and warn the operator that manual refresh might be required.
- All errors propagate to the console with the
[SwissArmyKnife][ResourceMonitor]prefix and surface a Toast that explains the failure.
The behavior mirrors the menu command restart logic, but the inline placement plus stateful button make the process much more discoverable.
Data Sources & Update Loop
- Initial snapshot:
GET /swissarmyknife/monitor/statusreturns{ hardware, gpu }. The UI uses this payload to decide which tiles to render (e.g., no psutil => only GPU tiles, no pynvml => skip utilization tiles, etc.). - Live updates: The backend broadcasts
swissarmyknife.monitorevents.handleMonitorUpdate()consumes the payload, runs the formatters (formatGB,formatPercent,formatTemp,formatGHz) and updates every matching tile. - Health checks: Restart logic pings
/system_statsbecause it is small, unauthenticated, and always hosted by ComfyUI itself. - Debug logs: Controlled by
SwissArmyKnife.debug_mode. When enabled the console prints setup/start/end banners, AJAX failures, and periodic polling output (every 5 attempts during restart polling).
Dependency Matrix & Graceful Degradation
| Backend Dependency | UI Impact |
|---|---|
psutil |
CPU %, RAM, CPU Temp tiles render. Without it, those tiles are simply not created. |
py-cpuinfo |
Used server-side to enrich CPU labels; UI automatically falls back to -- when a value is missing. |
torch |
Enables VRAM reporting when pynvml is absent. The UI still displays VRAM rows because the backend formats the bytes. |
pynvml |
Unlocks GPU utilization and temperature metrics. Without it only VRAM tiles (and restart) appear. |
No JavaScript errors are thrown when a capability is missing; the UI just renders the tiles that make sense for the current machine.
Browser Refresh Requirements
Because this is a pure JavaScript widget, no ComfyUI restart is required. Developers should perform a hard refresh (Ctrl/Cmd + Shift + R) whenever resource_monitor.js changes to force the new bundle to load and to re-inject the CSS.
Roadmap
Near-Term Enhancements
- Clear VRAM button and backend endpoint wiring
- Per-tile context menus (click to copy usage, open GPU inspector, etc.)
- Visibility presets so laptop users can hide GPU tiles
- Historical sparklines per metric
- Settings surface for sampling interval + toast verbosity
ProfilerX Parity Plan
To reach the level of workflow profiling offered by ComfyUI ProfilerX, we will deliver the following phases with concrete implementation hooks learned from their codebase.
-
Instrumentation Layer
- Mirror ProfilerX’s
prestartup.pyshim: wrapexecution.execute,PromptExecutor.execute, andExecutionList.__init__so we can call aProfilerManageranalogue that recordsstart_workflow,start_node,end_node, andend_workflowevents. - Track cache hits by checking
caches.outputs[current_item]before/after execution just like ProfilerX’s wrappers do, and collect tensor shapes via helpers similar to_get_tensor_sizes. - Reset CUDA peak stats per node (
torch.cuda.reset_peak_memory_stats) to capture accurate VRAM deltas, persist RAM viapsutil.Process().memory_info().rss, and roll running averages for nodes/workflows. - Keep a short-lived
active_profilesdict plus a disk-backedhistoryarray, batching writes (ProfilerX saves every 5 workflows) so we don’t thrash the filesystem.
- Mirror ProfilerX’s
-
Profiler API Surface
- Expose REST endpoints modeled after ProfilerX’s
server.py:/swissarmyknife/profiler/stats,/archives,/archive/:id/load,/archive/:id(DELETE). Responses should includecurrent,latest,node_averages,workflow_averages, and a boundedhistory. - Support archive rotation by dumping JSON snapshots into
data/archives/and deleting them after loading—this matches ProfilerX’sarchive_history,load_archive, anddelete_archive. - Emit monitor broadcasts (existing
swissarmyknife.monitor) plus a new profiler-specific event whenever a workflow completes so the UI can refresh instantly.
- Expose REST endpoints modeled after ProfilerX’s
-
Profiler Dashboard UI
- Attach a menu button the way ProfilerX does in
web/index.ts: locate.comfyui-menu-right, create a.comfyui-button-group, and inject a button that toggles a detailed popup. - Reuse their approach of listening to
app.addEventListener("executed")and customprofiler:historyLoadedevents so tabs stay in sync after archives load. - Implement tabbed content similar to
web/ui/tabs.ts, with menu-level mini stats, per-node sortable tables, cache hit/miss breakdown, and inline charts (SVG or canvas) fed from the REST payloads.
- Attach a menu button the way ProfilerX does in
-
Historical Storage & Analytics
- Persist up to ~10k workflow entries on disk (same order-of-magnitude as ProfilerX’s
max_history) with auto-archiving when limits are hit, plus asynchronous saves via a background executor. - Augment each record with
executionOrder,averages, tensor sizes, andcacheHits/Missesso we can compute the same aggregates ProfilerX exposes. - Build archive management UI (list + load + delete) mirroring ProfilerX’s
profilerx-stats-popupso operators can prune or revisit past runs without leaving the browser.
- Persist up to ~10k workflow entries on disk (same order-of-magnitude as ProfilerX’s
-
Advanced Execution Tracking
- Once Profiler parity lands, add an opt-in tracker patterned after
execution_core.ExecutionTracker: wrap internal ComfyUI methods with decorators that push method names, durations, and stack depth intomethod_traces.json. - Stream summarized trace metadata to the UI so a “Deep Trace” tab can highlight slow internals without forcing users to open raw JSON files.
- Surface a settings toggle (Swiss Army Knife config) that sets
ExecutionTracker.ENABLEDinstead of requiring code edits, matching ProfilerX’s optional tracing workflow.
- Once Profiler parity lands, add an opt-in tracker patterned after
Keep the file-level documentation synchronized with each feature drop so operators know which telemetry to expect.
Profiler Implementation Status
Last Updated: November 28, 2025 Status: ✅ Phase 1-3 Complete | 🚧 Phase 4-5 In Progress
Implemented Features
✅ Phase 1: Backend Instrumentation
- Prestartup hook system (
nodes/utils/resource_monitor/prestartup.py) - ProfilerManager singleton with workflow/node tracking
- VRAM/RAM measurement with graceful degradation
- Cache hit detection and tensor shape tracking
- Batched async saves (every 5 workflows)
- Auto-archiving at 10k workflow limit
✅ Phase 2: REST API & WebSocket
GET /swissarmyknife/profiler/stats- Latest stats and historyGET /swissarmyknife/profiler/archives- List archivesPOST /swissarmyknife/profiler/archive- Create archivePOST /swissarmyknife/profiler/archive/:filename/load- Load archiveDELETE /swissarmyknife/profiler/archive/:filename- Delete archive- WebSocket broadcasts on workflow completion
✅ Phase 3: Profiler UI
- Profiler button (📊) in floating resource monitor
- Glassmorphic popup with:
- Circular progress gauge
- 6-card stats grid (Time, VRAM, RAM, Cache, Nodes). The Total Time card now renders long runs in minutes+seconds (e.g.,
5m 50.9s) while sub-minute workflows stay in seconds for better readability. - Top 10 slowest nodes table
- "View Full History" and "Clear History" buttons
- Full-screen modal with 4 tabs (Latest, Previous, Analytics, Settings)
- Real-time WebSocket updates
- Auto-refresh on workflow execution
Zero-Config Design
Profiler is enabled by default with:
- No user configuration required
- Automatic startup integration
- Graceful degradation when dependencies missing
- < 1% execution overhead
Usage
- Click 📊 profiler button in floating monitor for quick stats
- Click "View Full History" for detailed modal analysis
- Use Settings tab for archive management
Known Limitations
VRAM Tracking
- Requires NVIDIA CUDA: VRAM tracking only works on systems with CUDA-enabled GPUs (PyTorch CUDA API)
- CPU-only mode: No VRAM tracking available when running on CPU-only systems (logs warning at startup)
- AMD/Intel GPUs: VRAM tracking not supported (PyTorch CUDA-specific implementation)
- Allocation timing: Captures memory at node execution boundaries only, not internal allocations during node execution
- Shared memory: Cannot distinguish between node-specific and globally shared VRAM usage across the process
RAM Tracking
- Requires psutil: RAM tracking disabled if psutil package not installed
- Process-level only: Measures total process memory, not per-node isolation
- System overhead: Includes Python interpreter and ComfyUI framework overhead in measurements
Cache Hit Detection
- Not yet implemented: Currently always reports
cache_hit=Falsefor all node executions - Planned enhancement: Future integration with ComfyUI's cache system to detect actual cache hits
Tensor Shape Tracking
- Requires PyTorch: Only available when torch package is installed
- Limited types: Only tracks
torch.Tensorobjects in inputs/outputs - Nested structures: May miss tensors deeply nested in complex data structures
Timing Accuracy
- Async operations: May not capture accurate timing for nodes with async GPU operations that return before completion
- Python GIL: Subject to Python Global Interpreter Lock overhead
- System scheduling: Wall-clock timing affected by system scheduling and other processes
History & Storage
- Auto-archive limit: Automatically archives after 10,000 workflows to prevent unbounded disk growth
- Disk I/O: Batched saves (every 5 workflows) may delay persistence during crashes
- Memory footprint: Active profiling adds ~500KB-2MB per workflow in memory before archiving
Hardware Detection
- Best-effort detection: Dependency availability checked at startup only, not dynamically
- Silent degradation: Missing dependencies log warnings but don't fail loudly to user
- CUDA initialization: First CUDA call may add latency during ProfilerManager initialization
Troubleshooting
VRAM shows as 0 or null:
- Verify CUDA is available: Check console for "CUDA available: True" message
- Ensure GPU is NVIDIA with CUDA support
- Confirm PyTorch installed with CUDA support:
python -c "import torch; print(torch.cuda.is_available())"
RAM tracking not working:
- Install psutil:
pip install psutil - Restart ComfyUI server after installation
Profiler not capturing data:
- Check console for "[SwissArmyKnife][Profiler]" messages
- Verify profiler enabled in settings (enabled by default)
- Look for warnings about dependency availability
Next Steps
- Enhanced archive management UI (Phase 4)
- Historical charts and sparklines
- Advanced execution tracking (Phase 5)
- Implement cache hit detection