Skip to main content
Glama
MetricsCollector.ts15.2 kB
/** * Real-time Metrics Collector for WordPress MCP Server * Integrates with existing client and cache systems */ import { PerformanceMonitor, PerformanceMetrics } from "./PerformanceMonitor.js"; import type { CacheStats } from "@/cache/CacheManager.js"; import type { ClientStats } from "@/types/client.js"; import { ConfigHelpers } from "@/config/Config.js"; import { LoggerFactory } from "@/utils/logger.js"; export interface CollectorConfig { enableRealTime: boolean; collectInterval: number; enableToolTracking: boolean; enableRequestInterception: boolean; enableCacheIntegration: boolean; enableSystemMetrics: boolean; } export interface RequestMetadata { toolName?: string; siteId?: string; endpoint: string; method: string; startTime: number; fromCache: boolean; } export interface ToolExecutionContext { toolName: string; parameters: Record<string, unknown>; startTime: number; siteId: string | undefined; } /** * Metrics Collector - Central hub for all performance data */ export class MetricsCollector { private monitor: PerformanceMonitor; private config: CollectorConfig; private activeRequests: Map<string, RequestMetadata> = new Map(); private activeTools: Map<string, ToolExecutionContext> = new Map(); private clientInstances: Map<string, unknown> = new Map(); private cacheManagers: Map<string, unknown> = new Map(); private logger = LoggerFactory.performance(); private realTimeInterval?: NodeJS.Timeout | undefined; constructor(monitor: PerformanceMonitor, config: Partial<CollectorConfig> = {}) { this.monitor = monitor; this.config = { enableRealTime: true, collectInterval: 30000, // 30 seconds enableToolTracking: true, enableRequestInterception: true, enableCacheIntegration: true, enableSystemMetrics: true, ...config, }; // Only enable real-time collection in appropriate environments if (this.config.enableRealTime && (ConfigHelpers.isDev() || ConfigHelpers.isProd())) { this.startRealTimeCollection(); } else if (ConfigHelpers.isTest() || ConfigHelpers.isCI()) { this.logger.debug("Skipping real-time metrics collection in test/CI environment"); } } /** * Register a WordPress client for monitoring */ registerClient(siteId: string, client: unknown): void { this.clientInstances.set(siteId, client); if (this.config.enableRequestInterception) { this.interceptClientRequests(siteId, client as Record<string, unknown>); } } /** * Register a cache manager for monitoring */ registerCacheManager(siteId: string, cacheManager: unknown): void { this.cacheManagers.set(siteId, cacheManager); } /** * Start tracking a tool execution */ startToolExecution(toolName: string, parameters: Record<string, unknown>, siteId?: string): string { const executionId = `${toolName}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; this.activeTools.set(executionId, { toolName, parameters, startTime: Date.now(), siteId, }); return executionId; } /** * End tool execution and record metrics */ endToolExecution(executionId: string, success: boolean, error?: Error): void { const context = this.activeTools.get(executionId); if (!context) return; const responseTime = Date.now() - context.startTime; // Record in performance monitor this.monitor.recordRequest(responseTime, success, context.toolName); this.activeTools.delete(executionId); } /** * Record a raw request (bypass tool tracking) */ recordRawRequest(responseTime: number, success: boolean, endpoint: string, fromCache: boolean = false): void { this.monitor.recordRequest(responseTime, success); } /** * Collect current metrics from all sources */ collectCurrentMetrics(): PerformanceMetrics { // Update cache metrics from all registered cache managers this.updateCacheMetrics(); // Update client metrics from all registered clients this.updateClientMetrics(); // Get current metrics from monitor return this.monitor.getMetrics(); } /** * Get aggregated cache statistics */ getAggregatedCacheStats(): CacheStats { const aggregated: CacheStats = { hits: 0, misses: 0, evictions: 0, totalSize: 0, hitRate: 0, }; for (const [_siteId, cacheManager] of this.cacheManagers) { const manager = cacheManager as { getStats?: () => CacheStats }; if (manager && typeof manager.getStats === "function") { const stats = manager.getStats(); aggregated.hits += stats.hits || 0; aggregated.misses += stats.misses || 0; aggregated.evictions += stats.evictions || 0; aggregated.totalSize += stats.totalSize || 0; } } // Calculate overall hit rate const total = aggregated.hits + aggregated.misses; aggregated.hitRate = total > 0 ? aggregated.hits / total : 0; return aggregated; } /** * Get aggregated client statistics */ getAggregatedClientStats(): ClientStats { const aggregated: ClientStats = { totalRequests: 0, successfulRequests: 0, failedRequests: 0, averageResponseTime: 0, rateLimitHits: 0, authFailures: 0, errors: 0, }; const responseTimes: number[] = []; for (const [_siteId, client] of this.clientInstances) { const clientObj = client as { getStats?: () => ClientStats }; if (clientObj && typeof clientObj.getStats === "function") { const stats = clientObj.getStats(); aggregated.totalRequests += stats.totalRequests || 0; aggregated.successfulRequests += stats.successfulRequests || 0; aggregated.failedRequests += stats.failedRequests || 0; aggregated.rateLimitHits += stats.rateLimitHits || 0; aggregated.authFailures += stats.authFailures || 0; if (stats.averageResponseTime) { responseTimes.push(stats.averageResponseTime); } } } // Calculate overall average response time if (responseTimes.length > 0) { aggregated.averageResponseTime = responseTimes.reduce((sum, time) => sum + time, 0) / responseTimes.length; } return aggregated; } /** * Get performance summary for specific site */ getSiteMetrics(siteId: string): { cache?: CacheStats; client?: ClientStats; isActive: boolean; } { const result: Record<string, unknown> = { isActive: false }; const cacheManager = this.cacheManagers.get(siteId); const cacheObj = cacheManager as { getStats?: () => CacheStats } | undefined; if (cacheObj && typeof cacheObj.getStats === "function") { result.cache = cacheObj.getStats(); result.isActive = true; } const client = this.clientInstances.get(siteId); const clientObj = client as { getStats?: () => ClientStats } | undefined; if (clientObj && typeof clientObj.getStats === "function") { result.client = clientObj.getStats(); result.isActive = true; } return result as { cache?: CacheStats; client?: ClientStats; isActive: boolean }; } /** * Generate performance comparison between sites */ compareSitePerformance(): { sites: string[]; comparison: Record< string, { responseTime: number; cacheHitRate: number; errorRate: number; requestCount: number; ranking: number; } >; bestPerforming: string; worstPerforming: string; } { const sites = Array.from(this.clientInstances.keys()); const comparison: Record< string, { responseTime: number; cacheHitRate: number; errorRate: number; requestCount: number; ranking: number; } > = {}; const rankings: Array<{ site: string; score: number }> = []; for (const siteId of sites) { const metrics = this.getSiteMetrics(siteId); const responseTime = metrics.client?.averageResponseTime || 0; const cacheHitRate = metrics.cache?.hitRate || 0; const errorRate = metrics.client ? metrics.client.failedRequests / Math.max(metrics.client.totalRequests, 1) : 0; const requestCount = metrics.client?.totalRequests || 0; // Calculate performance score (lower is better for response time and error rate) const score = responseTime / 1000 + errorRate * 100 - cacheHitRate * 50 + requestCount * 0.001; comparison[siteId] = { responseTime, cacheHitRate, errorRate, requestCount, ranking: 0, // Will be set after sorting }; rankings.push({ site: siteId, score }); } // Sort by performance score rankings.sort((a, b) => a.score - b.score); // Assign rankings rankings.forEach((item, index) => { const siteData = comparison[item.site]; if (siteData) { siteData.ranking = index + 1; } }); return { sites, comparison, bestPerforming: rankings[0]?.site || "", worstPerforming: rankings[rankings.length - 1]?.site || "", }; } /** * Generate optimization suggestions */ generateOptimizationSuggestions(): { critical: string[]; recommended: string[]; optional: string[]; } { const metrics = this.collectCurrentMetrics(); const critical: string[] = []; const recommended: string[] = []; const optional: string[] = []; // Critical issues if (metrics.requests.averageResponseTime > 5000) { critical.push("Response times are critically high (>5s). Enable caching immediately."); } const errorRate = metrics.requests.failed / Math.max(metrics.requests.total, 1); if (errorRate > 0.1) { critical.push("Error rate is critically high (>10%). Check WordPress connectivity."); } // Recommended optimizations if (metrics.cache.hitRate < 0.8) { recommended.push("Cache hit rate is below 80%. Consider cache warming or TTL adjustment."); } if (metrics.requests.averageResponseTime > 2000) { recommended.push("Response times could be improved. Consider enabling more aggressive caching."); } if (metrics.system.memoryUsage > 80) { recommended.push("Memory usage is high. Consider increasing cache size limits or server resources."); } // Optional optimizations if (metrics.cache.totalSize < 100) { optional.push("Cache utilization is low. Consider pre-warming with frequently accessed data."); } if (Object.keys(metrics.tools.toolUsageCount).length > 10) { optional.push("Many tools are being used. Consider creating custom workflows for common operations."); } return { critical, recommended, optional }; } /** * Export detailed performance report */ exportDetailedReport(): { timestamp: string; overview: PerformanceMetrics; siteComparison: Record<string, unknown>; aggregatedStats: { cache: CacheStats; client: ClientStats; }; optimizations: Record<string, unknown>; alerts: unknown[]; } { return { timestamp: new Date().toISOString(), overview: this.collectCurrentMetrics(), siteComparison: this.compareSitePerformance(), aggregatedStats: { cache: this.getAggregatedCacheStats(), client: this.getAggregatedClientStats(), }, optimizations: this.generateOptimizationSuggestions(), alerts: this.monitor.getAlerts(), }; } /** * Start real-time metric collection */ private startRealTimeCollection(): void { // Skip in test/CI environments to prevent performance issues if (ConfigHelpers.isTest() || ConfigHelpers.isCI()) { return; } // Adjust collection frequency based on environment const interval = ConfigHelpers.isDev() ? Math.max(this.config.collectInterval * 2, 60000) // Longer intervals in dev : this.config.collectInterval; this.logger.info("Starting real-time metrics collection", { interval: `${interval / 1000}s`, environment: ConfigHelpers.get().get().app.nodeEnv, }); this.realTimeInterval = setInterval(() => { try { this.updateCacheMetrics(); this.updateClientMetrics(); this.logger.debug("Real-time metrics updated"); } catch (_error) { this.logger.error("Failed to update real-time metrics", { _error: _error instanceof Error ? _error.message : String(_error), }); } }, interval); } /** * Stop real-time collection and cleanup resources */ public destroy(): void { if (this.realTimeInterval) { clearInterval(this.realTimeInterval); this.realTimeInterval = undefined; this.logger.info("Real-time metrics collection stopped"); } } /** * Update cache metrics in performance monitor */ private updateCacheMetrics(): void { const aggregatedStats = this.getAggregatedCacheStats(); // Type assertion: convert CacheStats to Record<string, unknown> via unknown this.monitor.updateCacheMetrics(aggregatedStats as unknown as Record<string, unknown>); } /** * Update client metrics in performance monitor */ private updateClientMetrics(): void { // Client metrics are updated through request interception // This method can be used for additional client-specific metrics } /** * Intercept client requests for automatic tracking */ private interceptClientRequests(siteId: string, client: Record<string, unknown>): void { if (!client.request || typeof client.request !== "function") { return; } const originalRequest = client.request.bind(client); const clientObj = client as { request?: (...args: unknown[]) => Promise<unknown>; originalRequest?: (...args: unknown[]) => Promise<unknown>; }; if (!clientObj.request) return; clientObj.originalRequest = clientObj.request; clientObj.request = async (...args: unknown[]) => { const startTime = Date.now(); const requestId = `${siteId}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; // Extract metadata with proper type assertions const metadata: RequestMetadata = { siteId, endpoint: (args[0] as string) || "unknown", method: (args[1] as string) || "GET", startTime, fromCache: false, }; this.activeRequests.set(requestId, metadata); try { const result = await originalRequest(...args); const responseTime = Date.now() - startTime; // Check if response came from cache const _fromCache = result.cached || false; this.monitor.recordRequest(responseTime, true); this.activeRequests.delete(requestId); return result; } catch (_error) { const responseTime = Date.now() - startTime; this.monitor.recordRequest(responseTime, false); this.activeRequests.delete(requestId); throw _error; } }; } /** * Stop all monitoring and cleanup */ stop(): void { this.monitor.stop(); this.activeRequests.clear(); this.activeTools.clear(); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/docdyhr/mcp-wordpress'

If you have feedback or need assistance with the MCP directory API, please join our Discord server