Add functions to analyse the composition of payload.

payload_composition.js defines functions that could parse the
manifest of payload and do statistical analysis based on different
metrics. Currently, there are two functions:
1. Number of blocks (in target build) that are being operated,
categorized by the installation operations.

2. Disk usage of the payload.bin, categorized by the installation
operations.

The output is currently a list of pairs: (Operation, Number), which
can be later turned into input of visualized element.

Test: Mannual Tested.
Change-Id: I07defc23f6f04616656d8c9d3a7ecd05026bbbff
diff --git a/tools/otagui/src/components/OperationDetail.vue b/tools/otagui/src/components/OperationDetail.vue
index e2626e5..4be93ac 100644
--- a/tools/otagui/src/components/OperationDetail.vue
+++ b/tools/otagui/src/components/OperationDetail.vue
@@ -1,18 +1,18 @@
 <template>
   {{ mapType.get(operation.type) }}
-  <p v-if="operation.dataOffset !== null">
+  <p v-if="operation.hasOwnProperty('dataOffset')">
     Data offset: {{ operation.dataOffset }}
   </p>
-  <p v-if="operation.dataLength !== null">
+  <p v-if="operation.hasOwnProperty('dataLength')">
     Data length: {{ operation.dataLength }}
   </p>
-  <p v-if="operation.srcExtents !== null">
+  <p v-if="operation.hasOwnProperty('srcExtents')">
     Source: {{ operation.srcExtents.length }} extents ({{ srcTotalBlocks }}
     blocks)
     <br>
     {{ srcBlocks }}
   </p>
-  <p v-if="operation.dstExtents !== null">
+  <p v-if="operation.hasOwnProperty('dstExtents')">
     Destination: {{ operation.dstExtents.length }} extents ({{ dstTotalBlocks }}
     blocks)
     <br>
@@ -21,6 +21,8 @@
 </template>
 
 <script>
+import { numBlocks, displayBlocks } from '../services/payload_composition.js'
+
 export default {
   props: {
     operation: {
@@ -51,16 +53,4 @@
     }
   },
 }
-
-function numBlocks(exts) {
-  const accumulator = (total, ext) => total + ext.numBlocks
-  return exts.reduce(accumulator, 0)
-}
-
-function displayBlocks(exts) {
-  const accumulator = (total, ext) =>
-    total + '(' + ext.startBlock + ',' + ext.numBlocks + ')'
-  return exts.reduce(accumulator, '')
-}
-
 </script>
\ No newline at end of file
diff --git a/tools/otagui/src/components/PartialCheckbox.vue b/tools/otagui/src/components/PartialCheckbox.vue
index 426b261..fc7694f 100644
--- a/tools/otagui/src/components/PartialCheckbox.vue
+++ b/tools/otagui/src/components/PartialCheckbox.vue
@@ -34,4 +34,15 @@
     },
   },
 }
-</script>
\ No newline at end of file
+</script>
+
+<style scoped>
+ul > li {
+  display: inline-block;
+  list-style-type: none;
+  margin-left: 5%;
+  margin-right: 5%;
+  top: 0px;
+  height: 50px;
+}
+</style>
\ No newline at end of file
diff --git a/tools/otagui/src/components/PayloadComposition.vue b/tools/otagui/src/components/PayloadComposition.vue
new file mode 100644
index 0000000..420b96d
--- /dev/null
+++ b/tools/otagui/src/components/PayloadComposition.vue
@@ -0,0 +1,71 @@
+<template>
+  <PartialCheckbox
+    v-model="partitionInclude"
+    :labels="updatePartitions"
+  />
+  <button @click="updateChart">
+    Update the chart
+  </button>
+  <div
+    v-if="listData"
+    class="list-data"
+  >
+    <pre>
+      {{ listData }}
+    </pre>
+  </div>
+</template>
+
+<script>
+import PartialCheckbox from '@/components/PartialCheckbox.vue'
+import { operatedBlockStatistics } from '../services/payload_composition.js'
+import { EchartsData } from '../services/echarts_data.js'
+import { chromeos_update_engine as update_metadata_pb } from '../services/update_metadata_pb.js'
+
+export default {
+  components: {
+    PartialCheckbox,
+  },
+  props: {
+    manifest: {
+      type: update_metadata_pb.DeltaArchiveManifest,
+      default: () => [],
+    },
+  },
+  data() {
+    return {
+      partitionInclude: new Map(),
+      echartsData: null,
+      listData: '',
+    }
+  },
+  computed: {
+    updatePartitions() {
+      return this.manifest.partitions.map((partition) => {
+        return partition.partitionName
+      })
+    },
+  },
+  mounted() {
+    this.manifest.partitions.forEach((partition) => {
+      this.partitionInclude.set(partition.partitionName, true)
+    })
+  },
+  methods: {
+    updateChart() {
+      let partitionSelected = this.manifest.partitions.filter((partition) =>
+        this.partitionInclude.get(partition.partitionName)
+      )
+      let statisticsData = operatedBlockStatistics(partitionSelected)
+      this.echartsData = new EchartsData(statisticsData)
+      this.listData = this.echartsData.listData()
+    },
+  },
+}
+</script>
+
+<style scoped>
+.list-data {
+  text-align: center;
+}
+</style>
\ No newline at end of file
diff --git a/tools/otagui/src/components/PayloadDetail.vue b/tools/otagui/src/components/PayloadDetail.vue
index 245be24..4781190 100644
--- a/tools/otagui/src/components/PayloadDetail.vue
+++ b/tools/otagui/src/components/PayloadDetail.vue
@@ -8,6 +8,10 @@
     </ul>
   </div>
   <div v-if="payload">
+    <h3>Payload Compositin</h3>
+    <div v-if="payload.manifest">
+      <PayloadComposition :manifest="payload.manifest" />
+    </div>
     <h3>Partition List</h3>
     <ul v-if="payload.manifest">
       <li
@@ -38,11 +42,13 @@
 
 <script>
 import PartitionDetail from './PartitionDetail.vue'
+import PayloadComposition from './PayloadComposition.vue'
 import { Payload } from '@/services/payload.js'
 
 export default {
   components: {
     PartitionDetail,
+    PayloadComposition,
   },
   props: {
     zipFile: {
diff --git a/tools/otagui/src/services/echarts_data.js b/tools/otagui/src/services/echarts_data.js
new file mode 100644
index 0000000..faddeec
--- /dev/null
+++ b/tools/otagui/src/services/echarts_data.js
@@ -0,0 +1,14 @@
+// This function will be used later for generating a pie chart through eCharts
+export class EchartsData {
+  constructor(statisticData) {
+    this.statisticData = statisticData
+  }
+
+  listData() {
+    let table = ''
+    for (let [key, value] of this.statisticData) {
+      table += key + ' : ' + value.toString() + ' Blocks' + '\n'
+    }
+    return table
+  }
+}
\ No newline at end of file
diff --git a/tools/otagui/src/services/payload_composition.js b/tools/otagui/src/services/payload_composition.js
new file mode 100644
index 0000000..b5ad85d
--- /dev/null
+++ b/tools/otagui/src/services/payload_composition.js
@@ -0,0 +1,73 @@
+import { OpType } from '@/services/payload.js'
+
+/**
+ * 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 operatedBlocks = new Map()
+  let opType = new OpType()
+  for (let partition of partitions) {
+    for (let operation of partition.operations) {
+      let operationType = opType.mapType.get(operation.type)
+      if (!operatedBlocks.get(operationType)) {
+        operatedBlocks.set(operationType, 0)
+      }
+      operatedBlocks.set(
+        operationType,
+        operatedBlocks.get(operationType) + numBlocks(operation.dstExtents)
+      )
+    }
+  }
+  return operatedBlocks
+}
+
+/**
+ * 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 operatedBlocks = new Map()
+  let opType = new OpType()
+  for (let partition of partitions) {
+    for (let operation of partition.operations) {
+      let operationType = opType.mapType.get(operation.type)
+      if (!operatedBlocks.get(operationType)) {
+        operatedBlocks.set(operationType, 0)
+      }
+      operatedBlocks.set(
+        operationType,
+        operatedBlocks.get(operationType) + operation.dataLength
+      )
+    }
+  }
+  return operatedBlocks
+}
+
+/**
+ * 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, '')
+}
\ No newline at end of file