From 6ef9c5bd686b6f326fa4f1b222027d9887c9485b Mon Sep 17 00:00:00 2001 From: LuqP2 Date: Wed, 24 Dec 2025 02:42:32 -0300 Subject: [PATCH] feat: added MetaHub Timer Node for accurate timing --- README.md | 16 +++++++-- __init__.py | 7 ++-- metahub_save_node.py | 31 ++---------------- timer_node.py | 78 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 99 insertions(+), 33 deletions(-) create mode 100644 timer_node.py diff --git a/README.md b/README.md index 43df0a8..55e3ffa 100644 --- a/README.md +++ b/README.md @@ -70,11 +70,21 @@ Connect any override input to replace auto-detected values: - `positive`, `negative` - `denoise` -**Performance Metrics (Advanced):** -For custom benchmarking workflows: +**Performance Metrics:** +- **Automatic**: VRAM peak and GPU device are auto-detected +- **Generation Time**: Use the **MetaHub Timer** node for accurate workflow timing + +**MetaHub Timer Node (Recommended for accurate timing):** +1. Add "MetaHub Timer" node at the START of your workflow +2. Connect any early input (e.g., from KSampler, LoadImage, etc.) to `trigger` +3. Connect the Timer's `elapsed_time` output to Save Node's `generation_time_override` +4. The `passthrough` output returns the original input unchanged (use for chaining) + +**Performance Overrides (Advanced):** +For custom benchmarking, connect these hidden inputs: - `vram_peak_mb` - Override VRAM peak measurement - `gpu_device_override` - Override GPU device name -- `generation_time_override` - Override generation time tracking +- `generation_time_override` - Manual generation time (or from Timer node) All overrides use `forceInput: True` (hidden from UI, connection-only). diff --git a/__init__.py b/__init__.py index 1aa0f9a..21d4d6e 100644 --- a/__init__.py +++ b/__init__.py @@ -4,13 +4,16 @@ Entry point for node registration """ from .metahub_save_node import MetaHubSaveNode +from .timer_node import MetaHubTimerNode NODE_CLASS_MAPPINGS = { - "MetaHubSaveNode": MetaHubSaveNode + "MetaHubSaveNode": MetaHubSaveNode, + "MetaHubTimerNode": MetaHubTimerNode } NODE_DISPLAY_NAME_MAPPINGS = { - "MetaHubSaveNode": "MetaHub Save Image" + "MetaHubSaveNode": "MetaHub Save Image", + "MetaHubTimerNode": "MetaHub Timer" } __all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"] diff --git a/metahub_save_node.py b/metahub_save_node.py index 500f8e2..383fc99 100644 --- a/metahub_save_node.py +++ b/metahub_save_node.py @@ -8,7 +8,6 @@ from . import metadata_utils as utils from .workflow_extractor import WorkflowExtractor _HINT_SHOWN = False -_WORKFLOW_TIMINGS = {} # {workflow_id: start_time} class MetaHubSaveNode: @@ -186,23 +185,7 @@ class MetaHubSaveNode: extra_pnginfo=None, unique_id=None, ): - global _HINT_SHOWN, _WORKFLOW_TIMINGS - import time - - # Auto-timing using workflow tracking - # Use a combination of inputs as workflow identifier - workflow_id = id(prompt) if prompt is not None else id(images) - - # If this is a new workflow, mark start time - if workflow_id not in _WORKFLOW_TIMINGS: - _WORKFLOW_TIMINGS[workflow_id] = time.time() - # Clean old entries (keep only last 10 workflows) - if len(_WORKFLOW_TIMINGS) > 10: - oldest_key = min(_WORKFLOW_TIMINGS.keys(), key=lambda k: _WORKFLOW_TIMINGS[k]) - del _WORKFLOW_TIMINGS[oldest_key] - - # Calculate elapsed time since workflow start - workflow_elapsed = time.time() - _WORKFLOW_TIMINGS[workflow_id] + global _HINT_SHOWN try: workflow_json = utils.get_workflow_json(extra_pnginfo) @@ -266,22 +249,14 @@ class MetaHubSaveNode: height, width = images[0].shape[0], images[0].shape[1] - # Determine final generation time with priority: - # 1. Manual override (generation_time_override input) - # 2. Auto-measured workflow elapsed time (from global tracking) - # 3. Legacy generation_time input (deprecated) + # Determine final generation time + # Priority: generation_time_override (from MetaHub Timer Node) > legacy generation_time if generation_time_override is not None: final_time = generation_time_override - print(f"[MetaHub] Using generation_time_override: {final_time:.2f}s") - elif workflow_elapsed > 0.1: # Sanity check (> 100ms) - final_time = workflow_elapsed - print(f"[MetaHub] Using auto-measured workflow time: {final_time:.2f}s") elif generation_time > 0: final_time = generation_time - print(f"[MetaHub] Using legacy generation_time: {final_time:.2f}s") else: final_time = 0.0 - print(f"[MetaHub] No timing data available, using fallback: {final_time:.2f}s") # Collect GPU metrics (auto-detect) gpu_metrics = utils.collect_gpu_metrics() diff --git a/timer_node.py b/timer_node.py new file mode 100644 index 0000000..a1fe059 --- /dev/null +++ b/timer_node.py @@ -0,0 +1,78 @@ +""" +MetaHub Timer Node for ComfyUI +Measures workflow execution time from start to finish +""" + +import time + +# Global storage for workflow timers +_TIMER_START_TIMES = {} + + +class MetaHubTimerNode: + """ + Timer node that measures workflow execution time. + + Usage: + 1. Place this node at the START of your workflow + 2. Connect any input (image, latent, conditioning, etc.) to trigger it early + 3. Connect the 'elapsed_time' output to MetaHub Save Node's 'generation_time_override' + + The timer starts when this node first executes in a workflow run. + """ + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + # Accept any type as passthrough to ensure early execution + "trigger": ("*",), + }, + } + + RETURN_TYPES = ("*", "FLOAT") + RETURN_NAMES = ("passthrough", "elapsed_time") + FUNCTION = "measure_time" + CATEGORY = "image/save" + DESCRIPTION = "Measures workflow execution time for MetaHub Save Node" + + def measure_time(self, trigger): + """ + Measures elapsed time since workflow start. + + Args: + trigger: Any input to trigger early execution (passed through unchanged) + + Returns: + (passthrough, elapsed_time): Original input and elapsed time in seconds + """ + current_time = time.time() + + # Use trigger object ID as workflow identifier + # This works because ComfyUI reuses the same objects within a workflow execution + workflow_id = id(trigger) + + # Start timer on first execution + if workflow_id not in _TIMER_START_TIMES: + _TIMER_START_TIMES[workflow_id] = current_time + print(f"[MetaHub Timer] Started timer for workflow") + + # Cleanup old timers (keep only last 20) + if len(_TIMER_START_TIMES) > 20: + oldest_key = min(_TIMER_START_TIMES.keys(), key=lambda k: _TIMER_START_TIMES[k]) + del _TIMER_START_TIMES[oldest_key] + + # Calculate elapsed time + elapsed = current_time - _TIMER_START_TIMES[workflow_id] + + # Passthrough the input unchanged + return elapsed time + return (trigger, elapsed) + + +NODE_CLASS_MAPPINGS = { + "MetaHubTimerNode": MetaHubTimerNode +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "MetaHubTimerNode": "MetaHub Timer" +}