/**
* Утилиты для мониторинга производительности
*/
export interface PerformanceMetrics {
operation: string;
duration: number;
timestamp: string;
success: boolean;
error?: string;
metadata?: Record<string, unknown>;
}
export interface PerformanceConfig {
enableMetrics: boolean;
slowOperationThreshold: number; // в миллисекундах
maxMetricsHistory: number;
}
export class PerformanceMonitor {
private static instance: PerformanceMonitor;
private metrics: PerformanceMetrics[] = [];
private config: PerformanceConfig = {
enableMetrics: true,
slowOperationThreshold: 5000, // 5 секунд
maxMetricsHistory: 1000,
};
private constructor() {}
static getInstance(): PerformanceMonitor {
if (!PerformanceMonitor.instance) {
PerformanceMonitor.instance = new PerformanceMonitor();
}
return PerformanceMonitor.instance;
}
/**
* Настраивает мониторинг
*/
configure(config: Partial<PerformanceConfig>): void {
this.config = { ...this.config, ...config };
}
/**
* Измеряет производительность операции
*/
async measure<T>(operation: string, fn: () => Promise<T>, metadata?: Record<string, unknown>): Promise<T> {
if (!this.config.enableMetrics) {
return fn();
}
const startTime = Date.now();
const timestamp = new Date().toISOString();
let success = true;
let error: string | undefined;
try {
const result = await fn();
return result;
} catch (err) {
success = false;
error = err instanceof Error ? err.message : String(err);
throw err;
} finally {
const duration = Date.now() - startTime;
this.recordMetric({
operation,
duration,
timestamp,
success,
error,
metadata,
});
// Логируем медленные операции
if (duration > this.config.slowOperationThreshold) {
console.warn(`[PERFORMANCE] Slow operation detected: ${operation} took ${duration}ms`, {
operation,
duration,
threshold: this.config.slowOperationThreshold,
metadata,
});
}
}
}
/**
* Записывает метрику
*/
private recordMetric(metric: PerformanceMetrics): void {
this.metrics.push(metric);
// Ограничиваем размер истории
if (this.metrics.length > this.config.maxMetricsHistory) {
this.metrics = this.metrics.slice(-this.config.maxMetricsHistory);
}
// Логируем метрику в JSON формате
console.log(
JSON.stringify({
timestamp: metric.timestamp,
level: "PERFORMANCE",
event: "operation_completed",
data: {
operation: metric.operation,
duration: metric.duration,
success: metric.success,
error: metric.error,
metadata: metric.metadata,
},
})
);
}
/**
* Получает статистику по операциям
*/
getStats(operation?: string): {
total: number;
successful: number;
failed: number;
averageDuration: number;
minDuration: number;
maxDuration: number;
slowOperations: number;
} {
const filteredMetrics = operation ? this.metrics.filter((m) => m.operation === operation) : this.metrics;
if (filteredMetrics.length === 0) {
return {
total: 0,
successful: 0,
failed: 0,
averageDuration: 0,
minDuration: 0,
maxDuration: 0,
slowOperations: 0,
};
}
const successful = filteredMetrics.filter((m) => m.success).length;
const failed = filteredMetrics.length - successful;
const durations = filteredMetrics.map((m) => m.duration);
const averageDuration = durations.reduce((sum, d) => sum + d, 0) / durations.length;
const minDuration = Math.min(...durations);
const maxDuration = Math.max(...durations);
const slowOperations = filteredMetrics.filter((m) => m.duration > this.config.slowOperationThreshold).length;
return {
total: filteredMetrics.length,
successful,
failed,
averageDuration: Math.round(averageDuration),
minDuration,
maxDuration,
slowOperations,
};
}
/**
* Получает последние метрики
*/
getRecentMetrics(count: number = 10): PerformanceMetrics[] {
return this.metrics.slice(-count);
}
/**
* Очищает историю метрик
*/
clearMetrics(): void {
this.metrics = [];
}
/**
* Получает метрики по операциям
*/
getMetricsByOperation(): Record<string, PerformanceMetrics[]> {
const grouped: Record<string, PerformanceMetrics[]> = {};
for (const metric of this.metrics) {
if (!grouped[metric.operation]) {
grouped[metric.operation] = [];
}
grouped[metric.operation].push(metric);
}
return grouped;
}
/**
* Проверяет здоровье системы
*/
getHealthStatus(): {
status: "healthy" | "degraded" | "unhealthy";
issues: string[];
metrics: {
totalOperations: number;
successRate: number;
averageResponseTime: number;
slowOperationsCount: number;
};
} {
const stats = this.getStats();
const issues: string[] = [];
let status: "healthy" | "degraded" | "unhealthy" = "healthy";
// Проверяем успешность операций
const successRate = stats.total > 0 ? (stats.successful / stats.total) * 100 : 100;
if (successRate < 95) {
issues.push(`Low success rate: ${successRate.toFixed(1)}%`);
status = successRate < 80 ? "unhealthy" : "degraded";
}
// Проверяем время ответа
if (stats.averageDuration > this.config.slowOperationThreshold) {
issues.push(`High average response time: ${stats.averageDuration}ms`);
status = "degraded";
}
// Проверяем количество медленных операций
if (stats.slowOperations > stats.total * 0.1) {
issues.push(`High number of slow operations: ${stats.slowOperations}/${stats.total}`);
status = "degraded";
}
return {
status,
issues,
metrics: {
totalOperations: stats.total,
successRate: Math.round(successRate * 100) / 100,
averageResponseTime: stats.averageDuration,
slowOperationsCount: stats.slowOperations,
},
};
}
}
/**
* Декоратор для автоматического измерения производительности
*/
export function measurePerformance(operation: string, metadata?: Record<string, unknown>) {
return function (target: any, propertyName: string, descriptor: PropertyDescriptor) {
const method = descriptor.value;
const monitor = PerformanceMonitor.getInstance();
descriptor.value = async function (...args: any[]) {
return monitor.measure(operation, () => method.apply(this, args), metadata);
};
return descriptor;
};
}
/**
* Утилита для измерения производительности функции
*/
export async function measure<T>(
operation: string,
fn: () => Promise<T>,
metadata?: Record<string, unknown>
): Promise<T> {
const monitor = PerformanceMonitor.getInstance();
return monitor.measure(operation, fn, metadata);
}