comfyui-node
Version:
ComfyUI Node.js Client
222 lines • 7.21 kB
JavaScript
/**
* Job Profiler for MultiWorkflowPool - Automatic per-node execution profiling
* ===========================================================================
*
* Captures detailed execution metrics for workflow jobs automatically:
* - Per-node execution timing
* - Progress tracking for nodes that emit progress events
* - Execution order and dependencies
* - Node types and metadata
*
* Usage:
* ```ts
* const pool = new MultiWorkflowPool({ enableProfiling: true });
* const jobId = await pool.submitJob(workflow);
*
* const results = await pool.waitForJobCompletion(jobId);
* console.log(results.profileStats);
* ```
*/
/**
* JobProfiler tracks execution metrics for a single workflow job.
*/
export class JobProfiler {
queuedAt;
startedAt;
completedAt;
promptId;
nodeProfiles = new Map();
lastExecutingNode = null;
constructor(queuedAt, workflowJson) {
this.queuedAt = queuedAt;
// Initialize node profiles from workflow structure
if (workflowJson) {
for (const [nodeId, nodeData] of Object.entries(workflowJson)) {
const node = nodeData;
if (node && typeof node === 'object' && node.class_type) {
this.nodeProfiles.set(nodeId, {
nodeId,
type: node.class_type,
title: node._meta?.title,
cached: false,
status: 'pending'
});
}
}
}
}
/**
* Record execution start event
*/
onExecutionStart(promptId) {
this.promptId = promptId;
if (!this.startedAt) {
this.startedAt = Date.now();
}
}
/**
* Record cached nodes
*/
onCachedNodes(nodeIds) {
const now = Date.now();
for (const nodeId of nodeIds) {
let profile = this.nodeProfiles.get(nodeId);
if (!profile) {
profile = {
nodeId,
cached: true,
status: 'cached'
};
this.nodeProfiles.set(nodeId, profile);
}
profile.cached = true;
profile.status = 'cached';
profile.startedAt = now;
profile.completedAt = now;
profile.duration = 0;
}
}
/**
* Record node execution start
*/
onNodeExecuting(nodeId) {
// Complete previous node if any
if (this.lastExecutingNode && this.lastExecutingNode !== nodeId) {
this.completeNode(this.lastExecutingNode);
}
// Start tracking new node
let profile = this.nodeProfiles.get(nodeId);
if (!profile) {
profile = {
nodeId,
cached: false,
status: 'executing'
};
this.nodeProfiles.set(nodeId, profile);
}
if (!profile.startedAt) {
profile.startedAt = Date.now();
profile.status = 'executing';
}
this.lastExecutingNode = nodeId;
}
/**
* Record node completion (when next node starts or execution ends)
*/
completeNode(nodeId) {
const profile = this.nodeProfiles.get(nodeId);
if (profile && !profile.completedAt && profile.startedAt) {
profile.completedAt = Date.now();
profile.duration = profile.completedAt - profile.startedAt;
profile.status = 'completed';
}
}
/**
* Record execution end (node: null event)
*/
onExecutionComplete() {
// Complete last executing node
if (this.lastExecutingNode) {
this.completeNode(this.lastExecutingNode);
this.lastExecutingNode = null;
}
// Mark any nodes still in "executing" state as completed
for (const profile of Array.from(this.nodeProfiles.values())) {
if (profile.status === 'executing' && !profile.completedAt && profile.startedAt) {
profile.completedAt = Date.now();
profile.duration = profile.completedAt - profile.startedAt;
profile.status = 'completed';
}
}
this.completedAt = Date.now();
}
/**
* Record progress event for a node
*/
onProgress(nodeId, value, max) {
const nodeIdStr = String(nodeId);
let profile = this.nodeProfiles.get(nodeIdStr);
if (!profile) {
profile = {
nodeId: nodeIdStr,
cached: false,
status: 'executing',
progressEvents: []
};
this.nodeProfiles.set(nodeIdStr, profile);
}
if (!profile.progressEvents) {
profile.progressEvents = [];
}
profile.progressEvents.push({
timestamp: Date.now(),
value,
max
});
}
/**
* Record node execution error
*/
onNodeError(nodeId, error) {
let profile = this.nodeProfiles.get(nodeId);
if (!profile) {
profile = {
nodeId,
cached: false,
status: 'failed'
};
this.nodeProfiles.set(nodeId, profile);
}
profile.status = 'failed';
profile.error = error;
profile.completedAt = Date.now();
if (profile.startedAt) {
profile.duration = profile.completedAt - profile.startedAt;
}
}
/**
* Generate final profile statistics
*/
getStats() {
const now = Date.now();
const completedAt = this.completedAt || now;
const startedAt = this.startedAt || this.queuedAt;
const nodes = Array.from(this.nodeProfiles.values());
// Calculate summary statistics
const executedNodes = nodes.filter(n => n.status === 'completed').length;
const cachedNodes = nodes.filter(n => n.cached).length;
const failedNodes = nodes.filter(n => n.status === 'failed').length;
const slowestNodes = nodes
.filter(n => n.duration && n.duration > 0)
.sort((a, b) => (b.duration || 0) - (a.duration || 0))
.slice(0, 5)
.map(n => ({
nodeId: n.nodeId,
type: n.type,
title: n.title,
duration: n.duration
}));
const progressNodes = nodes
.filter(n => n.progressEvents && n.progressEvents.length > 0)
.map(n => n.nodeId);
return {
promptId: this.promptId,
totalDuration: completedAt - this.queuedAt,
queueTime: startedAt - this.queuedAt,
executionTime: completedAt - startedAt,
queuedAt: this.queuedAt,
startedAt: this.startedAt,
completedAt,
nodes,
summary: {
totalNodes: nodes.length,
executedNodes,
cachedNodes,
failedNodes,
slowestNodes,
progressNodes
}
};
}
}
//# sourceMappingURL=job-profiler.js.map