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