polyv-live-cli
Version:
CLI tool for managing PolyV live streaming services.
313 lines • 11 kB
JavaScript
"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