1/* 2 * Copyright 2023, 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 {assertDefined} from 'common/assert_utils'; 18import {Timestamp} from 'common/time'; 19import {ParserTimestampConverter} from 'common/timestamp_converter'; 20import {AbstractTracesParser} from 'parsers/traces/abstract_traces_parser'; 21import {EntryPropertiesTreeFactory} from 'parsers/transitions/entry_properties_tree_factory'; 22import {CoarseVersion} from 'trace/coarse_version'; 23import {Trace} from 'trace/trace'; 24import {Traces} from 'trace/traces'; 25import {TraceType} from 'trace/trace_type'; 26import {PropertyTreeNode} from 'trace/tree_node/property_tree_node'; 27 28export class TracesParserTransitions extends AbstractTracesParser<PropertyTreeNode> { 29 private readonly wmTransitionTrace: Trace<PropertyTreeNode> | undefined; 30 private readonly shellTransitionTrace: Trace<PropertyTreeNode> | undefined; 31 private readonly descriptors: string[]; 32 private decodedEntries: PropertyTreeNode[] | undefined; 33 34 constructor(traces: Traces, timestampConverter: ParserTimestampConverter) { 35 super(timestampConverter); 36 const wmTransitionTrace = traces.getTrace(TraceType.WM_TRANSITION); 37 const shellTransitionTrace = traces.getTrace(TraceType.SHELL_TRANSITION); 38 if (wmTransitionTrace && shellTransitionTrace) { 39 this.wmTransitionTrace = wmTransitionTrace; 40 this.shellTransitionTrace = shellTransitionTrace; 41 this.descriptors = this.wmTransitionTrace 42 .getDescriptors() 43 .concat(this.shellTransitionTrace.getDescriptors()); 44 } else { 45 this.descriptors = []; 46 } 47 } 48 49 override getCoarseVersion(): CoarseVersion { 50 return CoarseVersion.LEGACY; 51 } 52 53 override async parse() { 54 if (this.wmTransitionTrace === undefined) { 55 throw new Error('Missing WM Transition trace'); 56 } 57 58 if (this.shellTransitionTrace === undefined) { 59 throw new Error('Missing Shell Transition trace'); 60 } 61 62 const wmTransitionEntries: PropertyTreeNode[] = await Promise.all( 63 this.wmTransitionTrace.mapEntry((entry) => entry.getValue()), 64 ); 65 66 const shellTransitionEntries: PropertyTreeNode[] = await Promise.all( 67 this.shellTransitionTrace.mapEntry((entry) => entry.getValue()), 68 ); 69 70 const allEntries = wmTransitionEntries.concat(shellTransitionEntries); 71 72 this.decodedEntries = this.compressEntries(allEntries); 73 74 await this.createTimestamps(); 75 } 76 77 override async createTimestamps() { 78 this.timestamps = []; 79 for (let index = 0; index < this.getLengthEntries(); index++) { 80 const entry = await this.getEntry(index); 81 82 const shellData = entry.getChildByName('shellData'); 83 const dispatchTimestamp: Timestamp | undefined = shellData 84 ?.getChildByName('dispatchTimeNs') 85 ?.getValue(); 86 87 const realToBootTimeOffsetNs: bigint = 88 shellData 89 ?.getChildByName('realToBootTimeOffsetTimestamp') 90 ?.getValue() 91 ?.getValueNs() ?? 0n; 92 93 // for consistency with all transitions, elapsed nanos are defined as shell dispatch time else 0n 94 const timestampNs: bigint = dispatchTimestamp 95 ? dispatchTimestamp.getValueNs() 96 : realToBootTimeOffsetNs; 97 const timestamp = 98 this.timestampConverter.makeTimestampFromRealNs(timestampNs); 99 this.timestamps.push(timestamp); 100 } 101 } 102 103 override getLengthEntries(): number { 104 return assertDefined(this.decodedEntries).length; 105 } 106 107 override getEntry(index: number): Promise<PropertyTreeNode> { 108 const entry = assertDefined(this.decodedEntries)[index]; 109 return Promise.resolve(entry); 110 } 111 112 override getDescriptors(): string[] { 113 return this.descriptors; 114 } 115 116 override getTraceType(): TraceType { 117 return TraceType.TRANSITION; 118 } 119 120 override getRealToMonotonicTimeOffsetNs(): bigint | undefined { 121 return undefined; 122 } 123 124 override getRealToBootTimeOffsetNs(): bigint | undefined { 125 return undefined; 126 } 127 128 private compressEntries( 129 allTransitions: PropertyTreeNode[], 130 ): PropertyTreeNode[] { 131 const idToTransition = new Map<number, PropertyTreeNode>(); 132 133 for (const transition of allTransitions) { 134 const id = assertDefined(transition.getChildByName('id')).getValue(); 135 136 const accumulatedTransition = idToTransition.get(id); 137 if (!accumulatedTransition) { 138 idToTransition.set(id, transition); 139 } else { 140 const mergedTransition = this.mergePartialTransitions( 141 accumulatedTransition, 142 transition, 143 ); 144 idToTransition.set(id, mergedTransition); 145 } 146 } 147 148 const compressedTransitions = Array.from(idToTransition.values()); 149 compressedTransitions.forEach((transition) => { 150 EntryPropertiesTreeFactory.TRANSITION_OPERATIONS.forEach((operation) => 151 operation.apply(transition), 152 ); 153 }); 154 return compressedTransitions.sort(this.compareByTimestamp); 155 } 156 157 private compareByTimestamp(a: PropertyTreeNode, b: PropertyTreeNode): number { 158 const aNanos = 159 assertDefined(a.getChildByName('shellData')) 160 .getChildByName('dispatchTimeNs') 161 ?.getValue() 162 ?.getValueNs() ?? 0n; 163 const bNanos = 164 assertDefined(b.getChildByName('shellData')) 165 .getChildByName('dispatchTimeNs') 166 ?.getValue() 167 ?.getValueNs() ?? 0n; 168 if (aNanos !== bNanos) { 169 return aNanos < bNanos ? -1 : 1; 170 } 171 // if dispatchTimeNs not present for both, fallback to id 172 return assertDefined(a.getChildByName('id')).getValue() < 173 assertDefined(b.getChildByName('id')).getValue() 174 ? -1 175 : 1; 176 } 177 178 private mergePartialTransitions( 179 transition1: PropertyTreeNode, 180 transition2: PropertyTreeNode, 181 ): PropertyTreeNode { 182 if ( 183 assertDefined(transition1.getChildByName('id')).getValue() !== 184 assertDefined(transition2.getChildByName('id')).getValue() 185 ) { 186 throw new Error("Can't merge transitions with mismatching ids"); 187 } 188 189 const mergedTransition = this.mergeProperties( 190 transition1, 191 transition2, 192 false, 193 ); 194 195 const wmData1 = assertDefined(transition1.getChildByName('wmData')); 196 const wmData2 = assertDefined(transition2.getChildByName('wmData')); 197 const mergedWmData = this.mergeProperties(wmData1, wmData2); 198 mergedTransition.addOrReplaceChild(mergedWmData); 199 200 const shellData1 = assertDefined(transition1.getChildByName('shellData')); 201 const shellData2 = assertDefined(transition2.getChildByName('shellData')); 202 const mergedShellData = this.mergeProperties(shellData1, shellData2); 203 mergedTransition.addOrReplaceChild(mergedShellData); 204 205 return mergedTransition; 206 } 207 208 private mergeProperties( 209 node1: PropertyTreeNode, 210 node2: PropertyTreeNode, 211 visitNestedChildren = true, 212 ): PropertyTreeNode { 213 const mergedNode = new PropertyTreeNode( 214 node1.id, 215 node1.name, 216 node1.source, 217 undefined, 218 ); 219 220 node1.getAllChildren().forEach((property1) => { 221 if (!visitNestedChildren && property1.getAllChildren().length > 0) { 222 return; 223 } 224 225 const property2 = node2.getChildByName(property1.name); 226 if ( 227 !property2 || 228 property2.getValue()?.toString() < property1.getValue()?.toString() 229 ) { 230 mergedNode.addOrReplaceChild(property1); 231 return; 232 } 233 234 if (visitNestedChildren && property1.getAllChildren().length > 0) { 235 const mergedProperty = this.mergeProperties(property1, property2); 236 mergedNode.addOrReplaceChild(mergedProperty); 237 return; 238 } 239 240 mergedNode.addOrReplaceChild(property2); 241 }); 242 243 node2.getAllChildren().forEach((property2) => { 244 const existingProperty = mergedNode.getChildByName(property2.name); 245 if (!existingProperty) { 246 mergedNode.addOrReplaceChild(property2); 247 return; 248 } 249 }); 250 251 return mergedNode; 252 } 253} 254