1/* 2 * Copyright 2020, The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17import { FILE_TYPES, TRACE_TYPES } from '@/decode.js'; 18import TraceBase from './TraceBase'; 19 20/** 21 * @deprecated This trace has been replaced by the new transactions trace 22 */ 23export default class TransactionsTraceLegacy extends TraceBase { 24 transactionsFile: Object; 25 transactionHistory: TransactionHistory; 26 27 constructor(files: any[]) { 28 const transactionsFile = files[FILE_TYPES.TRANSACTIONS_TRACE_LEGACY]; 29 30 super(transactionsFile.data, transactionsFile.timeline, files); 31 32 this.transactionsFile = transactionsFile; 33 34 // Create new transaction history 35 this.transactionHistory = new TransactionHistory(transactionsFile); 36 } 37 38 get type() { 39 return TRACE_TYPES.TRANSACTION_LEGACY; 40 } 41} 42 43class TransactionHistory { 44 history: Object; 45 applied: Object; 46 mergeTrees: any; 47 48 constructor(transactionsEventsFiles) { 49 this.history = {}; 50 this.applied = {}; 51 52 if (!transactionsEventsFiles) { 53 return; 54 } 55 56 for (const eventsFile of transactionsEventsFiles) { 57 for (const event of eventsFile.data) { 58 if (event.merge) { 59 const merge = event.merge; 60 const originalId = merge.originalTransaction.id; 61 const mergedId = merge.mergedTransaction.id; 62 63 this.addMerge(originalId, mergedId); 64 } else if (event.apply) { 65 this.addApply(event.apply.tx_id); 66 } 67 } 68 } 69 } 70 71 addMerge(originalId, mergedId) { 72 const merge = new Merge(originalId, mergedId, this.history); 73 this.addToHistoryOf(originalId, merge); 74 } 75 76 addApply(transactionId) { 77 this.applied[transactionId] = true; 78 this.addToHistoryOf(transactionId, new Apply(transactionId)); 79 } 80 81 addToHistoryOf(transactionId, event) { 82 if (!this.history[transactionId]) { 83 this.history[transactionId] = []; 84 } 85 this.history[transactionId].push(event); 86 } 87 88 generateHistoryTreesOf(transactionId) { 89 return this._generateHistoryTree(transactionId); 90 } 91 92 _generateHistoryTree(transactionId, upTo = null) { 93 if (!this.history[transactionId]) { 94 return []; 95 } 96 97 const children = []; 98 const events = this.history[transactionId]; 99 for (let i = 0; i < (upTo ?? events.length); i++) { 100 const event = events[i]; 101 102 if (event instanceof Merge) { 103 const historyTree = this. 104 _generateHistoryTree(event.mergedId, event.mergedAt); 105 const mergeTreeNode = new MergeTreeNode(event.mergedId, historyTree); 106 children.push(mergeTreeNode); 107 } else if (event instanceof Apply) { 108 children.push(new ApplyTreeNode()); 109 } else { 110 throw new Error('Unhandled event type'); 111 } 112 } 113 114 return children; 115 } 116 117 /** 118 * Generates the list of all the transactions that have ever been merged into 119 * the target transaction directly or indirectly through the merges of 120 * transactions that ended up being merged into the transaction. 121 * This includes both merges that occur before and after the transaction is 122 * applied. 123 * @param {Number} transactionId - The id of the transaction we want the list 124 * of transactions merged in for 125 * @return {Set<Number>} a set of all the transaction ids that are in the 126 * history of merges of the transaction 127 */ 128 allTransactionsMergedInto(transactionId) { 129 const allTransactionsMergedIn = new Set(); 130 131 let event; 132 const toVisit = this.generateHistoryTreesOf(transactionId); 133 while (event = toVisit.pop()) { 134 if (event instanceof MergeTreeNode) { 135 allTransactionsMergedIn.add(event.mergedId); 136 for (const child of event.children) { 137 toVisit.push(child); 138 } 139 } 140 } 141 142 return allTransactionsMergedIn; 143 } 144 145 /** 146 * Generated the list of transactions that have been directly merged into the 147 * target transaction those are transactions that have explicitly been merged 148 * in the code with a call to merge. 149 * @param {Number} transactionId - The id of the target transaction. 150 * @return {Array<Number>} an array of the transaction ids of the transactions 151 * directly merged into the target transaction 152 */ 153 allDirectMergesInto(transactionId) { 154 return (this.history[transactionId] ?? []) 155 .filter((event) => event instanceof Merge) 156 .map((merge) => merge.mergedId); 157 } 158} 159 160class MergeTreeNode { 161 mergedId: Number; 162 mergedTransactionHistory: TransactionHistory; 163 children: TransactionHistory[]; 164 165 constructor(mergedId, mergedTransactionHistory) { 166 this.mergedId = mergedId; 167 this.mergedTransactionHistory = mergedTransactionHistory; 168 this.children = mergedTransactionHistory; 169 } 170 171 get type() { 172 return 'merge'; 173 } 174} 175 176class ApplyTreeNode { 177 children: any[]; 178 179 constructor() { 180 this.children = []; 181 } 182 183 get type() { 184 return 'apply'; 185 } 186} 187 188class Merge { 189 originalId: Number; 190 mergedId: Number; 191 mergedAt: Number; 192 193 constructor(originalId, mergedId, history) { 194 this.originalId = originalId; 195 this.mergedId = mergedId; 196 // Specifies how long the merge chain of the merged transaction was at the 197 // time is was merged. 198 this.mergedAt = history[mergedId]?.length ?? 0; 199 } 200} 201 202class Apply { 203 transactionId: Number; 204 205 constructor(transactionId) { 206 this.transactionId = transactionId; 207 } 208} 209 210/** 211 * Converts the transactionId to the values that compose the identifier. 212 * The top 32 bits is the PID of the process that created the transaction 213 * and the bottom 32 bits is the ID of the transaction unique within that 214 * process. 215 * @param {Number} transactionId 216 * @return {Object} An object containing the id and pid of the transaction. 217 */ 218export function expandTransactionId(transactionId) { 219 // Can't use bit shift operation because it isn't a 32 bit integer... 220 // Because js uses floating point numbers for everything, maths isn't 100% 221 // accurate so we need to round... 222 return Object.freeze({ 223 id: Math.round(transactionId % Math.pow(2, 32)), 224 pid: Math.round(transactionId / Math.pow(2, 32)), 225 }); 226} 227