UNPKG

polyv-live-cli

Version:

CLI tool for managing PolyV live streaming services.

313 lines 11 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AdaptivePollingManager = void 0; const events_1 = require("events"); class AdaptivePollingManager extends events_1.EventEmitter { constructor(config = {}) { super(); this.dataSourceStats = new Map(); this.intervalTimers = new Map(); this.pendingRequests = new Map(); this.batchTimers = new Map(); this.isRunning = false; this.config = { baseInterval: 5000, minInterval: 1000, maxInterval: 60000, sensitivity: 0.8, sampleSize: 10, rateLimitPerMinute: 60, ...config, }; this.batchConfig = { maxBatchSize: 10, batchTimeout: 2000, batchInterval: 500, }; this.apiStats = { totalCalls: 0, successfulCalls: 0, failedCalls: 0, averageResponseTime: 0, callsPerMinute: 0, lastCallTime: 0, }; this.setupRateLimitMonitoring(); } start() { if (this.isRunning) { return; } this.isRunning = true; this.emit('started', { timestamp: Date.now(), config: this.config, }); } stop() { if (!this.isRunning) { return; } this.isRunning = false; for (const [, timer] of this.intervalTimers) { clearInterval(timer); } this.intervalTimers.clear(); for (const [, timer] of this.batchTimers) { clearTimeout(timer); } this.batchTimers.clear(); this.emit('stopped', { timestamp: Date.now(), stats: this.getStats(), }); } registerDataSource(dataSourceId, initialInterval) { if (this.dataSourceStats.has(dataSourceId)) { return; } const stats = { id: dataSourceId, currentInterval: initialInterval || this.config.baseInterval, lastPoll: 0, changeCount: 0, totalPolls: 0, changeRate: 0, volatility: 0, changeSamples: [], }; this.dataSourceStats.set(dataSourceId, stats); this.startPollingForDataSource(dataSourceId); this.emit('dataSourceRegistered', { dataSourceId, stats, timestamp: Date.now(), }); } unregisterDataSource(dataSourceId) { const timer = this.intervalTimers.get(dataSourceId); if (timer) { clearInterval(timer); this.intervalTimers.delete(dataSourceId); } this.dataSourceStats.delete(dataSourceId); this.pendingRequests.delete(dataSourceId); this.emit('dataSourceUnregistered', { dataSourceId, timestamp: Date.now(), }); } reportDataChange(dataSourceId, changeData) { const stats = this.dataSourceStats.get(dataSourceId); if (!stats) { return; } const now = Date.now(); stats.changeCount++; stats.changeSamples.push(now); if (stats.changeSamples.length > this.config.sampleSize) { stats.changeSamples.shift(); } this.updateVolatilityAndInterval(dataSourceId); this.emit('dataChange', { dataSourceId, changeData, stats, timestamp: now, }); } getOptimalInterval(dataSourceId) { const stats = this.dataSourceStats.get(dataSourceId); if (!stats) { return this.config.baseInterval; } return stats.currentInterval; } queueBatchRequest(batchId, request, priority = 'medium') { return new Promise((resolve, reject) => { if (!this.pendingRequests.has(batchId)) { this.pendingRequests.set(batchId, []); } const requests = this.pendingRequests.get(batchId); requests.push({ request, priority, resolve, reject, timestamp: Date.now(), }); requests.sort((a, b) => { const priorityOrder = { high: 0, medium: 1, low: 2 }; return (priorityOrder[a.priority] || 2) - (priorityOrder[b.priority] || 2); }); if (requests.length >= this.batchConfig.maxBatchSize) { this.processBatch(batchId); } else if (!this.batchTimers.has(batchId)) { const timer = setTimeout(() => { this.processBatch(batchId); }, this.batchConfig.batchTimeout); this.batchTimers.set(batchId, timer); } }); } getApiStats() { return { ...this.apiStats }; } getDataSourceStats() { return new Map(this.dataSourceStats); } getStats() { const stats = Array.from(this.dataSourceStats.values()); const totalPolls = stats.reduce((sum, s) => sum + s.totalPolls, 0); const totalChanges = stats.reduce((sum, s) => sum + s.changeCount, 0); const averageInterval = stats.length > 0 ? stats.reduce((sum, s) => sum + s.currentInterval, 0) / stats.length : this.config.baseInterval; return { dataSourceCount: stats.length, totalPolls, totalChanges, apiStats: this.getApiStats(), averageInterval, activeDataSources: Array.from(this.dataSourceStats.keys()), }; } startPollingForDataSource(dataSourceId) { if (!this.isRunning) { return; } const stats = this.dataSourceStats.get(dataSourceId); if (!stats) { return; } const timer = setInterval(() => { this.pollDataSource(dataSourceId); }, stats.currentInterval); this.intervalTimers.set(dataSourceId, timer); } async pollDataSource(dataSourceId) { const stats = this.dataSourceStats.get(dataSourceId); if (!stats) { return; } const now = Date.now(); stats.lastPoll = now; stats.totalPolls++; stats.changeRate = stats.changeCount / Math.max(stats.totalPolls, 1); this.emit('poll', { dataSourceId, stats, timestamp: now, }); this.trackApiCall(now); } updateVolatilityAndInterval(dataSourceId) { const stats = this.dataSourceStats.get(dataSourceId); if (!stats) { return; } const now = Date.now(); const recentChanges = stats.changeSamples.filter(timestamp => now - timestamp < 60000); stats.volatility = Math.min(recentChanges.length / this.config.sampleSize, 1); const targetInterval = this.calculateOptimalInterval(stats.volatility); if (Math.abs(stats.currentInterval - targetInterval) > 1000) { stats.currentInterval = targetInterval; this.restartPollingForDataSource(dataSourceId); } } calculateOptimalInterval(volatility) { const { minInterval, maxInterval, sensitivity } = this.config; const adjustedVolatility = Math.pow(volatility, sensitivity); const intervalRange = maxInterval - minInterval; const interval = maxInterval - (adjustedVolatility * intervalRange); return Math.max(minInterval, Math.min(maxInterval, interval)); } restartPollingForDataSource(dataSourceId) { const existingTimer = this.intervalTimers.get(dataSourceId); if (existingTimer) { clearInterval(existingTimer); } this.startPollingForDataSource(dataSourceId); this.emit('intervalAdjusted', { dataSourceId, newInterval: this.dataSourceStats.get(dataSourceId)?.currentInterval, timestamp: Date.now(), }); } async processBatch(batchId) { const requests = this.pendingRequests.get(batchId); if (!requests || requests.length === 0) { return; } const timer = this.batchTimers.get(batchId); if (timer) { clearTimeout(timer); this.batchTimers.delete(batchId); } const batchRequests = requests.splice(0, this.batchConfig.maxBatchSize); try { this.emit('batchProcessing', { batchId, requestCount: batchRequests.length, timestamp: Date.now(), }); for (const batchRequest of batchRequests) { try { await new Promise(resolve => setTimeout(resolve, 100)); batchRequest.resolve({ success: true, processed: true }); } catch (error) { batchRequest.reject(error); } } this.emit('batchProcessed', { batchId, processedCount: batchRequests.length, timestamp: Date.now(), }); } catch (error) { for (const batchRequest of batchRequests) { batchRequest.reject(error); } this.emit('batchError', { batchId, error, timestamp: Date.now(), }); } if (requests.length > 0) { const nextTimer = setTimeout(() => { this.processBatch(batchId); }, this.batchConfig.batchInterval); this.batchTimers.set(batchId, nextTimer); } } trackApiCall(timestamp) { this.apiStats.totalCalls++; this.apiStats.lastCallTime = timestamp; this.apiStats.callsPerMinute = this.apiStats.totalCalls; } setupRateLimitMonitoring() { setInterval(() => { const callsPerMinute = this.apiStats.callsPerMinute; const rateLimitThreshold = this.config.rateLimitPerMinute * 0.8; if (callsPerMinute > rateLimitThreshold) { this.emit('rateLimitWarning', { callsPerMinute, rateLimitPerMinute: this.config.rateLimitPerMinute, timestamp: Date.now(), }); for (const [dataSourceId, stats] of this.dataSourceStats) { if (stats.currentInterval < this.config.maxInterval) { stats.currentInterval = Math.min(stats.currentInterval * 1.5, this.config.maxInterval); this.restartPollingForDataSource(dataSourceId); } } } }, 60000); } } exports.AdaptivePollingManager = AdaptivePollingManager; //# sourceMappingURL=adaptive-polling.js.map