UNPKG

polyv-live-cli

Version:

CLI tool for managing PolyV live streaming services.

263 lines 10 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TimeSeriesProcessor = void 0; class TimeSeriesProcessor { constructor(maxPointsPerSeries = 1000, retentionPeriodHours = 24) { this.series = new Map(); this.maxPointsPerSeries = maxPointsPerSeries; this.retentionPeriod = retentionPeriodHours * 60 * 60 * 1000; } addPoint(seriesKey, point, metadata) { let series = this.series.get(seriesKey); if (!series) { series = { name: metadata.name, metric: metadata.metric, channelId: metadata.channelId || '', points: [], unit: metadata.unit, aggregationType: metadata.aggregationType || 'last' }; this.series.set(seriesKey, series); } series.points.push(point); series.points.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); if (series.points.length > this.maxPointsPerSeries) { series.points = series.points.slice(-this.maxPointsPerSeries); } this.cleanupOldData(series); } addStreamMetrics(channelId, timestamp, metrics) { if (metrics.bitrate !== undefined) { this.addPoint(`${channelId}-bitrate`, { timestamp, value: metrics.bitrate }, { name: `Channel ${channelId} Bitrate`, metric: 'bitrate', channelId, unit: 'kbps', aggregationType: 'avg' }); } if (metrics.fps !== undefined) { this.addPoint(`${channelId}-fps`, { timestamp, value: metrics.fps }, { name: `Channel ${channelId} FPS`, metric: 'fps', channelId, unit: 'fps', aggregationType: 'avg' }); } if (metrics.viewerCount !== undefined) { this.addPoint(`${channelId}-viewerCount`, { timestamp, value: metrics.viewerCount }, { name: `Channel ${channelId} Viewers`, metric: 'viewerCount', channelId, unit: 'viewers', aggregationType: 'last' }); } if (metrics.bandwidth !== undefined) { this.addPoint(`${channelId}-bandwidth`, { timestamp, value: metrics.bandwidth }, { name: `Channel ${channelId} Bandwidth`, metric: 'bandwidth', channelId, unit: 'bps', aggregationType: 'avg' }); } if (metrics.uptime !== undefined) { this.addPoint(`${channelId}-uptime`, { timestamp, value: metrics.uptime }, { name: `Channel ${channelId} Uptime`, metric: 'uptime', channelId, unit: 'ms', aggregationType: 'last' }); } } query(query = {}) { const results = []; for (const series of this.series.values()) { let matches = true; if (query.channelId && series.channelId !== query.channelId) { matches = false; } if (query.metric && series.metric !== query.metric) { matches = false; } if (matches) { const filteredSeries = { ...series }; if (query.timeRange) { filteredSeries.points = series.points.filter(point => point.timestamp >= query.timeRange.start && point.timestamp <= query.timeRange.end); } else { filteredSeries.points = [...series.points]; } if (query.aggregation) { filteredSeries.points = this.aggregatePoints(filteredSeries.points, query.aggregation); } if (query.limit && filteredSeries.points.length > query.limit) { filteredSeries.points = filteredSeries.points.slice(-query.limit); } results.push(filteredSeries); } } return results; } getHistoricalData(channelId, metric, timeRange) { const seriesKey = `${channelId}-${metric}`; const series = this.series.get(seriesKey); if (!series) return null; let points = [...series.points]; if (timeRange) { points = points.filter(point => point.timestamp >= timeRange.start && point.timestamp <= timeRange.end); } return { ...series, points }; } getChannelMetrics(channelId, timeRange) { const results = new Map(); for (const [, series] of this.series.entries()) { if (series.channelId === channelId) { let points = [...series.points]; if (timeRange) { points = points.filter(point => point.timestamp >= timeRange.start && point.timestamp <= timeRange.end); } results.set(series.metric, { ...series, points }); } } return results; } aggregatePoints(points, config) { if (points.length === 0) return []; const buckets = new Map(); for (const point of points) { const bucketTime = Math.floor(point.timestamp.getTime() / config.interval) * config.interval; if (!buckets.has(bucketTime)) { buckets.set(bucketTime, []); } buckets.get(bucketTime).push(point); } const aggregatedPoints = []; for (const [bucketTime, bucketPoints] of buckets.entries()) { let aggregatedValue; switch (config.type) { case 'avg': aggregatedValue = bucketPoints.reduce((sum, p) => sum + p.value, 0) / bucketPoints.length; break; case 'sum': aggregatedValue = bucketPoints.reduce((sum, p) => sum + p.value, 0); break; case 'min': aggregatedValue = Math.min(...bucketPoints.map(p => p.value)); break; case 'max': aggregatedValue = Math.max(...bucketPoints.map(p => p.value)); break; case 'last': default: aggregatedValue = bucketPoints[bucketPoints.length - 1]?.value || 0; break; } aggregatedPoints.push({ timestamp: new Date(bucketTime), value: aggregatedValue }); } return aggregatedPoints.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); } cleanupOldData(series) { const cutoffTime = new Date(Date.now() - this.retentionPeriod); series.points = series.points.filter(point => point.timestamp >= cutoffTime); } clearAll() { this.series.clear(); } clearChannel(channelId) { const keysToDelete = []; for (const [key, series] of this.series.entries()) { if (series.channelId === channelId) { keysToDelete.push(key); } } for (const key of keysToDelete) { this.series.delete(key); } } getStats() { let totalPoints = 0; let oldestPoint; let newestPoint; const channels = new Set(); const metricTypes = new Set(); for (const series of this.series.values()) { totalPoints += series.points.length; if (series.channelId) { channels.add(series.channelId); } metricTypes.add(series.metric); for (const point of series.points) { if (!oldestPoint || point.timestamp < oldestPoint) { oldestPoint = point.timestamp; } if (!newestPoint || point.timestamp > newestPoint) { newestPoint = point.timestamp; } } } return { seriesCount: this.series.size, totalPoints, ...(oldestPoint ? { oldestPoint } : {}), ...(newestPoint ? { newestPoint } : {}), channelCount: channels.size, metricTypes: Array.from(metricTypes) }; } exportToJSON() { const data = Array.from(this.series.entries()).map(([key, series]) => ({ key, ...series })); return JSON.stringify(data, null, 2); } importFromJSON(jsonData) { try { const data = JSON.parse(jsonData); if (!Array.isArray(data)) { throw new Error('Invalid JSON format: expected array'); } this.series.clear(); for (const item of data) { if (item.key && item.name && item.metric && item.points) { const points = item.points.map((p) => ({ timestamp: new Date(p.timestamp), value: p.value })); this.series.set(item.key, { name: item.name, metric: item.metric, channelId: item.channelId, points, unit: item.unit || '', aggregationType: item.aggregationType || 'last' }); } } } catch (error) { throw new Error(`Failed to import JSON data: ${error instanceof Error ? error.message : 'Unknown error'}`); } } } exports.TimeSeriesProcessor = TimeSeriesProcessor; //# sourceMappingURL=time-series.processor.js.map