blob: 7d9085b206e85e39a7535ac3d618175a48a47830 [file] [log] [blame]
/*
* Copyright 2023, 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 {assertDefined} from 'common/assert_utils';
import {Timestamp, TimestampType} from 'common/time';
import {AbstractTracesParser} from 'parsers/abstract_traces_parser';
import {Trace} from 'trace/trace';
import {Traces} from 'trace/traces';
import {TraceType} from 'trace/trace_type';
import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
import {ParserTransitionsUtils} from './parser_transitions_utils';
export class TracesParserTransitions extends AbstractTracesParser<PropertyTreeNode> {
private readonly wmTransitionTrace: Trace<PropertyTreeNode> | undefined;
private readonly shellTransitionTrace: Trace<PropertyTreeNode> | undefined;
private readonly descriptors: string[];
private decodedEntries: PropertyTreeNode[] | undefined;
constructor(traces: Traces) {
super();
const wmTransitionTrace = traces.getTrace(TraceType.WM_TRANSITION);
const shellTransitionTrace = traces.getTrace(TraceType.SHELL_TRANSITION);
if (wmTransitionTrace && shellTransitionTrace) {
this.wmTransitionTrace = wmTransitionTrace;
this.shellTransitionTrace = shellTransitionTrace;
this.descriptors = this.wmTransitionTrace
.getDescriptors()
.concat(this.shellTransitionTrace.getDescriptors());
} else {
this.descriptors = [];
}
}
override async parse() {
if (this.wmTransitionTrace === undefined) {
throw new Error('Missing WM Transition trace');
}
if (this.shellTransitionTrace === undefined) {
throw new Error('Missing Shell Transition trace');
}
const wmTransitionEntries: PropertyTreeNode[] = await Promise.all(
this.wmTransitionTrace.mapEntry((entry) => entry.getValue())
);
const shellTransitionEntries: PropertyTreeNode[] = await Promise.all(
this.shellTransitionTrace.mapEntry((entry) => entry.getValue())
);
const allEntries = wmTransitionEntries.concat(shellTransitionEntries);
this.decodedEntries = this.compressEntries(allEntries);
await this.parseTimestamps();
}
override getLengthEntries(): number {
return assertDefined(this.decodedEntries).length;
}
override getEntry(index: number, timestampType: TimestampType): Promise<PropertyTreeNode> {
const entry = assertDefined(this.decodedEntries)[index];
return Promise.resolve(entry);
}
override getDescriptors(): string[] {
return this.descriptors;
}
override getTraceType(): TraceType {
return TraceType.TRANSITION;
}
override getTimestamp(
type: TimestampType,
decodedEntry: PropertyTreeNode
): undefined | Timestamp {
// for consistency with all transitions, elapsed nanos are defined as shell dispatch time else 0n
const realToElapsedTimeOffsetNs = decodedEntry
.getChildByName('realToElapsedTimeOffsetNs')
?.getValue();
const dispatchTimeLong = decodedEntry
.getChildByName('shellData')
?.getChildByName('dispatchTimeNs')
?.getValue();
const timestampNs = dispatchTimeLong ? BigInt(dispatchTimeLong.toString()) : 0n;
return Timestamp.from(type, timestampNs, realToElapsedTimeOffsetNs);
}
private compressEntries(allTransitions: PropertyTreeNode[]): PropertyTreeNode[] {
const idToTransition = new Map<number, PropertyTreeNode>();
for (const transition of allTransitions) {
const id = assertDefined(transition.getChildByName('id')).getValue();
const accumulatedTransition = idToTransition.get(id);
if (!accumulatedTransition) {
idToTransition.set(id, transition);
} else {
const mergedTransition = this.mergePartialTransitions(accumulatedTransition, transition);
idToTransition.set(id, mergedTransition);
}
}
const compressedTransitions = Array.from(idToTransition.values());
compressedTransitions.forEach((transition) => {
ParserTransitionsUtils.TRANSITION_OPERATIONS.forEach((operation) =>
operation.apply(transition)
);
});
return compressedTransitions.sort(this.compareByTimestamp);
}
private compareByTimestamp(a: PropertyTreeNode, b: PropertyTreeNode): number {
const aTimestamp = BigInt(
assertDefined(a.getChildByName('shellData'))
.getChildByName('dispatchTimeNs')
?.getValue()
.toString() ?? 0n
);
const bTimestamp = BigInt(
assertDefined(b.getChildByName('shellData'))
.getChildByName('dispatchTimeNs')
?.getValue()
.toString() ?? 0n
);
if (aTimestamp !== bTimestamp) {
return aTimestamp < bTimestamp ? -1 : 1;
}
// if dispatchTimeNs not present for both, fallback to id
return assertDefined(a.getChildByName('id')).getValue() <
assertDefined(b.getChildByName('id')).getValue()
? -1
: 1;
}
private mergePartialTransitions(
transition1: PropertyTreeNode,
transition2: PropertyTreeNode
): PropertyTreeNode {
if (
assertDefined(transition1.getChildByName('id')).getValue() !==
assertDefined(transition2.getChildByName('id')).getValue()
) {
throw Error("Can't merge transitions with mismatching ids");
}
const mergedTransition = this.mergeProperties(transition1, transition2, false);
const wmData1 = assertDefined(transition1.getChildByName('wmData'));
const wmData2 = assertDefined(transition2.getChildByName('wmData'));
const mergedWmData = this.mergeProperties(wmData1, wmData2);
mergedTransition.addOrReplaceChild(mergedWmData);
const shellData1 = assertDefined(transition1.getChildByName('shellData'));
const shellData2 = assertDefined(transition2.getChildByName('shellData'));
const mergedShellData = this.mergeProperties(shellData1, shellData2);
mergedTransition.addOrReplaceChild(mergedShellData);
return mergedTransition;
}
private mergeProperties(
node1: PropertyTreeNode,
node2: PropertyTreeNode,
visitNestedChildren = true
): PropertyTreeNode {
const mergedNode = new PropertyTreeNode(node1.id, node1.name, node1.source, undefined);
node1.getAllChildren().forEach((property1) => {
if (!visitNestedChildren && property1.getAllChildren().length > 0) {
return;
}
const property2 = node2.getChildByName(property1.name);
if (!property2 || property2.getValue()?.toString() < property1.getValue()?.toString()) {
mergedNode.addOrReplaceChild(property1);
return;
}
if (visitNestedChildren && property1.getAllChildren().length > 0) {
const mergedProperty = this.mergeProperties(property1, property2);
mergedNode.addOrReplaceChild(mergedProperty);
return;
}
mergedNode.addOrReplaceChild(property2);
});
node2.getAllChildren().forEach((property2) => {
const existingProperty = mergedNode.getChildByName(property2.name);
if (!existingProperty) {
mergedNode.addOrReplaceChild(property2);
return;
}
});
return mergedNode;
}
}