blob: ad25d4283cd4046b0f941cc84181a42beee861f2 [file] [log] [blame]
/*
* Copyright 2020, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { FILE_TYPES, TRACE_TYPES } from '@/decode.js';
import TraceBase from './TraceBase';
/**
* @deprecated This trace has been replaced by the new transactions trace
*/
export default class TransactionsTraceLegacy extends TraceBase {
transactionsFile: Object;
transactionHistory: TransactionHistory;
constructor(files: any[]) {
const transactionsFile = files[FILE_TYPES.TRANSACTIONS_TRACE_LEGACY];
super(transactionsFile.data, transactionsFile.timeline, files);
this.transactionsFile = transactionsFile;
// Create new transaction history
this.transactionHistory = new TransactionHistory(transactionsFile);
}
get type() {
return TRACE_TYPES.TRANSACTION_LEGACY;
}
}
class TransactionHistory {
history: Object;
applied: Object;
mergeTrees: any;
constructor(transactionsEventsFiles) {
this.history = {};
this.applied = {};
if (!transactionsEventsFiles) {
return;
}
for (const eventsFile of transactionsEventsFiles) {
for (const event of eventsFile.data) {
if (event.merge) {
const merge = event.merge;
const originalId = merge.originalTransaction.id;
const mergedId = merge.mergedTransaction.id;
this.addMerge(originalId, mergedId);
} else if (event.apply) {
this.addApply(event.apply.tx_id);
}
}
}
}
addMerge(originalId, mergedId) {
const merge = new Merge(originalId, mergedId, this.history);
this.addToHistoryOf(originalId, merge);
}
addApply(transactionId) {
this.applied[transactionId] = true;
this.addToHistoryOf(transactionId, new Apply(transactionId));
}
addToHistoryOf(transactionId, event) {
if (!this.history[transactionId]) {
this.history[transactionId] = [];
}
this.history[transactionId].push(event);
}
generateHistoryTreesOf(transactionId) {
return this._generateHistoryTree(transactionId);
}
_generateHistoryTree(transactionId, upTo = null) {
if (!this.history[transactionId]) {
return [];
}
const children = [];
const events = this.history[transactionId];
for (let i = 0; i < (upTo ?? events.length); i++) {
const event = events[i];
if (event instanceof Merge) {
const historyTree = this.
_generateHistoryTree(event.mergedId, event.mergedAt);
const mergeTreeNode = new MergeTreeNode(event.mergedId, historyTree);
children.push(mergeTreeNode);
} else if (event instanceof Apply) {
children.push(new ApplyTreeNode());
} else {
throw new Error('Unhandled event type');
}
}
return children;
}
/**
* Generates the list of all the transactions that have ever been merged into
* the target transaction directly or indirectly through the merges of
* transactions that ended up being merged into the transaction.
* This includes both merges that occur before and after the transaction is
* applied.
* @param {Number} transactionId - The id of the transaction we want the list
* of transactions merged in for
* @return {Set<Number>} a set of all the transaction ids that are in the
* history of merges of the transaction
*/
allTransactionsMergedInto(transactionId) {
const allTransactionsMergedIn = new Set();
let event;
const toVisit = this.generateHistoryTreesOf(transactionId);
while (event = toVisit.pop()) {
if (event instanceof MergeTreeNode) {
allTransactionsMergedIn.add(event.mergedId);
for (const child of event.children) {
toVisit.push(child);
}
}
}
return allTransactionsMergedIn;
}
/**
* Generated the list of transactions that have been directly merged into the
* target transaction those are transactions that have explicitly been merged
* in the code with a call to merge.
* @param {Number} transactionId - The id of the target transaction.
* @return {Array<Number>} an array of the transaction ids of the transactions
* directly merged into the target transaction
*/
allDirectMergesInto(transactionId) {
return (this.history[transactionId] ?? [])
.filter((event) => event instanceof Merge)
.map((merge) => merge.mergedId);
}
}
class MergeTreeNode {
mergedId: Number;
mergedTransactionHistory: TransactionHistory;
children: TransactionHistory[];
constructor(mergedId, mergedTransactionHistory) {
this.mergedId = mergedId;
this.mergedTransactionHistory = mergedTransactionHistory;
this.children = mergedTransactionHistory;
}
get type() {
return 'merge';
}
}
class ApplyTreeNode {
children: any[];
constructor() {
this.children = [];
}
get type() {
return 'apply';
}
}
class Merge {
originalId: Number;
mergedId: Number;
mergedAt: Number;
constructor(originalId, mergedId, history) {
this.originalId = originalId;
this.mergedId = mergedId;
// Specifies how long the merge chain of the merged transaction was at the
// time is was merged.
this.mergedAt = history[mergedId]?.length ?? 0;
}
}
class Apply {
transactionId: Number;
constructor(transactionId) {
this.transactionId = transactionId;
}
}
/**
* Converts the transactionId to the values that compose the identifier.
* The top 32 bits is the PID of the process that created the transaction
* and the bottom 32 bits is the ID of the transaction unique within that
* process.
* @param {Number} transactionId
* @return {Object} An object containing the id and pid of the transaction.
*/
export function expandTransactionId(transactionId) {
// Can't use bit shift operation because it isn't a 32 bit integer...
// Because js uses floating point numbers for everything, maths isn't 100%
// accurate so we need to round...
return Object.freeze({
id: Math.round(transactionId % Math.pow(2, 32)),
pid: Math.round(transactionId / Math.pow(2, 32)),
});
}