UNPKG

polyv-live-cli

Version:

CLI tool for managing PolyV live streaming services.

351 lines 14.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ApiAnalytics = void 0; const events_1 = require("events"); class ApiAnalytics extends events_1.EventEmitter { constructor(config = {}) { super(); this.requestRecords = []; this.endpointStats = new Map(); this.errorCounts = new Map(); this.isRunning = false; this.config = { enableDetailedTracking: true, maxRequestRecords: 10000, analyticsWindow: 3600000, enablePerformanceMonitoring: true, enableErrorTracking: true, sampleRate: 1.0, ...config, }; this.setupPeriodicAnalytics(); } start() { if (this.isRunning) { return; } this.isRunning = true; this.emit('analyticsStarted', { timestamp: Date.now(), config: this.config, }); } stop() { if (!this.isRunning) { return; } this.isRunning = false; if (this.analyticsTimer) { clearInterval(this.analyticsTimer); delete this.analyticsTimer; } this.emit('analyticsStopped', { timestamp: Date.now(), finalStats: this.getOverallStats(), }); } recordRequest(id, method, url, responseTime, options = {}) { if (!this.isRunning) { return; } if (this.config.sampleRate < 1.0 && Math.random() > this.config.sampleRate) { return; } const record = { id, method: method.toUpperCase(), url, timestamp: Date.now(), responseTime, statusCode: options.statusCode || 0, requestSize: options.requestSize || 0, responseSize: options.responseSize || 0, success: options.success !== false, error: options.error || '', priority: options.priority || 'medium', cached: options.cached || false, batched: options.batched || false, userAgent: options.userAgent || '', }; if (this.config.enableDetailedTracking) { this.requestRecords.push(record); if (this.requestRecords.length > this.config.maxRequestRecords) { this.requestRecords.shift(); } } this.updateEndpointStats(record); if (this.config.enableErrorTracking && !record.success && record.error) { this.trackError(record.error); } this.emit('requestRecorded', { record, timestamp: Date.now(), }); } getOverallStats() { const now = Date.now(); const windowStart = now - this.config.analyticsWindow; const recentRecords = this.requestRecords.filter(r => r.timestamp >= windowStart); const totalRequests = recentRecords.length; const successfulRequests = recentRecords.filter(r => r.success).length; const failedRequests = totalRequests - successfulRequests; const averageResponseTime = totalRequests > 0 ? recentRecords.reduce((sum, r) => sum + r.responseTime, 0) / totalRequests : 0; const successRate = totalRequests > 0 ? successfulRequests / totalRequests : 0; const totalDataTransferred = recentRecords.reduce((sum, r) => sum + r.requestSize + r.responseSize, 0); return { totalRequests, successfulRequests, failedRequests, averageResponseTime, successRate, totalDataTransferred, timeWindow: this.config.analyticsWindow, }; } getEndpointStats() { return Array.from(this.endpointStats.values()).sort((a, b) => b.totalRequests - a.totalRequests); } getPerformanceMetrics() { const now = Date.now(); const windowStart = now - this.config.analyticsWindow; const recentRecords = this.requestRecords.filter(r => r.timestamp >= windowStart); if (recentRecords.length === 0) { return { averageResponseTime: 0, p95ResponseTime: 0, p99ResponseTime: 0, overallSuccessRate: 0, requestsPerSecond: 0, cacheHitRate: 0, batchEfficiency: 0, errorRate: 0, averageRequestSize: 0, averageResponseSize: 0, }; } const responseTimes = recentRecords.map(r => r.responseTime).sort((a, b) => a - b); const p95Index = Math.floor(responseTimes.length * 0.95); const p99Index = Math.floor(responseTimes.length * 0.99); const averageResponseTime = responseTimes.reduce((sum, rt) => sum + rt, 0) / responseTimes.length; const p95ResponseTime = responseTimes[p95Index] || 0; const p99ResponseTime = responseTimes[p99Index] || 0; const successfulRequests = recentRecords.filter(r => r.success).length; const overallSuccessRate = successfulRequests / recentRecords.length; const timeSpan = (now - windowStart) / 1000; const requestsPerSecond = recentRecords.length / timeSpan; const cachedRequests = recentRecords.filter(r => r.cached).length; const cacheHitRate = cachedRequests / recentRecords.length; const batchedRequests = recentRecords.filter(r => r.batched).length; const batchEfficiency = batchedRequests / recentRecords.length; const errorRate = 1 - overallSuccessRate; const averageRequestSize = recentRecords.reduce((sum, r) => sum + r.requestSize, 0) / recentRecords.length; const averageResponseSize = recentRecords.reduce((sum, r) => sum + r.responseSize, 0) / recentRecords.length; return { averageResponseTime, p95ResponseTime, p99ResponseTime, overallSuccessRate, requestsPerSecond, cacheHitRate, batchEfficiency, errorRate, averageRequestSize, averageResponseSize, }; } getErrorAnalysis() { const now = Date.now(); const windowStart = now - this.config.analyticsWindow; const recentRecords = this.requestRecords.filter(r => r.timestamp >= windowStart); const errorRecords = recentRecords.filter(r => !r.success && r.error); const errorCounts = new Map(); for (const record of errorRecords) { const error = record.error; const existing = errorCounts.get(error) || { count: 0, endpoints: new Set() }; existing.count++; existing.endpoints.add(record.url); errorCounts.set(error, existing); } const commonErrors = Array.from(errorCounts.entries()) .map(([error, data]) => ({ error, count: data.count, percentage: (data.count / errorRecords.length) * 100, endpoints: Array.from(data.endpoints), })) .sort((a, b) => b.count - a.count); const endpointErrorCounts = new Map(); const endpointTotalCounts = new Map(); for (const record of recentRecords) { const current = endpointTotalCounts.get(record.url) || 0; endpointTotalCounts.set(record.url, current + 1); if (!record.success) { const errorCurrent = endpointErrorCounts.get(record.url) || 0; endpointErrorCounts.set(record.url, errorCurrent + 1); } } const errorsByEndpoint = Array.from(endpointErrorCounts.entries()) .map(([endpoint, errorCount]) => ({ endpoint, errorCount, errorRate: errorCount / (endpointTotalCounts.get(endpoint) || 1), })) .sort((a, b) => b.errorRate - a.errorRate); const hourlyBuckets = new Map(); const hourMs = 3600000; for (const record of recentRecords) { const hourBucket = Math.floor(record.timestamp / hourMs) * hourMs; const existing = hourlyBuckets.get(hourBucket) || { errors: 0, total: 0 }; existing.total++; if (!record.success) { existing.errors++; } hourlyBuckets.set(hourBucket, existing); } const errorTrends = Array.from(hourlyBuckets.entries()) .map(([timestamp, data]) => ({ timestamp, errorCount: data.errors, totalRequests: data.total, })) .sort((a, b) => a.timestamp - b.timestamp); return { commonErrors, errorsByEndpoint, errorTrends, }; } getUsagePatterns() { const now = Date.now(); const windowStart = now - this.config.analyticsWindow; const recentRecords = this.requestRecords.filter(r => r.timestamp >= windowStart); const hourlyRequests = new Map(); for (const record of recentRecords) { const hour = new Date(record.timestamp).getHours(); hourlyRequests.set(hour, (hourlyRequests.get(hour) || 0) + 1); } const peakHours = Array.from(hourlyRequests.entries()) .map(([hour, requestCount]) => ({ hour, requestCount })) .sort((a, b) => b.requestCount - a.requestCount); const endpointRequests = new Map(); for (const record of recentRecords) { endpointRequests.set(record.url, (endpointRequests.get(record.url) || 0) + 1); } const totalRequests = recentRecords.length; const activeEndpoints = Array.from(endpointRequests.entries()) .map(([endpoint, requestCount]) => ({ endpoint, requestCount, percentage: (requestCount / totalRequests) * 100, })) .sort((a, b) => b.requestCount - a.requestCount); const methodDistribution = {}; for (const record of recentRecords) { methodDistribution[record.method] = (methodDistribution[record.method] || 0) + 1; } const priorityDistribution = {}; for (const record of recentRecords) { priorityDistribution[record.priority] = (priorityDistribution[record.priority] || 0) + 1; } const cachedRequests = recentRecords.filter(r => r.cached).length; const cacheableRequests = recentRecords.filter(r => r.method === 'GET').length; const cachingStats = { cacheHitRate: cacheableRequests > 0 ? cachedRequests / cacheableRequests : 0, cacheMissRate: cacheableRequests > 0 ? (cacheableRequests - cachedRequests) / cacheableRequests : 0, cacheableRequests, }; return { peakHours, activeEndpoints, methodDistribution, priorityDistribution, cachingStats, }; } generateReport() { return { summary: this.getOverallStats(), performance: this.getPerformanceMetrics(), errors: this.getErrorAnalysis(), usage: this.getUsagePatterns(), endpoints: this.getEndpointStats(), generatedAt: Date.now(), }; } clearData() { this.requestRecords = []; this.endpointStats.clear(); this.errorCounts.clear(); this.emit('dataCleared', { timestamp: Date.now(), }); } updateConfig(newConfig) { this.config = { ...this.config, ...newConfig }; this.emit('configUpdated', { config: this.config, timestamp: Date.now(), }); } updateEndpointStats(record) { const endpoint = this.normalizeEndpoint(record.url); const existing = this.endpointStats.get(endpoint) || { endpoint, totalRequests: 0, successfulRequests: 0, failedRequests: 0, averageResponseTime: 0, minResponseTime: Infinity, maxResponseTime: 0, successRate: 0, totalDataTransferred: 0, lastRequestTime: 0, requestsPerHour: 0, }; existing.totalRequests++; existing.lastRequestTime = record.timestamp; existing.totalDataTransferred += record.requestSize + record.responseSize; if (record.success) { existing.successfulRequests++; } else { existing.failedRequests++; } existing.averageResponseTime = ((existing.averageResponseTime * (existing.totalRequests - 1)) + record.responseTime) / existing.totalRequests; existing.minResponseTime = Math.min(existing.minResponseTime, record.responseTime); existing.maxResponseTime = Math.max(existing.maxResponseTime, record.responseTime); existing.successRate = existing.successfulRequests / existing.totalRequests; const now = Date.now(); const hourAgo = now - 3600000; const recentRequests = this.requestRecords.filter(r => r.url === record.url && r.timestamp >= hourAgo); existing.requestsPerHour = recentRequests.length; this.endpointStats.set(endpoint, existing); } normalizeEndpoint(url) { const baseUrl = url.split('?')[0]; return (baseUrl || '').replace(/\/\d+/g, '/{id}'); } trackError(error) { this.errorCounts.set(error, (this.errorCounts.get(error) || 0) + 1); } setupPeriodicAnalytics() { this.analyticsTimer = setInterval(() => { if (this.isRunning) { this.performPeriodicAnalytics(); } }, 60000); } performPeriodicAnalytics() { const report = this.generateReport(); this.emit('periodicAnalytics', { report, timestamp: Date.now(), }); const cutoffTime = Date.now() - this.config.analyticsWindow; this.requestRecords = this.requestRecords.filter(r => r.timestamp >= cutoffTime); } } exports.ApiAnalytics = ApiAnalytics; //# sourceMappingURL=api-analytics.js.map