Files
sammykumar-ComfyUI-SwissArm…/docs/web-js/RESOURCE_MONITOR.md
T

16 KiB
Raw Blame History

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/restart POST, /system_stats health 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

  1. app.registerExtension({ name: "comfyui_swissarmyknife.resource_monitor", setup }) wires the feature into ComfyUI.
  2. During setup() the extension:
    • Injects the style tag #swissarmyknife-restart-button-styles that 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.body so it floats independently of the toolbar DOM hierarchy.
    • Subscribes to swissarmyknife.monitor events on the ComfyUI API bridge, which surface the backend push updates every ~2 seconds.

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 by extractGPUModel().
  • 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:

  1. Disable + relabel the button to "Restarting..." and push a Toast (app.extensionManager.toast.add) describing what is happening.
  2. Fire fetch("/swissarmyknife/restart", { method: "POST" }). Any network failure is ignored because the backend tears down the HTTP connection during restart.
  3. Wait 2 seconds for shutdown to start, then poll /system_stats every second (up to 60s) until a 200 OK proves the server is alive again.
  4. 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.
  5. If the timeout expires, re-enable the button and warn the operator that manual refresh might be required.
  6. 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/status returns { 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.monitor events. handleMonitorUpdate() consumes the payload, runs the formatters (formatGB, formatPercent, formatTemp, formatGHz) and updates every matching tile.
  • Health checks: Restart logic pings /system_stats because 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.

  1. Instrumentation Layer

    • Mirror ProfilerX’s prestartup.py shim: wrap execution.execute, PromptExecutor.execute, and ExecutionList.__init__ so we can call a ProfilerManager analogue that records start_workflow, start_node, end_node, and end_workflow events.
    • 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 via psutil.Process().memory_info().rss, and roll running averages for nodes/workflows.
    • Keep a short-lived active_profiles dict plus a disk-backed history array, batching writes (ProfilerX saves every 5 workflows) so we don’t thrash the filesystem.
  2. Profiler API Surface

    • Expose REST endpoints modeled after ProfilerX’s server.py: /swissarmyknife/profiler/stats, /archives, /archive/:id/load, /archive/:id (DELETE). Responses should include current, latest, node_averages, workflow_averages, and a bounded history.
    • Support archive rotation by dumping JSON snapshots into data/archives/ and deleting them after loading—this matches ProfilerX’s archive_history, load_archive, and delete_archive.
    • Emit monitor broadcasts (existing swissarmyknife.monitor) plus a new profiler-specific event whenever a workflow completes so the UI can refresh instantly.
  3. 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 custom profiler:historyLoaded events 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.
  4. 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, and cacheHits/Misses so we can compute the same aggregates ProfilerX exposes.
    • Build archive management UI (list + load + delete) mirroring ProfilerX’s profilerx-stats-popup so operators can prune or revisit past runs without leaving the browser.
  5. 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 into method_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.ENABLED instead of requiring code edits, matching ProfilerX’s optional tracing workflow.

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 history
  • GET /swissarmyknife/profiler/archives - List archives
  • POST /swissarmyknife/profiler/archive - Create archive
  • POST /swissarmyknife/profiler/archive/:filename/load - Load archive
  • DELETE /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

  1. Click 📊 profiler button in floating monitor for quick stats
  2. Click "View Full History" for detailed modal analysis
  3. 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=False for 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.Tensor objects 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