flowengine-n8n-workflow-builder
Version:
Build n8n workflows from text using AI. Connect to Claude, Cursor, or any LLM to generate and validate n8n workflows with expert knowledge and intelligent auto-fixing. Built by FlowEngine. Now with real node parameter schemas from n8n packages!
259 lines • 9.64 kB
JavaScript
/**
* Performance Analyzer - Workflow Performance Analysis
*
* Analyze workflows for performance bottlenecks, resource usage,
* and optimization opportunities.
*/
/**
* Analyze workflow performance
*/
export function analyzePerformance(workflow) {
const nodeCount = workflow.nodes.length;
const connectionCount = Object.values(workflow.connections)
.reduce((total, conns) => {
return total + Object.values(conns)
.reduce((sum, arr) => sum + (arr?.reduce?.((s, a) => s + (a?.length || 0), 0) || 0), 0);
}, 0);
// Calculate workflow depth (longest path)
const depth = calculateWorkflowDepth(workflow);
// Count parallel paths
const parallelPaths = countParallelPaths(workflow);
// Detect bottlenecks
const bottlenecks = detectBottlenecks(workflow);
// Estimate execution time
const estimatedExecutionTime = estimateExecutionTime(workflow);
// Determine complexity
const complexity = calculateComplexity(nodeCount, depth, parallelPaths);
// Generate recommendations
const recommendations = generatePerformanceRecommendations(workflow, nodeCount, depth, parallelPaths, bottlenecks);
return {
complexity,
estimatedExecutionTime,
nodeCount,
connectionCount,
depth,
parallelPaths,
bottlenecks,
recommendations,
};
}
/**
* Calculate maximum workflow depth (longest execution path)
*/
function calculateWorkflowDepth(workflow) {
const trigger = workflow.nodes.find(n => n.type.includes('Trigger') || n.type.includes('trigger'));
if (!trigger)
return 0;
const depths = new Map();
depths.set(trigger.name, 0);
const queue = [trigger.name];
let maxDepth = 0;
while (queue.length > 0) {
const currentNode = queue.shift();
const currentDepth = depths.get(currentNode) || 0;
const connections = workflow.connections[currentNode];
if (!connections)
continue;
Object.values(connections).forEach((connArray) => {
connArray?.forEach?.((conns) => {
conns?.forEach?.((conn) => {
const nextDepth = currentDepth + 1;
const existingDepth = depths.get(conn.node) || 0;
if (nextDepth > existingDepth) {
depths.set(conn.node, nextDepth);
maxDepth = Math.max(maxDepth, nextDepth);
if (!queue.includes(conn.node)) {
queue.push(conn.node);
}
}
});
});
});
}
return maxDepth;
}
/**
* Count parallel execution paths
*/
function countParallelPaths(workflow) {
let maxParallel = 0;
Object.values(workflow.connections).forEach(connections => {
Object.values(connections).forEach((connArray) => {
const pathCount = connArray?.reduce?.((sum, conns) => sum + (conns?.length || 0), 0) || 0;
maxParallel = Math.max(maxParallel, pathCount);
});
});
return maxParallel;
}
/**
* Detect performance bottlenecks
*/
function detectBottlenecks(workflow) {
const bottlenecks = [];
workflow.nodes.forEach(node => {
// Check for heavy computation nodes
if (node.type === 'n8n-nodes-base.code') {
bottlenecks.push({
type: 'heavy-computation',
location: node.name,
impact: 'medium',
description: 'Code node may perform heavy computation',
});
}
// Check for API calls
if (node.type === 'n8n-nodes-base.httpRequest' ||
node.type.includes('api') ||
node.type.includes('Api')) {
bottlenecks.push({
type: 'external-api',
location: node.name,
impact: 'high',
description: 'External API call may cause latency',
});
}
// Check for sequential dependencies
const connections = workflow.connections[node.name];
if (connections && connections.main) {
const outputCount = connections.main?.reduce?.((sum, conns) => sum + (conns?.length || 0), 0) || 0;
if (outputCount > 3) {
bottlenecks.push({
type: 'excessive-branching',
location: node.name,
impact: 'medium',
description: `Node fans out to ${outputCount} paths`,
});
}
}
});
// Check for long sequential chains
const depth = calculateWorkflowDepth(workflow);
if (depth > 10) {
bottlenecks.push({
type: 'sequential-dependency',
location: 'Overall workflow',
impact: 'high',
description: `Long sequential chain of ${depth} nodes`,
});
}
return bottlenecks;
}
/**
* Estimate workflow execution time
*/
function estimateExecutionTime(workflow) {
let totalTime = 0;
workflow.nodes.forEach(node => {
// Base time per node
let nodeTime = 100; // 100ms base
// Adjust based on node type
if (node.type === 'n8n-nodes-base.httpRequest') {
nodeTime += 500; // +500ms for API calls
}
if (node.type === 'n8n-nodes-base.code') {
nodeTime += 200; // +200ms for code execution
}
if (node.type.includes('database') || node.type.includes('postgres') || node.type.includes('mysql')) {
nodeTime += 300; // +300ms for database queries
}
if (node.type.includes('agent') || node.type.includes('langchain')) {
nodeTime += 2000; // +2s for AI operations
}
totalTime += nodeTime;
});
return totalTime;
}
/**
* Calculate workflow complexity
*/
function calculateComplexity(nodeCount, depth, parallelPaths) {
const score = nodeCount + (depth * 2) + (parallelPaths * 3);
if (score < 10)
return 'low';
if (score < 25)
return 'medium';
if (score < 50)
return 'high';
return 'very-high';
}
/**
* Generate performance recommendations
*/
function generatePerformanceRecommendations(workflow, nodeCount, depth, parallelPaths, bottlenecks) {
const recommendations = [];
// Check for excessive nodes
if (nodeCount > 20) {
recommendations.push('Consider splitting into multiple workflows for better maintainability');
}
// Check for deep sequential chains
if (depth > 10) {
recommendations.push('Long sequential chain detected - consider parallel processing where possible');
}
// Check for lack of parallelization
if (nodeCount > 5 && parallelPaths < 2) {
recommendations.push('Workflow runs sequentially - identify opportunities for parallel execution');
}
// API-specific recommendations
const apiNodes = workflow.nodes.filter(n => n.type === 'n8n-nodes-base.httpRequest' || n.type.includes('api'));
if (apiNodes.length > 3) {
recommendations.push('Multiple API calls detected - consider batching or caching to reduce latency');
}
// Database-specific recommendations
const dbNodes = workflow.nodes.filter(n => n.type.includes('postgres') || n.type.includes('mysql') || n.type.includes('database'));
if (dbNodes.length > 2) {
recommendations.push('Multiple database queries - optimize with joins or batch operations');
}
// Error handling recommendations
const hasErrorHandling = workflow.nodes.some(n => n.type.includes('errorTrigger') || n.type.includes('stopAndError'));
if (!hasErrorHandling && nodeCount > 5) {
recommendations.push('Add error handling to prevent workflow failures from cascading');
}
// Bottleneck-specific recommendations
bottlenecks.forEach(bottleneck => {
if (bottleneck.impact === 'high') {
if (bottleneck.type === 'external-api') {
recommendations.push(`Optimize ${bottleneck.location} - add caching or retry logic`);
}
if (bottleneck.type === 'sequential-dependency') {
recommendations.push('Break up long sequential chains with parallel Split/Merge patterns');
}
}
});
return [...new Set(recommendations)]; // Remove duplicates
}
/**
* Calculate resource usage estimate
*/
export function estimateResourceUsage(workflow) {
let memoryScore = 0;
let cpuScore = 0;
let networkScore = 0;
workflow.nodes.forEach(node => {
// Memory-intensive nodes
if (node.type.includes('aggregate') || node.type.includes('merge')) {
memoryScore += 2;
}
if (node.type.includes('agent') || node.type.includes('langchain')) {
memoryScore += 3;
}
// CPU-intensive nodes
if (node.type === 'n8n-nodes-base.code') {
cpuScore += 2;
}
if (node.type.includes('crypto') || node.type.includes('compress')) {
cpuScore += 3;
}
// Network-intensive nodes
if (node.type === 'n8n-nodes-base.httpRequest') {
networkScore += 2;
}
if (node.type.includes('api') || node.type.includes('Api')) {
networkScore += 2;
}
});
return {
memory: memoryScore < 5 ? 'low' : memoryScore < 10 ? 'medium' : 'high',
cpu: cpuScore < 5 ? 'low' : cpuScore < 10 ? 'medium' : 'high',
network: networkScore < 5 ? 'low' : networkScore < 10 ? 'medium' : 'high',
};
}
//# sourceMappingURL=analyzer.js.map