The non-A/B OTA package has a very different file system compared with an OTA package. However, our OTA_analysis tool is based on the update_metadata.proto. What we do here is try to convert the non-A/B OTA package information, into a standard update_metadata.proto formated manifest. The format and how the conversion works can be found in this document: https://docs.google.com/document/d/e/2PACX-1vRwMRodq4TCvTPEmlU6KL9vPSeFmEJjVXzq4PHhrB8tGy6oHFDJGCk3bIDA5Uv-4UEP0stLarBlhl2c/pub In this CL, most of the information is successfully parsed, except installation ops like stash, free, bsdiff, imgdiff, move. (anything related to stash is not yet implemented) Test: test by selecting a non-A/B OTA package. Change-Id: I298f238395478422daece47cedbaa52a976d9f4c
247 lines
7.3 KiB
JavaScript
247 lines
7.3 KiB
JavaScript
/**
|
|
* @fileoverview Offer functions that can be used to parse the partitionUpdate
|
|
* and then do statistics over it. One can use analysePartitions to specify the
|
|
* partitions been analysed and metrics.
|
|
*/
|
|
|
|
import { OpType, MergeOpType } from '@/services/payload.js'
|
|
import { EchartsData } from '@/services/echarts_data.js'
|
|
import { MapParser } from '@/services/map_parser.js'
|
|
|
|
/**
|
|
* Add a <value> to a element associated to <key>. If the element dose not
|
|
* exists than its value will be initialized to zero.
|
|
* @param {Map} map
|
|
* @param {String} key
|
|
* @param {Number} value
|
|
*/
|
|
function addNumberToMap(map, key, value) {
|
|
if (!map.get(key)) {
|
|
map.set(key, 0)
|
|
}
|
|
map.set(key, map.get(key) + value)
|
|
}
|
|
|
|
/**
|
|
* Return a statistics over the numbers of blocks (in destination) that are
|
|
* being operated by different installation operation (e.g. REPLACE, BSDIFF).
|
|
* Only partitions that are being passed in will be included.
|
|
* @param {Array<PartitionUpdate>} partitions
|
|
* @return {Map}
|
|
*/
|
|
export function operatedBlockStatistics(partitions) {
|
|
let /** Map */ operatedBlocks = new Map()
|
|
let /** OpType */ opType = new OpType()
|
|
for (let partition of partitions) {
|
|
for (let operation of partition.operations) {
|
|
let operationType = opType.mapType.getWithDefault(operation.type)
|
|
addNumberToMap(
|
|
operatedBlocks,
|
|
operationType,
|
|
numBlocks(operation.dstExtents))
|
|
}
|
|
}
|
|
return operatedBlocks
|
|
}
|
|
|
|
export function mergeOperationStatistics(partitions, blockSize) {
|
|
let /** Map */ mergeOperations = new Map()
|
|
let /** MergeOpType */ opType = new MergeOpType()
|
|
let /** Number */ totalBlocks = 0
|
|
for (let partition of partitions) {
|
|
for (let operation of partition.mergeOperations) {
|
|
let operationType = opType.mapType.getWithDefault(operation.type)
|
|
addNumberToMap(
|
|
mergeOperations,
|
|
operationType,
|
|
operation.dstExtent.numBlocks)
|
|
}
|
|
// The total blocks number should be rounded up
|
|
totalBlocks += Math.ceil(partition.newPartitionInfo.size / blockSize)
|
|
}
|
|
// The COW merge operation is default to be COW_replace and not shown in
|
|
// the manifest info. We have to mannually add that part of operations,
|
|
// by subtracting the total blocks with other blocks.
|
|
mergeOperations.forEach((value, key) => totalBlocks -= value)
|
|
mergeOperations.set('COW_REPLACE', totalBlocks)
|
|
return mergeOperations
|
|
}
|
|
|
|
/**
|
|
* Return a statistics over the disk usage of payload.bin, based on the type of
|
|
* installation operations. Only partitions that are being passed in will be
|
|
* included.
|
|
* @param {Array<PartitionUpdate>} partitions
|
|
* @return {Map}
|
|
*/
|
|
export function operatedPayloadStatistics(partitions) {
|
|
let /** Map */ operatedBlocks = new Map()
|
|
let /** OpType */ opType = new OpType()
|
|
for (let partition of partitions) {
|
|
for (let operation of partition.operations) {
|
|
let operationType = opType.mapType.getWithDefault(operation.type)
|
|
addNumberToMap(
|
|
operatedBlocks,
|
|
operationType,
|
|
operation.dataLength)
|
|
}
|
|
}
|
|
return operatedBlocks
|
|
}
|
|
|
|
/**
|
|
* Return a statistics over the disk usage of each file types in a OTA package.
|
|
* A target file has to be provided and address-filename maps will be built.
|
|
* Only partitions that are being passed in will be included.
|
|
* @param {Array<PartitionUpdate>} partitions
|
|
* @param {Number} blockSize
|
|
* @param {File} targetFile
|
|
* @return {Map}
|
|
*/
|
|
export async function operatedExtensionStatistics(partitions, blockSize, targetFile) {
|
|
let /** Map */ operatedExtensions = new Map()
|
|
if (!targetFile) {
|
|
return operatedExtensions
|
|
}
|
|
let buildMap = new MapParser(targetFile)
|
|
await buildMap.init()
|
|
for (let partition of partitions) {
|
|
await buildMap.add(
|
|
partition.partitionName,
|
|
Math.ceil(partition.newPartitionInfo.size / blockSize))
|
|
for (let operation of partition.operations) {
|
|
if (!operation.hasOwnProperty('dataLength')) continue
|
|
let operatedFileNames = buildMap.query(
|
|
partition.partitionName,
|
|
operation.dstExtents)
|
|
let extentDataLength = distributeExtensions(
|
|
operatedFileNames,
|
|
operation.dstExtents,
|
|
operation.dataLength
|
|
)
|
|
extentDataLength.forEach((value, key) => {
|
|
addNumberToMap(
|
|
operatedExtensions,
|
|
key,
|
|
value
|
|
)
|
|
})
|
|
}
|
|
}
|
|
return operatedExtensions
|
|
}
|
|
|
|
/**
|
|
* Analyse the given partitions using the given metrics.
|
|
* @param {String} metrics
|
|
* @param {Array<PartitionUpdate>} partitions
|
|
* @return {EchartsData}
|
|
*/
|
|
export async function analysePartitions(metrics, partitions, blockSize = 4096, targetFile = null) {
|
|
let /** Map */statisticsData
|
|
let /** Echartsdata */ echartsData
|
|
switch (metrics) {
|
|
case 'blocks':
|
|
statisticsData = operatedBlockStatistics(partitions)
|
|
echartsData = new EchartsData(
|
|
statisticsData,
|
|
'Operated blocks in target build',
|
|
'blocks'
|
|
)
|
|
break
|
|
case 'payload':
|
|
statisticsData = operatedPayloadStatistics(partitions)
|
|
echartsData = new EchartsData(
|
|
statisticsData,
|
|
'Payload disk usage',
|
|
'bytes'
|
|
)
|
|
break
|
|
case 'COWmerge':
|
|
statisticsData = mergeOperationStatistics(partitions, blockSize)
|
|
echartsData = new EchartsData(
|
|
statisticsData,
|
|
'COW merge operations',
|
|
'blocks'
|
|
)
|
|
break
|
|
case 'extensions':
|
|
try {
|
|
statisticsData = await operatedExtensionStatistics(partitions, blockSize, targetFile)
|
|
}
|
|
catch (err) {
|
|
throw err
|
|
}
|
|
echartsData = new EchartsData(
|
|
statisticsData,
|
|
'Size of operated filename extensions',
|
|
'bytes'
|
|
)
|
|
}
|
|
if (echartsData) {
|
|
return echartsData
|
|
} else {
|
|
throw 'Please double check if this is a proper AB OTA package.'
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculate the number of blocks being operated
|
|
* @param {Array<InstallOperations>} exts
|
|
* @return {number}
|
|
*/
|
|
export function numBlocks(exts) {
|
|
const accumulator = (total, ext) => total + ext.numBlocks
|
|
return exts.reduce(accumulator, 0)
|
|
}
|
|
|
|
/**
|
|
* Return a string that indicates the blocks being operated
|
|
* in the manner of (start_block, block_length)
|
|
* @param {Array<InstallOperations>} exts
|
|
* @return {string}
|
|
*/
|
|
export function displayBlocks(exts) {
|
|
const accumulator = (total, ext) =>
|
|
total + '(' + ext.startBlock + ',' + ext.numBlocks + ')'
|
|
return exts.reduce(accumulator, '')
|
|
}
|
|
|
|
/**
|
|
* Return a map with pairs of (file extension, data length used by this
|
|
* extension). The total data length will be distributed by the blocks ratio
|
|
* of each extent.
|
|
* @param {Array<String>} filenames
|
|
* @param {Array<InstallOperations>} exts
|
|
* @param {Number} length
|
|
* @return {Map}
|
|
*/
|
|
export function distributeExtensions(filenames, exts, length) {
|
|
let totalBlocks = numBlocks(exts)
|
|
let distributedLengths = new Map()
|
|
for (let i = 0; i < filenames.length; i++) {
|
|
addNumberToMap(
|
|
distributedLengths,
|
|
name2Extension(filenames[i]),
|
|
Math.round(length * exts[i].numBlocks / totalBlocks)
|
|
)
|
|
}
|
|
return distributedLengths
|
|
}
|
|
|
|
/**
|
|
* convert a filename into extension, for example:
|
|
* '//system/apex/com.android.adbd.apex' => 'apex'
|
|
* @param {String} filename
|
|
* @return {String}
|
|
*/
|
|
export function name2Extension(filename) {
|
|
let elements = filename.split('.')
|
|
if (elements.length>1) {
|
|
return elements[elements.length - 1]
|
|
} else if (elements[0]==='unknown') {
|
|
return 'unknown'
|
|
} else {
|
|
return 'no-extension'
|
|
}
|
|
} |