performance-monitor.ts•6.66 kB
import { Logger } from "../../core/logger.js";
import { MetricsCollector } from "./metrics-collector.js";
import {
  PerformanceMetric,
  PerformanceAlert,
  PerformanceConfig,
  PerformanceThresholds,
} from "../../types/performance.types.js";
/**
 * Performance Monitor - Single responsibility: Control monitoring and alerts
 * Simple monitoring control for MCP-SQL database operations
 */
export class PerformanceMonitor {
  private isMonitoring: boolean = false;
  private monitoringInterval: NodeJS.Timeout | null = null;
  private metrics: PerformanceMetric[] = [];
  private alerts: PerformanceAlert[] = [];
  private config: PerformanceConfig;
  constructor(
    private metricsCollector: MetricsCollector,
    private logger: Logger,
    config?: Partial<PerformanceConfig>
  ) {
    // Default configuration for MCP-SQL
    this.config = {
      thresholds: {
        slowQueryThreshold: 1000, // 1 second
        highCpuThreshold: 80,
        highMemoryThreshold: 85,
        maxConnections: 100,
        maxErrorRate: 5,
      },
      monitoringInterval: 5000, // 5 seconds
      maxMetricsHistory: 1000,
      ...config,
    };
  }
  /**
   * Start performance monitoring
   */
  startMonitoring(customInterval?: number): void {
    if (this.isMonitoring) {
      this.logger.warn("Performance monitoring is already running");
      return;
    }
    const interval = customInterval || this.config.monitoringInterval;
    this.isMonitoring = true;
    this.logger.info("Performance monitoring started", {
      intervalMs: interval,
    });
    this.monitoringInterval = setInterval(async () => {
      await this.collectAndCheckMetrics();
    }, interval);
  }
  /**
   * Stop performance monitoring
   */
  stopMonitoring(): void {
    if (!this.isMonitoring) {
      this.logger.warn("Performance monitoring is not running");
      return;
    }
    this.isMonitoring = false;
    if (this.monitoringInterval) {
      clearInterval(this.monitoringInterval);
      this.monitoringInterval = null;
    }
    this.logger.info("Performance monitoring stopped", {
      totalMetrics: this.metrics.length,
      totalAlerts: this.alerts.length,
    });
  }
  /**
   * Get current metrics
   */
  getCurrentMetrics(): PerformanceMetric | null {
    return this.metrics.length > 0
      ? this.metrics[this.metrics.length - 1]
      : null;
  }
  /**
   * Get metrics history
   */
  getMetricsHistory(limit: number = 100): PerformanceMetric[] {
    return this.metrics.slice(-limit);
  }
  /**
   * Get active alerts
   */
  getActiveAlerts(): PerformanceAlert[] {
    return this.alerts.filter((alert) => !alert.resolved);
  }
  /**
   * Get all alerts
   */
  getAllAlerts(): PerformanceAlert[] {
    return this.alerts;
  }
  /**
   * Clear all metrics and alerts
   */
  clearMetrics(): void {
    this.metrics = [];
    this.alerts = [];
    this.metricsCollector.clearMetrics();
    this.logger.info("Performance metrics and alerts history cleared");
  }
  /**
   * Record slow query for monitoring
   */
  recordSlowQuery(query: string, executionTime: number): void {
    this.metricsCollector.recordQueryTime(executionTime);
    if (executionTime > this.config.thresholds.slowQueryThreshold) {
      this.generateAlert(
        "warning",
        "queryTime",
        executionTime,
        this.config.thresholds.slowQueryThreshold,
        `Slow query detected: ${executionTime}ms (threshold: ${this.config.thresholds.slowQueryThreshold}ms)`
      );
      this.logger.warn("Slow query detected", {
        query: query.substring(0, 100) + (query.length > 100 ? "..." : ""),
        executionTime: `${executionTime}ms`,
        threshold: `${this.config.thresholds.slowQueryThreshold}ms`,
      });
    }
  }
  // Private methods
  private async collectAndCheckMetrics(): Promise<void> {
    try {
      const metric = await this.metricsCollector.collectMetrics();
      this.addMetric(metric);
      this.checkThresholds(metric);
    } catch (error) {
      this.logger.error("Failed to collect metrics during monitoring", {
        error: error instanceof Error ? error.message : String(error),
      });
    }
  }
  private addMetric(metric: PerformanceMetric): void {
    this.metrics.push(metric);
    // Limit history size
    if (this.metrics.length > this.config.maxMetricsHistory) {
      this.metrics = this.metrics.slice(-this.config.maxMetricsHistory);
    }
  }
  private checkThresholds(metric: PerformanceMetric): void {
    const thresholds = this.config.thresholds;
    // Check CPU usage
    if (metric.cpuUsage && metric.cpuUsage > thresholds.highCpuThreshold) {
      this.generateAlert(
        "warning",
        "cpuUsage",
        metric.cpuUsage,
        thresholds.highCpuThreshold,
        `High CPU usage: ${metric.cpuUsage}%`
      );
    }
    // Check memory usage
    if (
      metric.memoryUsage &&
      metric.memoryUsage > thresholds.highMemoryThreshold
    ) {
      this.generateAlert(
        "warning",
        "memoryUsage",
        metric.memoryUsage,
        thresholds.highMemoryThreshold,
        `High memory usage: ${metric.memoryUsage}%`
      );
    }
    // Check connection count
    if (
      metric.connectionCount &&
      metric.connectionCount > thresholds.maxConnections
    ) {
      this.generateAlert(
        "critical",
        "connectionCount",
        metric.connectionCount,
        thresholds.maxConnections,
        `Connection limit exceeded: ${metric.connectionCount}`
      );
    }
    // Check error rate
    if (metric.errorRate && metric.errorRate > thresholds.maxErrorRate) {
      this.generateAlert(
        "critical",
        "errorRate",
        metric.errorRate,
        thresholds.maxErrorRate,
        `High error rate: ${metric.errorRate}%`
      );
    }
  }
  private generateAlert(
    type: "warning" | "critical" | "info",
    metric: string,
    currentValue: number,
    threshold: number,
    message: string
  ): void {
    const alert: PerformanceAlert = {
      id: `alert-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
      type,
      metric,
      threshold,
      currentValue,
      message,
      timestamp: new Date(),
      resolved: false,
    };
    this.alerts.push(alert);
    // Log alert based on severity
    if (type === "critical") {
      this.logger.error("Critical performance alert", alert);
    } else if (type === "warning") {
      this.logger.warn("Performance warning", alert);
    } else {
      this.logger.info("Performance info", alert);
    }
    // Limit alerts history
    if (this.alerts.length > 1000) {
      this.alerts = this.alerts.slice(-1000);
    }
  }
}