1/** 2 * @fileoverview Offer functions that can be used to parse the partitionUpdate 3 * and then do statistics over it. One can use analysePartitions to specify the 4 * partitions been analysed and metrics. 5 */ 6 7import { OpType, MergeOpType } from '@/services/payload.js' 8import { EchartsData } from '@/services/echarts_data.js' 9import { MapParser } from '@/services/map_parser.js' 10 11/** 12 * Add a <value> to a element associated to <key>. If the element dose not 13 * exists than its value will be initialized to zero. 14 * @param {Map} map 15 * @param {String} key 16 * @param {Number} value 17 */ 18function addNumberToMap(map, key, value) { 19 if (!map.get(key)) { 20 map.set(key, 0) 21 } 22 map.set(key, map.get(key) + value) 23} 24 25/** 26 * Return a statistics over the numbers of blocks (in destination) that are 27 * being operated by different installation operation (e.g. REPLACE, BSDIFF). 28 * Only partitions that are being passed in will be included. 29 * @param {Array<PartitionUpdate>} partitions 30 * @return {Map} 31 */ 32export function operatedBlockStatistics(partitions) { 33 let /** Map */ operatedBlocks = new Map() 34 let /** OpType */ opType = new OpType() 35 for (let partition of partitions) { 36 for (let operation of partition.operations) { 37 let operationType = opType.mapType.getWithDefault(operation.type) 38 addNumberToMap( 39 operatedBlocks, 40 operationType, 41 numBlocks(operation.dstExtents)) 42 } 43 } 44 return operatedBlocks 45} 46 47export function mergeOperationStatistics(partitions, blockSize) { 48 let /** Map */ mergeOperations = new Map() 49 let /** MergeOpType */ opType = new MergeOpType() 50 let /** Number */ totalBlocks = 0 51 for (let partition of partitions) { 52 for (let operation of partition.mergeOperations) { 53 let operationType = opType.mapType.getWithDefault(operation.type) 54 addNumberToMap( 55 mergeOperations, 56 operationType, 57 operation.dstExtent.numBlocks) 58 } 59 // The total blocks number should be rounded up 60 totalBlocks += Math.ceil(partition.newPartitionInfo.size / blockSize) 61 } 62 // The COW merge operation is default to be COW_replace and not shown in 63 // the manifest info. We have to mannually add that part of operations, 64 // by subtracting the total blocks with other blocks. 65 mergeOperations.forEach((value, key) => totalBlocks -= value) 66 mergeOperations.set('COW_REPLACE', totalBlocks) 67 return mergeOperations 68} 69 70/** 71 * Return a statistics over the disk usage of payload.bin, based on the type of 72 * installation operations. Only partitions that are being passed in will be 73 * included. 74 * @param {Array<PartitionUpdate>} partitions 75 * @return {Map} 76 */ 77export function operatedPayloadStatistics(partitions) { 78 let /** Map */ operatedBlocks = new Map() 79 let /** OpType */ opType = new OpType() 80 for (let partition of partitions) { 81 for (let operation of partition.operations) { 82 let operationType = opType.mapType.getWithDefault(operation.type) 83 addNumberToMap( 84 operatedBlocks, 85 operationType, 86 operation.dataLength) 87 } 88 } 89 return operatedBlocks 90} 91 92/** 93 * Return a statistics over the disk usage of each file types in a OTA package. 94 * A target file has to be provided and address-filename maps will be built. 95 * Only partitions that are being passed in will be included. 96 * @param {Array<PartitionUpdate>} partitions 97 * @param {Number} blockSize 98 * @param {File} targetFile 99 * @return {Map} 100 */ 101export async function operatedExtensionStatistics(partitions, blockSize, targetFile) { 102 let /** Map */ operatedExtensions = new Map() 103 if (!targetFile) { 104 return operatedExtensions 105 } 106 let buildMap = new MapParser(targetFile) 107 await buildMap.init() 108 for (let partition of partitions) { 109 await buildMap.add( 110 partition.partitionName, 111 Math.ceil(partition.newPartitionInfo.size / blockSize)) 112 for (let operation of partition.operations) { 113 if (!operation.hasOwnProperty('dataLength')) continue 114 let operatedFileNames = buildMap.query( 115 partition.partitionName, 116 operation.dstExtents) 117 let extentDataLength = distributeExtensions( 118 operatedFileNames, 119 operation.dstExtents, 120 operation.dataLength 121 ) 122 extentDataLength.forEach((value, key) => { 123 addNumberToMap( 124 operatedExtensions, 125 key, 126 value 127 ) 128 }) 129 } 130 } 131 return operatedExtensions 132} 133 134/** 135 * Analyse the given partitions using the given metrics. 136 * @param {String} metrics 137 * @param {Array<PartitionUpdate>} partitions 138 * @return {EchartsData} 139 */ 140export async function analysePartitions(metrics, partitions, blockSize = 4096, targetFile = null) { 141 let /** Map */statisticsData 142 let /** Echartsdata */ echartsData 143 switch (metrics) { 144 case 'blocks': 145 statisticsData = operatedBlockStatistics(partitions) 146 echartsData = new EchartsData( 147 statisticsData, 148 'Operated blocks in target build', 149 'blocks' 150 ) 151 break 152 case 'payload': 153 statisticsData = operatedPayloadStatistics(partitions) 154 echartsData = new EchartsData( 155 statisticsData, 156 'Payload disk usage', 157 'bytes' 158 ) 159 break 160 case 'COWmerge': 161 statisticsData = mergeOperationStatistics(partitions, blockSize) 162 echartsData = new EchartsData( 163 statisticsData, 164 'COW merge operations', 165 'blocks' 166 ) 167 break 168 case 'extensions': 169 try { 170 statisticsData = await operatedExtensionStatistics(partitions, blockSize, targetFile) 171 } 172 catch (err) { 173 throw err 174 } 175 echartsData = new EchartsData( 176 statisticsData, 177 'Size of operated filename extensions', 178 'bytes' 179 ) 180 } 181 if (echartsData) { 182 return echartsData 183 } else { 184 throw 'Please double check if this is a proper AB OTA package.' 185 } 186} 187 188/** 189 * Calculate the number of blocks being operated 190 * @param {Array<InstallOperations>} exts 191 * @return {number} 192 */ 193export function numBlocks(exts) { 194 const accumulator = (total, ext) => total + ext.numBlocks 195 return exts.reduce(accumulator, 0) 196} 197 198/** 199 * Return a string that indicates the blocks being operated 200 * in the manner of (start_block, block_length) 201 * @param {Array<InstallOperations>} exts 202 * @return {string} 203 */ 204export function displayBlocks(exts) { 205 const accumulator = (total, ext) => 206 total + '(' + ext.startBlock + ',' + ext.numBlocks + ')' 207 return exts.reduce(accumulator, '') 208} 209 210/** 211 * Return a map with pairs of (file extension, data length used by this 212 * extension). The total data length will be distributed by the blocks ratio 213 * of each extent. 214 * @param {Array<String>} filenames 215 * @param {Array<InstallOperations>} exts 216 * @param {Number} length 217 * @return {Map} 218 */ 219export function distributeExtensions(filenames, exts, length) { 220 let totalBlocks = numBlocks(exts) 221 let distributedLengths = new Map() 222 for (let i = 0; i < filenames.length; i++) { 223 addNumberToMap( 224 distributedLengths, 225 name2Extension(filenames[i]), 226 Math.round(length * exts[i].numBlocks / totalBlocks) 227 ) 228 } 229 return distributedLengths 230} 231 232/** 233 * convert a filename into extension, for example: 234 * '//system/apex/com.android.adbd.apex' => 'apex' 235 * @param {String} filename 236 * @return {String} 237 */ 238export function name2Extension(filename) { 239 let elements = filename.split('.') 240 if (elements.length>1) { 241 return elements[elements.length - 1] 242 } else if (elements[0]==='unknown') { 243 return 'unknown' 244 } else { 245 return 'no-extension' 246 } 247}