statistics.js (5280B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 const { 8 FILTER_TAGS, 9 } = require("resource://devtools/client/netmonitor/src/constants.js"); 10 const { 11 Filters, 12 } = require("resource://devtools/client/netmonitor/src/utils/filter-predicates.js"); 13 const { 14 processNetworkUpdates, 15 responseIsFresh, 16 } = require("resource://devtools/client/netmonitor/src/utils/request-utils.js"); 17 const { 18 ADD_REQUEST, 19 UPDATE_REQUEST, 20 CLEAR_REQUESTS, 21 OPEN_STATISTICS, 22 } = require("resource://devtools/client/netmonitor/src/constants.js"); 23 24 function initStatisticsData() { 25 return FILTER_TAGS.map(type => ({ 26 cached: 0, 27 count: 0, 28 label: type, 29 size: 0, 30 transferredSize: 0, 31 time: 0, 32 nonBlockingTime: 0, 33 })); 34 } 35 36 /** 37 * This structure stores the statistics data for the empty browser cache (first visit to the site) 38 * and the primed browser cache (subsequent visits to the site) scenarios. 39 */ 40 function Statistics() { 41 return { 42 // This list of requests is used to help process the statistics data. 43 // Changes to this list should not trigger updates. 44 mutableRequests: [], 45 // List of requests whose data has already been included in the 46 // statistics data. 47 mutableUsedRequests: new Set(), 48 statisticsData: { 49 emptyCacheData: initStatisticsData(), 50 primedCacheData: initStatisticsData(), 51 }, 52 // Tracks when the statistics panel is open so we only process requests 53 // for the reducer then. 54 statisticsPanelOpen: false, 55 }; 56 } 57 58 /** 59 * This reducer is responsible for maintaining the statistics data. 60 */ 61 function statisticsReducer(state = Statistics(), action) { 62 switch (action.type) { 63 case OPEN_STATISTICS: { 64 if (state.statisticsPanelOpen !== action.open) { 65 state.statisticsPanelOpen = action.open; 66 } 67 return state; 68 } 69 case ADD_REQUEST: { 70 if (!state.statisticsPanelOpen) { 71 return state; 72 } 73 return addRequest(state, action); 74 } 75 76 case UPDATE_REQUEST: { 77 if (!state.statisticsPanelOpen) { 78 return state; 79 } 80 const index = state.mutableRequests.findIndex( 81 request => request.id === action.id 82 ); 83 if (index === -1) { 84 return state; 85 } 86 const request = state.mutableRequests[index]; 87 const updatedRequest = { 88 ...request, 89 ...processNetworkUpdates(action.data), 90 }; 91 state.mutableRequests[index] = updatedRequest; 92 93 return updateStatisticsData(state, state.mutableRequests[index]); 94 } 95 96 case CLEAR_REQUESTS: { 97 return { 98 ...Statistics(), 99 statisticsPanelOpen: state.statisticsPanelOpen, 100 }; 101 } 102 103 default: 104 return state; 105 } 106 } 107 108 function addRequest(state, action) { 109 // The target front is not used and cannot be serialized by redux 110 // eslint-disable-next-line no-unused-vars 111 const { targetFront, ...requestData } = action.data; 112 const newRequest = { 113 id: action.id, 114 ...requestData, 115 }; 116 state.mutableRequests.push(newRequest); 117 return state; 118 } 119 120 function updateStatisticsData(state, request) { 121 // Make sure all the data needed is included in the request 122 // This avoids firing unnecessary request updates. 123 if ( 124 request.contentSize == undefined || 125 !request.mimeType || 126 !request.eventTimings || 127 !request.responseHeaders || 128 request.status == undefined || 129 request.totalTime == undefined || 130 state.mutableUsedRequests.has(request.id) 131 ) { 132 return state; 133 } 134 const { 135 statisticsData: { emptyCacheData, primedCacheData }, 136 } = state; 137 // If non of the filter types are matched, defaults to "others" 138 let dataType = 8; 139 for (const [index, type] of FILTER_TAGS.entries()) { 140 if (Filters[type](request)) { 141 dataType = index; 142 } 143 if (Filters.xhr(request)) { 144 // Verify XHR last, to categorize other mime types in their own blobs. 145 // "xhr" 146 dataType = 3; 147 } 148 } 149 150 let newEmptyCacheData = [...emptyCacheData]; 151 let newPrimedCacheData = [...primedCacheData]; 152 153 newEmptyCacheData = setData(newEmptyCacheData, request, dataType, true); 154 newPrimedCacheData = setData(newPrimedCacheData, request, dataType, false); 155 state.mutableUsedRequests.add(request.id); 156 157 return { 158 mutableRequests: state.mutableRequests, 159 mutableUsedRequests: state.mutableUsedRequests, 160 statisticsData: { 161 emptyCacheData: newEmptyCacheData, 162 primedCacheData: newPrimedCacheData, 163 }, 164 statisticsPanelOpen: state.statisticsPanelOpen, 165 }; 166 } 167 168 function setData(cacheData, request, dataType, emptyCache) { 169 if (emptyCache || !responseIsFresh(request)) { 170 cacheData[dataType].time += request.totalTime || 0; 171 cacheData[dataType].size += request.contentSize || 0; 172 cacheData[dataType].transferredSize += request.transferredSize || 0; 173 174 const nonBlockingTime = 175 request.eventTimings.totalTime - 176 (request.eventTimings.timings?.blocked || 0); 177 cacheData[dataType].nonBlockingTime += nonBlockingTime || 0; 178 } else { 179 cacheData[dataType].cached++; 180 } 181 cacheData[dataType].count++; 182 return cacheData; 183 } 184 185 module.exports = { 186 statisticsReducer, 187 };