supa-seed
Version:
A constraint-aware, framework-agnostic database seeding framework with deep PostgreSQL business logic discovery and MakerKit integration support
356 lines โข 13.6 kB
JavaScript
;
/**
* Performance Monitoring System
* Phase 6, Checkpoint F1 - Comprehensive performance tracking and optimization
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.PerformanceMonitor = void 0;
exports.monitor = monitor;
exports.withPerformanceMonitoring = withPerformanceMonitoring;
const logger_1 = require("./logger");
class PerformanceMonitor {
/**
* Initialize performance monitoring with default thresholds
*/
static initialize() {
// Set default performance thresholds (in milliseconds)
this.setThreshold('database_query', 1000);
this.setThreshold('ai_generation', 30000);
this.setThreshold('schema_detection', 5000);
this.setThreshold('template_rendering', 2000);
this.setThreshold('file_operations', 500);
this.setThreshold('configuration_update', 1000);
logger_1.Logger.info('๐ Performance monitoring initialized');
}
/**
* Start timing an operation
*/
static startOperation(operationName, component, metadata) {
const operationId = `${component}_${operationName}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const timing = {
operationName,
component,
startTime: performance.now(),
memoryUsage: {
before: process.memoryUsage()
},
success: false,
metadata
};
this.activeOperations.set(operationId, timing);
logger_1.Logger.debug(`โฑ๏ธ Started timing: ${operationName}`, { operationId, component });
return operationId;
}
/**
* End timing an operation
*/
static endOperation(operationId, success = true, errorCode, additionalMetadata) {
const timing = this.activeOperations.get(operationId);
if (!timing) {
logger_1.Logger.warn('โ ๏ธ Attempted to end non-existent operation:', operationId);
return null;
}
const endTime = performance.now();
const duration = endTime - timing.startTime;
const memoryAfter = process.memoryUsage();
// Calculate memory delta
const memoryDelta = {
rss: memoryAfter.rss - timing.memoryUsage.before.rss,
heapUsed: memoryAfter.heapUsed - timing.memoryUsage.before.heapUsed,
heapTotal: memoryAfter.heapTotal - timing.memoryUsage.before.heapTotal,
external: memoryAfter.external - timing.memoryUsage.before.external,
arrayBuffers: memoryAfter.arrayBuffers - timing.memoryUsage.before.arrayBuffers
};
// Update timing
timing.endTime = endTime;
timing.duration = duration;
timing.success = success;
timing.errorCode = errorCode;
timing.memoryUsage.after = memoryAfter;
timing.memoryUsage.delta = memoryDelta;
if (additionalMetadata) {
timing.metadata = { ...timing.metadata, ...additionalMetadata };
}
// Check performance threshold
this.checkPerformanceThreshold(timing);
// Record metrics
this.recordMetric({
name: `${timing.component}_${timing.operationName}_duration`,
value: duration,
unit: 'ms',
timestamp: new Date(),
tags: {
component: timing.component,
operation: timing.operationName,
success: success.toString()
},
metadata: timing.metadata
});
// Record memory usage
this.recordMetric({
name: `${timing.component}_${timing.operationName}_memory_delta`,
value: memoryDelta.heapUsed / 1024 / 1024, // Convert to MB
unit: 'mb',
timestamp: new Date(),
tags: {
component: timing.component,
operation: timing.operationName,
type: 'heap_used_delta'
}
});
// Remove from active operations
this.activeOperations.delete(operationId);
logger_1.Logger.debug(`โ
Completed timing: ${timing.operationName}`, {
duration: `${duration.toFixed(2)}ms`,
success,
memoryDelta: `${(memoryDelta.heapUsed / 1024 / 1024).toFixed(2)}MB`
});
return timing;
}
/**
* Record a custom metric
*/
static recordMetric(metric) {
this.metrics.push(metric);
// Maintain metrics history size
if (this.metrics.length > this.MAX_METRICS_HISTORY) {
this.metrics.shift();
}
// Log significant metrics
if (this.isSignificantMetric(metric)) {
logger_1.Logger.info('๐ Performance metric:', {
name: metric.name,
value: `${metric.value}${metric.unit}`,
tags: metric.tags
});
}
}
/**
* Set performance threshold for an operation
*/
static setThreshold(operationKey, thresholdMs) {
this.performanceThresholds.set(operationKey, thresholdMs);
logger_1.Logger.debug(`๐ฏ Set performance threshold: ${operationKey} = ${thresholdMs}ms`);
}
/**
* Check if operation exceeded performance threshold
*/
static checkPerformanceThreshold(timing) {
const thresholdKey = `${timing.component}_${timing.operationName}`;
const threshold = this.performanceThresholds.get(thresholdKey) ||
this.performanceThresholds.get(timing.operationName);
if (threshold && timing.duration && timing.duration > threshold) {
logger_1.Logger.warn('๐ Performance threshold exceeded:', {
operation: thresholdKey,
duration: `${timing.duration.toFixed(2)}ms`,
threshold: `${threshold}ms`,
overrun: `${(timing.duration - threshold).toFixed(2)}ms`,
recommendation: this.getPerformanceRecommendation(timing)
});
}
}
/**
* Get performance recommendation based on timing
*/
static getPerformanceRecommendation(timing) {
const { component, operationName, duration = 0 } = timing;
if (component === 'database' && duration > 5000) {
return 'Consider optimizing database queries or adding indexes';
}
if (component === 'ai' && duration > 60000) {
return 'AI operation taking too long, consider smaller batch sizes or timeout';
}
if (component === 'template' && duration > 3000) {
return 'Template rendering slow, check for complex logic or large datasets';
}
if (operationName.includes('file') && duration > 1000) {
return 'File operations slow, check disk I/O or file sizes';
}
return 'Consider optimizing this operation for better performance';
}
/**
* Check if metric is significant enough to log
*/
static isSignificantMetric(metric) {
// Log all error cases
if (metric.tags.success === 'false') {
return true;
}
// Log slow operations
if (metric.unit === 'ms' && metric.value > 5000) {
return true;
}
// Log high memory usage
if (metric.unit === 'mb' && Math.abs(metric.value) > 50) {
return true;
}
return false;
}
/**
* Get performance statistics
*/
static getPerformanceStats() {
const durationMetrics = this.metrics.filter(m => m.unit === 'ms');
const memoryMetrics = this.metrics.filter(m => m.unit === 'mb');
// Calculate averages
const totalDuration = durationMetrics.reduce((sum, m) => sum + m.value, 0);
const averageResponseTime = durationMetrics.length > 0 ? totalDuration / durationMetrics.length : 0;
// Find slowest operations
const slowestOperations = durationMetrics
.sort((a, b) => b.value - a.value)
.slice(0, 10)
.map(m => ({
operation: m.name,
duration: m.value,
timestamp: m.timestamp
}));
// Memory trends
const memoryTrends = memoryMetrics
.filter(m => m.name.includes('memory_delta'))
.slice(-50)
.map(m => ({
timestamp: m.timestamp,
heapUsed: m.value,
component: m.tags.component
}));
// Error rate
const errorMetrics = durationMetrics.filter(m => m.tags.success === 'false');
const errorRate = durationMetrics.length > 0 ? errorMetrics.length / durationMetrics.length : 0;
// Component performance
const componentPerformance = {};
for (const metric of durationMetrics) {
const component = metric.tags.component;
if (!componentPerformance[component]) {
componentPerformance[component] = {
totalDuration: 0,
operationCount: 0,
errorCount: 0
};
}
componentPerformance[component].totalDuration += metric.value;
componentPerformance[component].operationCount += 1;
if (metric.tags.success === 'false') {
componentPerformance[component].errorCount += 1;
}
}
// Calculate averages for components
for (const component in componentPerformance) {
const stats = componentPerformance[component];
stats.averageDuration = stats.totalDuration / stats.operationCount;
delete stats.totalDuration;
}
return {
totalOperations: durationMetrics.length,
averageResponseTime,
slowestOperations,
memoryTrends,
errorRate,
componentPerformance
};
}
/**
* Get system resource usage
*/
static getSystemStats() {
return {
memory: process.memoryUsage(),
uptime: process.uptime(),
activeOperations: this.activeOperations.size,
cpuUsage: process.cpuUsage()
};
}
/**
* Clear metrics history
*/
static clearMetrics() {
this.metrics.length = 0;
logger_1.Logger.info('๐งน Performance metrics cleared');
}
/**
* Export metrics for external monitoring
*/
static exportMetrics(format = 'json') {
if (format === 'prometheus') {
return this.formatPrometheusMetrics();
}
return JSON.stringify({
timestamp: new Date().toISOString(),
metrics: this.metrics,
activeOperations: Array.from(this.activeOperations.values()),
systemStats: this.getSystemStats(),
performanceStats: this.getPerformanceStats()
}, null, 2);
}
/**
* Format metrics in Prometheus format
*/
static formatPrometheusMetrics() {
let output = '';
// Group metrics by name
const metricGroups = new Map();
for (const metric of this.metrics) {
if (!metricGroups.has(metric.name)) {
metricGroups.set(metric.name, []);
}
metricGroups.get(metric.name).push(metric);
}
// Format each metric group
for (const [name, metrics] of metricGroups) {
output += `# HELP ${name} Performance metric from supa-seed\n`;
output += `# TYPE ${name} gauge\n`;
for (const metric of metrics.slice(-10)) { // Last 10 entries
const tagString = Object.entries(metric.tags)
.map(([key, value]) => `${key}="${value}"`)
.join(',');
output += `${name}{${tagString}} ${metric.value} ${metric.timestamp.getTime()}\n`;
}
output += '\n';
}
return output;
}
}
exports.PerformanceMonitor = PerformanceMonitor;
PerformanceMonitor.metrics = [];
PerformanceMonitor.activeOperations = new Map();
PerformanceMonitor.performanceThresholds = new Map();
PerformanceMonitor.MAX_METRICS_HISTORY = 10000;
/**
* Performance monitoring decorator
*/
function monitor(component, operationName) {
return function (target, propertyName, descriptor) {
const method = descriptor.value;
const opName = operationName || propertyName;
descriptor.value = async function (...args) {
const operationId = PerformanceMonitor.startOperation(opName, component, {
argsCount: args.length
});
try {
const result = await method.apply(this, args);
PerformanceMonitor.endOperation(operationId, true);
return result;
}
catch (error) {
PerformanceMonitor.endOperation(operationId, false, error.name);
throw error;
}
};
};
}
/**
* Async wrapper for performance monitoring
*/
async function withPerformanceMonitoring(operation, component, operationName, metadata) {
const operationId = PerformanceMonitor.startOperation(operationName, component, metadata);
try {
const result = await operation();
PerformanceMonitor.endOperation(operationId, true);
return result;
}
catch (error) {
PerformanceMonitor.endOperation(operationId, false, error.name);
throw error;
}
}
exports.default = PerformanceMonitor;
//# sourceMappingURL=performance-monitor.js.map