/** * Rate Limit Metrics Tracking * * In-memory metrics collection for rate limiting operations. * Tracks accepted/rejected requests, patterns, and provides analytics. */ export interface RateLimitMetric { timestamp: number; functionName: string; clientIP: string; userId?: string; allowed: boolean; remaining: number; retryAfter?: number; tier: string; } export interface MetricsStats { totalRequests: number; allowedRequests: number; blockedRequests: number; blockRate: number; uniqueIPs: number; uniqueUsers: number; topBlockedIPs: Array<{ ip: string; count: number }>; topBlockedUsers: Array<{ userId: string; count: number }>; tierDistribution: Record; } // In-memory storage for metrics const metricsStore: RateLimitMetric[] = []; const MAX_METRICS = 10000; // Keep last 10k metrics /** * Record a rate limit check result */ export function recordRateLimitMetric(metric: RateLimitMetric): void { metricsStore.push(metric); // Trim oldest metrics if we exceed max if (metricsStore.length > MAX_METRICS) { metricsStore.splice(0, metricsStore.length - MAX_METRICS); } } /** * Get recent metrics */ export function getRecentMetrics(limit: number = 100): RateLimitMetric[] { return metricsStore.slice(-limit); } /** * Get aggregated statistics for a time window */ export function getMetricsStats(timeWindowMs: number = 60000): MetricsStats { const now = Date.now(); const cutoff = now - timeWindowMs; const recentMetrics = metricsStore.filter(m => m.timestamp >= cutoff); const allowedRequests = recentMetrics.filter(m => m.allowed).length; const blockedRequests = recentMetrics.filter(m => !m.allowed).length; const totalRequests = recentMetrics.length; // Track unique IPs and users const uniqueIPs = new Set(recentMetrics.map(m => m.clientIP)).size; const uniqueUsers = new Set( recentMetrics.filter(m => m.userId).map(m => m.userId) ).size; // Find top blocked IPs const ipBlockCounts = new Map(); const userBlockCounts = new Map(); const tierCounts = new Map(); recentMetrics.forEach(metric => { if (!metric.allowed) { ipBlockCounts.set(metric.clientIP, (ipBlockCounts.get(metric.clientIP) || 0) + 1); if (metric.userId) { userBlockCounts.set(metric.userId, (userBlockCounts.get(metric.userId) || 0) + 1); } } tierCounts.set(metric.tier, (tierCounts.get(metric.tier) || 0) + 1); }); const topBlockedIPs = Array.from(ipBlockCounts.entries()) .map(([ip, count]) => ({ ip, count })) .sort((a, b) => b.count - a.count) .slice(0, 10); const topBlockedUsers = Array.from(userBlockCounts.entries()) .map(([userId, count]) => ({ userId, count })) .sort((a, b) => b.count - a.count) .slice(0, 10); const tierDistribution = Object.fromEntries(tierCounts); return { totalRequests, allowedRequests, blockedRequests, blockRate: totalRequests > 0 ? blockedRequests / totalRequests : 0, uniqueIPs, uniqueUsers, topBlockedIPs, topBlockedUsers, tierDistribution, }; } /** * Clear all metrics (useful for testing) */ export function clearMetrics(): void { metricsStore.length = 0; } /** * Get metrics for a specific function */ export function getFunctionMetrics(functionName: string, limit: number = 100): RateLimitMetric[] { return metricsStore .filter(m => m.functionName === functionName) .slice(-limit); } /** * Get metrics for a specific user */ export function getUserMetrics(userId: string, limit: number = 100): RateLimitMetric[] { return metricsStore .filter(m => m.userId === userId) .slice(-limit); } /** * Get metrics for a specific IP */ export function getIPMetrics(clientIP: string, limit: number = 100): RateLimitMetric[] { return metricsStore .filter(m => m.clientIP === clientIP) .slice(-limit); }