Files
android_development/tools/ota_analysis/src/services/payload_composition.js
lishutong 5bf9ad0eed Add support for non-A/B OTA package in OTA_analysis.
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
2021-08-09 16:16:14 +00:00

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'
}
}