diffing.js (5786B)
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 file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 const { 8 assert, 9 reportException, 10 } = require("resource://devtools/shared/DevToolsUtils.js"); 11 const { 12 actions, 13 diffingState, 14 viewState, 15 } = require("resource://devtools/client/memory/constants.js"); 16 const { 17 getSnapshot, 18 censusIsUpToDate, 19 snapshotIsDiffable, 20 findSelectedSnapshot, 21 } = require("resource://devtools/client/memory/utils.js"); 22 23 /** 24 * Toggle diffing mode on or off. 25 */ 26 exports.toggleDiffing = function () { 27 return function ({ dispatch, getState }) { 28 dispatch({ 29 type: actions.CHANGE_VIEW, 30 newViewState: getState().diffing ? viewState.CENSUS : viewState.DIFFING, 31 oldDiffing: getState().diffing, 32 oldSelected: findSelectedSnapshot(getState()), 33 }); 34 }; 35 }; 36 37 /** 38 * Select the given snapshot for diffing. 39 * 40 * @param {snapshotModel} snapshot 41 */ 42 const selectSnapshotForDiffing = (exports.selectSnapshotForDiffing = function ( 43 snapshot 44 ) { 45 assert( 46 snapshotIsDiffable(snapshot), 47 "To select a snapshot for diffing, it must be diffable" 48 ); 49 return { type: actions.SELECT_SNAPSHOT_FOR_DIFFING, snapshot }; 50 }); 51 52 /** 53 * Compute the difference between the first and second snapshots. 54 * 55 * @param {HeapAnalysesClient} heapWorker 56 * @param {snapshotModel} first 57 * @param {snapshotModel} second 58 */ 59 const takeCensusDiff = (exports.takeCensusDiff = function ( 60 heapWorker, 61 first, 62 second 63 ) { 64 return async function ({ dispatch, getState }) { 65 assert( 66 snapshotIsDiffable(first), 67 `First snapshot must be in a diffable state, found ${first.state}` 68 ); 69 assert( 70 snapshotIsDiffable(second), 71 `Second snapshot must be in a diffable state, found ${second.state}` 72 ); 73 74 let report, parentMap; 75 let display = getState().censusDisplay; 76 let filter = getState().filter; 77 78 if (censusIsUpToDate(filter, display, getState().diffing.census)) { 79 return; 80 } 81 82 do { 83 if ( 84 !getState().diffing || 85 getState().diffing.firstSnapshotId !== first.id || 86 getState().diffing.secondSnapshotId !== second.id 87 ) { 88 // If we stopped diffing or stopped and then started diffing a different 89 // pair of snapshots, then just give up with diffing this pair. In the 90 // latter case, a newly spawned task will handle the diffing for the new 91 // pair. 92 return; 93 } 94 95 display = getState().censusDisplay; 96 filter = getState().filter; 97 98 dispatch({ 99 type: actions.TAKE_CENSUS_DIFF_START, 100 first, 101 second, 102 filter, 103 display, 104 }); 105 106 const opts = display.inverted 107 ? { asInvertedTreeNode: true } 108 : { asTreeNode: true }; 109 opts.filter = filter || null; 110 111 try { 112 ({ delta: report, parentMap } = await heapWorker.takeCensusDiff( 113 first.path, 114 second.path, 115 { breakdown: display.breakdown }, 116 opts 117 )); 118 } catch (error) { 119 reportException("actions/diffing/takeCensusDiff", error); 120 dispatch({ type: actions.DIFFING_ERROR, error }); 121 return; 122 } 123 } while ( 124 filter !== getState().filter || 125 display !== getState().censusDisplay 126 ); 127 128 dispatch({ 129 type: actions.TAKE_CENSUS_DIFF_END, 130 first, 131 second, 132 report, 133 parentMap, 134 filter, 135 display, 136 }); 137 }; 138 }); 139 140 /** 141 * Ensure that the current diffing data is up to date with the currently 142 * selected display, filter, etc. If the state is not up-to-date, then a 143 * recompute is triggered. 144 * 145 * @param {HeapAnalysesClient} heapWorker 146 */ 147 const refreshDiffing = (exports.refreshDiffing = function (heapWorker) { 148 return function ({ dispatch, getState }) { 149 if (getState().diffing.secondSnapshotId === null) { 150 return; 151 } 152 153 assert(getState().diffing.firstSnapshotId, "Should have first snapshot id"); 154 155 if (getState().diffing.state === diffingState.TAKING_DIFF) { 156 // There is an existing task that will ensure that the diffing data is 157 // up-to-date. 158 return; 159 } 160 161 const { firstSnapshotId, secondSnapshotId } = getState().diffing; 162 163 const first = getSnapshot(getState(), firstSnapshotId); 164 const second = getSnapshot(getState(), secondSnapshotId); 165 dispatch(takeCensusDiff(heapWorker, first, second)); 166 }; 167 }); 168 169 /** 170 * Select the given snapshot for diffing and refresh the diffing data if 171 * necessary (for example, if two snapshots are now selected for diffing). 172 * 173 * @param {HeapAnalysesClient} heapWorker 174 * @param {snapshotModel} snapshot 175 */ 176 exports.selectSnapshotForDiffingAndRefresh = function (heapWorker, snapshot) { 177 return async function ({ dispatch, getState }) { 178 assert( 179 getState().diffing, 180 "If we are selecting for diffing, we must be in diffing mode" 181 ); 182 dispatch(selectSnapshotForDiffing(snapshot)); 183 await dispatch(refreshDiffing(heapWorker)); 184 }; 185 }; 186 187 /** 188 * Expand the given node in the diffing's census's delta-report. 189 * 190 * @param {CensusTreeNode} node 191 */ 192 exports.expandDiffingCensusNode = function (node) { 193 return { 194 type: actions.EXPAND_DIFFING_CENSUS_NODE, 195 node, 196 }; 197 }; 198 199 /** 200 * Collapse the given node in the diffing's census's delta-report. 201 * 202 * @param {CensusTreeNode} node 203 */ 204 exports.collapseDiffingCensusNode = function (node) { 205 return { 206 type: actions.COLLAPSE_DIFFING_CENSUS_NODE, 207 node, 208 }; 209 }; 210 211 /** 212 * Focus the given node in the snapshot's census's report. 213 * 214 * @param {DominatorTreeNode} node 215 */ 216 exports.focusDiffingCensusNode = function (node) { 217 return { 218 type: actions.FOCUS_DIFFING_CENSUS_NODE, 219 node, 220 }; 221 };