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