Rewrite README with classification docs, logo, and status icons
Updated documentation to reflect the 4-tier classification system and uninstalled detection. Added SVG logo and color-coded status indicator dots. Added architecture diagram and collapsible API response example. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
136
README.md
136
README.md
@@ -1,15 +1,46 @@
|
|||||||
# ComfyUI Node Usage Stats
|
# ComfyUI Node Usage Stats
|
||||||
|
|
||||||
A ComfyUI custom node package that silently tracks which nodes and packages you actually use. Helps identify never-used packages that are safe to remove.
|
<p align="center">
|
||||||
|
<img src="docs/logo.svg" width="120" alt="Node Stats logo">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
A ComfyUI custom node package that silently tracks which nodes and packages you actually use. Helps identify unused packages that are safe to remove — keeping your ComfyUI install lean.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Tracks every node used in every workflow execution
|
- **Silent tracking** — hooks into every prompt submission, zero config needed
|
||||||
- Maps each node to its source package
|
- **Per-package classification** — packages are sorted into tiers based on usage recency
|
||||||
- SQLite storage for efficient querying
|
- **Smart aging** — packages gradually move from "recently unused" to "safe to remove" over time
|
||||||
- Per-package aggregated stats (total nodes, used/unused, execution counts)
|
- **Uninstall detection** — removed packages are flagged separately, historical data preserved
|
||||||
- Frontend dialog with never-used packages highlighted for removal
|
- **Expandable detail** — click any package to see individual node-level stats
|
||||||
- Expandable rows to see individual node-level stats within each package
|
- **Non-blocking** — DB writes happen in a background thread, no impact on workflow execution
|
||||||
|
|
||||||
|
## Package Classification
|
||||||
|
|
||||||
|
Packages are classified into tiers based on when they were last used:
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td><img src="docs/status_used.svg" width="18"> <b>Used</b></td>
|
||||||
|
<td>Actively used within the last month</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><img src="docs/status_unused_new.svg" width="18"> <b>Recently Unused</b></td>
|
||||||
|
<td>Not used yet, but tracking started less than a month ago — too early to judge</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><img src="docs/status_consider.svg" width="18"> <b>Consider Removing</b></td>
|
||||||
|
<td>Unused for 1–2 months — worth reviewing</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><img src="docs/status_safe.svg" width="18"> <b>Safe to Remove</b></td>
|
||||||
|
<td>Unused for 2+ months — confident removal candidate</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><img src="docs/status_uninstalled.svg" width="18"> <b>Uninstalled</b></td>
|
||||||
|
<td>Previously tracked but no longer installed — shown for reference</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@@ -24,28 +55,26 @@ Restart ComfyUI. Tracking starts immediately and silently.
|
|||||||
|
|
||||||
### UI
|
### UI
|
||||||
|
|
||||||
Click the **"Node Stats"** button in the ComfyUI menu. A dialog shows:
|
Click the **Node Stats** button (bar chart icon) in the ComfyUI top menu bar. A dialog shows:
|
||||||
|
|
||||||
- Summary: how many packages are never-used vs used
|
- **Summary bar** with counts for each classification tier
|
||||||
- **Never Used** section (highlighted) — safe to remove
|
- **Sections** for each tier, sorted from most actionable to least
|
||||||
- **Used** section sorted by least-to-most executions
|
- **Expandable rows** — click any package to see per-node execution counts and timestamps
|
||||||
- Click any row to expand and see individual node stats
|
|
||||||
|
|
||||||
### API
|
### API
|
||||||
|
|
||||||
| Endpoint | Method | Description |
|
| Endpoint | Method | Description |
|
||||||
|----------|--------|-------------|
|
|----------|--------|-------------|
|
||||||
| `/nodes-stats/packages` | GET | Per-package aggregated stats |
|
| `/nodes-stats/packages` | GET | Per-package aggregated stats with classification |
|
||||||
| `/nodes-stats/usage` | GET | Raw per-node usage data |
|
| `/nodes-stats/usage` | GET | Raw per-node usage data |
|
||||||
| `/nodes-stats/reset` | POST | Clear all tracked data |
|
| `/nodes-stats/reset` | POST | Clear all tracked data |
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl http://localhost:8188/nodes-stats/packages | python3 -m json.tool
|
curl http://localhost:8188/nodes-stats/packages | python3 -m json.tool
|
||||||
```
|
```
|
||||||
|
|
||||||
### Package stats response format
|
<details>
|
||||||
|
<summary>Example response</summary>
|
||||||
|
|
||||||
```json
|
```json
|
||||||
[
|
[
|
||||||
@@ -54,8 +83,9 @@ curl http://localhost:8188/nodes-stats/packages | python3 -m json.tool
|
|||||||
"total_executions": 42,
|
"total_executions": 42,
|
||||||
"used_nodes": 5,
|
"used_nodes": 5,
|
||||||
"total_nodes": 30,
|
"total_nodes": 30,
|
||||||
"never_used": false,
|
|
||||||
"last_seen": "2026-02-22T12:00:00+00:00",
|
"last_seen": "2026-02-22T12:00:00+00:00",
|
||||||
|
"installed": true,
|
||||||
|
"status": "used",
|
||||||
"nodes": [
|
"nodes": [
|
||||||
{
|
{
|
||||||
"class_type": "SAMDetectorCombined",
|
"class_type": "SAMDetectorCombined",
|
||||||
@@ -65,34 +95,70 @@ curl http://localhost:8188/nodes-stats/packages | python3 -m json.tool
|
|||||||
"last_seen": "2026-02-22T12:00:00+00:00"
|
"last_seen": "2026-02-22T12:00:00+00:00"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "ComfyUI-Unused-Nodes",
|
||||||
|
"total_executions": 0,
|
||||||
|
"used_nodes": 0,
|
||||||
|
"total_nodes": 12,
|
||||||
|
"last_seen": null,
|
||||||
|
"installed": true,
|
||||||
|
"status": "safe_to_remove",
|
||||||
|
"nodes": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
## File Structure
|
</details>
|
||||||
|
|
||||||
```
|
|
||||||
__init__.py # Entry point: prompt handler, API routes
|
|
||||||
mapper.py # class_type -> package name mapping
|
|
||||||
tracker.py # SQLite persistence and stats aggregation
|
|
||||||
js/
|
|
||||||
nodes_stats.js # Frontend: menu button + stats dialog
|
|
||||||
pyproject.toml # Package metadata
|
|
||||||
```
|
|
||||||
|
|
||||||
## How It Works
|
## How It Works
|
||||||
|
|
||||||
|
```
|
||||||
|
Queue Prompt ──> Prompt Handler ──> Extract class_types ──> Background Thread
|
||||||
|
│
|
||||||
|
┌─────────────────────────┘
|
||||||
|
▼
|
||||||
|
SQLite DB
|
||||||
|
usage_stats.db
|
||||||
|
┌──────────┐
|
||||||
|
│node_usage │ per-node counts & timestamps
|
||||||
|
│prompt_log │ full node list per prompt
|
||||||
|
└──────────┘
|
||||||
|
│
|
||||||
|
GET /nodes-stats/packages ◄──┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Mapper merges DB data
|
||||||
|
with NODE_CLASS_MAPPINGS
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Classify by recency ──> JSON response ──> UI Dialog
|
||||||
|
```
|
||||||
|
|
||||||
1. Registers a prompt handler via `PromptServer.instance.add_on_prompt_handler()`
|
1. Registers a prompt handler via `PromptServer.instance.add_on_prompt_handler()`
|
||||||
2. On every prompt submission, extracts `class_type` from each node
|
2. On every prompt submission, extracts `class_type` from each node in the workflow
|
||||||
3. Maps each class_type to its source package using `RELATIVE_PYTHON_MODULE`
|
3. Offloads recording to a background thread (non-blocking)
|
||||||
4. Stores per-node counts and timestamps in SQLite (`usage_stats.db`)
|
4. Maps each class_type to its source package using `RELATIVE_PYTHON_MODULE`
|
||||||
5. Also logs the full set of nodes per prompt for future trend analysis
|
5. Upserts per-node counts and timestamps into SQLite
|
||||||
|
6. On stats request, merges DB data with current node registry and classifies by recency
|
||||||
|
|
||||||
## Data Storage
|
## Data Storage
|
||||||
|
|
||||||
All data is stored in `usage_stats.db` in the package directory. Two tables:
|
All data is stored in `usage_stats.db` in the package directory.
|
||||||
|
|
||||||
- **node_usage**: per-node counts, first/last seen timestamps
|
| Table | Contents |
|
||||||
- **prompt_log**: JSON array of nodes used per prompt, with timestamp
|
|-------|----------|
|
||||||
|
| `node_usage` | Per-node: class_type, package, execution count, first/last seen |
|
||||||
|
| `prompt_log` | Per-prompt: timestamp, JSON array of all class_types used |
|
||||||
|
|
||||||
Use `POST /nodes-stats/reset` to clear all data.
|
Use `POST /nodes-stats/reset` to clear all data and start fresh.
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
__init__.py Entry point: prompt handler, API routes
|
||||||
|
mapper.py class_type → package name mapping
|
||||||
|
tracker.py SQLite persistence and stats aggregation
|
||||||
|
js/nodes_stats.js Frontend: menu button + stats dialog
|
||||||
|
pyproject.toml Package metadata
|
||||||
|
```
|
||||||
|
|||||||
32
docs/logo.svg
Normal file
32
docs/logo.svg
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="120" viewBox="0 0 120 120">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
|
||||||
|
<stop offset="0%" stop-color="#1a1a2e"/>
|
||||||
|
<stop offset="100%" stop-color="#16213e"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="bar1" x1="0" y1="1" x2="0" y2="0">
|
||||||
|
<stop offset="0%" stop-color="#4a9"/>
|
||||||
|
<stop offset="100%" stop-color="#4c6"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="bar2" x1="0" y1="1" x2="0" y2="0">
|
||||||
|
<stop offset="0%" stop-color="#e90"/>
|
||||||
|
<stop offset="100%" stop-color="#ec0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="bar3" x1="0" y1="1" x2="0" y2="0">
|
||||||
|
<stop offset="0%" stop-color="#e44"/>
|
||||||
|
<stop offset="100%" stop-color="#f66"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect width="120" height="120" rx="24" fill="url(#bg)"/>
|
||||||
|
<!-- Bars -->
|
||||||
|
<rect x="20" y="62" width="18" height="36" rx="4" fill="url(#bar1)" opacity="0.9"/>
|
||||||
|
<rect x="51" y="42" width="18" height="56" rx="4" fill="url(#bar2)" opacity="0.9"/>
|
||||||
|
<rect x="82" y="22" width="18" height="76" rx="4" fill="url(#bar3)" opacity="0.9"/>
|
||||||
|
<!-- Node dots -->
|
||||||
|
<circle cx="29" cy="50" r="5" fill="#fff" opacity="0.9"/>
|
||||||
|
<circle cx="60" cy="32" r="5" fill="#fff" opacity="0.9"/>
|
||||||
|
<circle cx="91" cy="16" r="4" fill="#fff" opacity="0.7"/>
|
||||||
|
<!-- Connecting lines -->
|
||||||
|
<line x1="34" y1="48" x2="55" y2="34" stroke="#fff" stroke-width="2" opacity="0.4"/>
|
||||||
|
<line x1="65" y1="30" x2="87" y2="18" stroke="#fff" stroke-width="2" opacity="0.3"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
4
docs/status_consider.svg
Normal file
4
docs/status_consider.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18">
|
||||||
|
<circle cx="9" cy="9" r="7" fill="#e90" opacity="0.2"/>
|
||||||
|
<circle cx="9" cy="9" r="5" fill="#e90"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 193 B |
4
docs/status_safe.svg
Normal file
4
docs/status_safe.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18">
|
||||||
|
<circle cx="9" cy="9" r="7" fill="#e44" opacity="0.2"/>
|
||||||
|
<circle cx="9" cy="9" r="5" fill="#e44"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 193 B |
4
docs/status_uninstalled.svg
Normal file
4
docs/status_uninstalled.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18">
|
||||||
|
<circle cx="9" cy="9" r="7" fill="#555" opacity="0.2"/>
|
||||||
|
<circle cx="9" cy="9" r="5" fill="#555"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 193 B |
4
docs/status_unused_new.svg
Normal file
4
docs/status_unused_new.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18">
|
||||||
|
<circle cx="9" cy="9" r="7" fill="#68f" opacity="0.2"/>
|
||||||
|
<circle cx="9" cy="9" r="5" fill="#68f"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 193 B |
4
docs/status_used.svg
Normal file
4
docs/status_used.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18">
|
||||||
|
<circle cx="9" cy="9" r="7" fill="#4a4" opacity="0.2"/>
|
||||||
|
<circle cx="9" cy="9" r="5" fill="#4a4"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 193 B |
Reference in New Issue
Block a user