router-performance-monitor.js
1 /** 2 * Performance monitoring utilities for the video router 3 */ 4 import { createLogger } from './logger.js'; 5 import { getMultipleVideoSources } from '../app/services/video/router.js'; 6 7 const logger = createLogger('RouterPerformance'); 8 9 // Performance metrics storage 10 const metrics = { 11 totalCalls: 0, 12 totalTimeMs: 0, 13 averageTimeMs: 0, 14 minTimeMs: Infinity, 15 maxTimeMs: 0, 16 lastMeasurements: [], 17 maxMeasurements: 100, 18 byCondition: { 19 withCidData: { count: 0, totalTimeMs: 0, averageTimeMs: 0 }, 20 withoutCidData: { count: 0, totalTimeMs: 0, averageTimeMs: 0 }, 21 withPreemptiveResults: { count: 0, totalTimeMs: 0, averageTimeMs: 0 }, 22 withoutPreemptiveResults: { count: 0, totalTimeMs: 0, averageTimeMs: 0 } 23 } 24 }; 25 26 /** 27 * Wraps the getMultipleVideoSources function to monitor performance 28 * @param {string} cid - Content ID 29 * @param {number} maxSources - Maximum number of sources 30 * @param {string} altcid - Alternative content ID 31 * @param {Object} livestreamInfo - Livestream information 32 * @returns {Array} Video sources 33 */ 34 export function monitoredGetMultipleVideoSources(cid, maxSources, altcid, livestreamInfo = null) { 35 const start = performance.now(); 36 37 try { 38 // Determine if this is a livestream call 39 const isLivestream = !!livestreamInfo; 40 41 // Check if we have CID performance data 42 const hasCidData = cid && window._routerState && 43 window._routerState.cidPerformanceData && 44 window._routerState.cidPerformanceData[cid] && 45 Object.keys(window._routerState.cidPerformanceData[cid]).length > 0; 46 47 // Check if we have preemptive test results 48 const hasPreemptiveResults = window._routerState && 49 window._routerState.preemptiveTestResults && 50 window._routerState.preemptiveTestResults.length > 0; 51 52 // Call the original function 53 const result = getMultipleVideoSources(cid, maxSources, altcid, livestreamInfo); 54 55 // Measure performance 56 const end = performance.now(); 57 const executionTime = end - start; 58 59 // Update metrics with additional context 60 updateMetrics(executionTime, cid, hasCidData, hasPreemptiveResults); 61 62 return result; 63 } catch (error) { 64 logger.error(`Error in getMultipleVideoSources: ${error.message}`); 65 throw error; 66 } 67 } 68 69 /** 70 * Updates performance metrics 71 * @param {number} executionTime - Execution time in milliseconds 72 * @param {string} cid - Content ID used in the call 73 * @param {boolean} [hasCidData=false] - Whether CID performance data was available 74 * @param {boolean} [hasPreemptiveResults=false] - Whether preemptive test results were used 75 * @private 76 */ 77 function updateMetrics(executionTime, cid, hasCidData = false, hasPreemptiveResults = false) { 78 // Update overall metrics 79 metrics.totalCalls++; 80 metrics.totalTimeMs += executionTime; 81 metrics.averageTimeMs = metrics.totalTimeMs / metrics.totalCalls; 82 metrics.minTimeMs = Math.min(metrics.minTimeMs, executionTime); 83 metrics.maxTimeMs = Math.max(metrics.maxTimeMs, executionTime); 84 85 // Determine if this is a livestream call based on CID 86 const isLivestream = !cid || cid === ''; 87 88 // Add to recent measurements 89 metrics.lastMeasurements.push({ 90 timestamp: Date.now(), 91 executionTimeMs: executionTime, 92 cid: isLivestream ? 'livestream' : cid.slice(0, 8), 93 isLivestream, 94 hasCidData, 95 hasPreemptiveResults 96 }); 97 98 // Keep only the most recent measurements 99 if (metrics.lastMeasurements.length > metrics.maxMeasurements) { 100 metrics.lastMeasurements.shift(); 101 } 102 103 // Update condition-specific metrics 104 if (hasCidData) { 105 metrics.byCondition.withCidData.count++; 106 metrics.byCondition.withCidData.totalTimeMs += executionTime; 107 metrics.byCondition.withCidData.averageTimeMs = 108 metrics.byCondition.withCidData.totalTimeMs / metrics.byCondition.withCidData.count; 109 } else { 110 metrics.byCondition.withoutCidData.count++; 111 metrics.byCondition.withoutCidData.totalTimeMs += executionTime; 112 metrics.byCondition.withoutCidData.averageTimeMs = 113 metrics.byCondition.withoutCidData.totalTimeMs / metrics.byCondition.withoutCidData.count; 114 } 115 116 if (hasPreemptiveResults) { 117 metrics.byCondition.withPreemptiveResults.count++; 118 metrics.byCondition.withPreemptiveResults.totalTimeMs += executionTime; 119 metrics.byCondition.withPreemptiveResults.averageTimeMs = 120 metrics.byCondition.withPreemptiveResults.totalTimeMs / metrics.byCondition.withPreemptiveResults.count; 121 } else { 122 metrics.byCondition.withoutPreemptiveResults.count++; 123 metrics.byCondition.withoutPreemptiveResults.totalTimeMs += executionTime; 124 metrics.byCondition.withoutPreemptiveResults.averageTimeMs = 125 metrics.byCondition.withoutPreemptiveResults.totalTimeMs / metrics.byCondition.withoutPreemptiveResults.count; 126 } 127 128 // Log significant performance issues 129 if (executionTime > 100) { 130 logger.warn(`Slow getMultipleVideoSources call: ${executionTime.toFixed(2)}ms for ${isLivestream ? 'livestream' : `CID ${cid.slice(0, 8)}`}`); 131 } 132 } 133 134 /** 135 * Gets the current performance metrics 136 * @returns {Object} Performance metrics 137 */ 138 export function getRouterPerformanceMetrics() { 139 return { 140 ...metrics, 141 lastMeasurements: [...metrics.lastMeasurements] 142 }; 143 } 144 145 /** 146 * Resets the performance metrics 147 */ 148 export function resetRouterPerformanceMetrics() { 149 metrics.totalCalls = 0; 150 metrics.totalTimeMs = 0; 151 metrics.averageTimeMs = 0; 152 metrics.minTimeMs = Infinity; 153 metrics.maxTimeMs = 0; 154 metrics.lastMeasurements = []; 155 156 Object.keys(metrics.byCondition).forEach(key => { 157 metrics.byCondition[key] = { count: 0, totalTimeMs: 0, averageTimeMs: 0 }; 158 }); 159 160 logger.info('Router performance metrics reset'); 161 } 162 163 /** 164 * Logs the current performance metrics 165 */ 166 export function logRouterPerformanceMetrics() { 167 logger.info('Router Performance Metrics:'); 168 logger.info(`Total calls: ${metrics.totalCalls}`); 169 logger.info(`Average time: ${metrics.averageTimeMs.toFixed(2)}ms`); 170 logger.info(`Min time: ${metrics.minTimeMs.toFixed(2)}ms`); 171 logger.info(`Max time: ${metrics.maxTimeMs.toFixed(2)}ms`); 172 173 if (metrics.lastMeasurements.length > 0) { 174 const recentAvg = metrics.lastMeasurements.reduce((sum, m) => sum + m.executionTimeMs, 0) / 175 metrics.lastMeasurements.length; 176 logger.info(`Recent average (${metrics.lastMeasurements.length} calls): ${recentAvg.toFixed(2)}ms`); 177 } 178 179 // Log condition-specific metrics 180 logger.info('Condition-specific metrics:'); 181 logger.info(`With CID data: ${metrics.byCondition.withCidData.count} calls, ` + 182 `avg: ${metrics.byCondition.withCidData.averageTimeMs.toFixed(2)}ms`); 183 logger.info(`Without CID data: ${metrics.byCondition.withoutCidData.count} calls, ` + 184 `avg: ${metrics.byCondition.withoutCidData.averageTimeMs.toFixed(2)}ms`); 185 logger.info(`With preemptive results: ${metrics.byCondition.withPreemptiveResults.count} calls, ` + 186 `avg: ${metrics.byCondition.withPreemptiveResults.averageTimeMs.toFixed(2)}ms`); 187 logger.info(`Without preemptive results: ${metrics.byCondition.withoutPreemptiveResults.count} calls, ` + 188 `avg: ${metrics.byCondition.withoutPreemptiveResults.averageTimeMs.toFixed(2)}ms`); 189 }