• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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}