1/* 2 * Copyright (C) 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 { 18 CrossPlatform, 19 ShellTransitionData, 20 Transition, 21 WmTransitionData, 22} from 'trace/flickerlib/common'; 23import {ElapsedTimestamp, RealTimestamp, Timestamp, TimestampType} from 'trace/timestamp'; 24import {TraceType} from 'trace/trace_type'; 25import {AbstractParser} from './abstract_parser'; 26import {ShellTransitionsTraceFileProto} from './proto_types'; 27 28export class ParserTransitionsShell extends AbstractParser { 29 private realToElapsedTimeOffsetNs: undefined | bigint; 30 private handlerMapping: undefined | Map<number, string>; 31 32 override getTraceType(): TraceType { 33 return TraceType.SHELL_TRANSITION; 34 } 35 36 override decodeTrace(traceBuffer: Uint8Array): Transition[] { 37 const decodedProto = ShellTransitionsTraceFileProto.decode(traceBuffer) as any; 38 39 if (Object.prototype.hasOwnProperty.call(decodedProto, 'realToElapsedTimeOffsetNanos')) { 40 this.realToElapsedTimeOffsetNs = BigInt(decodedProto.realToElapsedTimeOffsetNanos); 41 } else { 42 console.warn('Missing realToElapsedTimeOffsetNanos property on SF trace proto'); 43 this.realToElapsedTimeOffsetNs = undefined; 44 } 45 46 this.handlerMapping = new Map<number, string>(); 47 for (const mapping of decodedProto.handlerMappings) { 48 this.handlerMapping.set(mapping.id, mapping.name); 49 } 50 51 return decodedProto.transitions; 52 } 53 54 override processDecodedEntry(index: number, timestampType: TimestampType, entryProto: any): any { 55 return this.parseShellTransitionEntry(entryProto); 56 } 57 58 private parseShellTransitionEntry(entry: any): Transition { 59 this.validateShellTransitionEntry(entry); 60 61 if (this.realToElapsedTimeOffsetNs === undefined) { 62 throw new Error('missing realToElapsedTimeOffsetNs'); 63 } 64 65 let dispatchTime = null; 66 if (entry.dispatchTimeNs && BigInt(entry.dispatchTimeNs.toString()) !== 0n) { 67 const unixNs = BigInt(entry.dispatchTimeNs.toString()) + this.realToElapsedTimeOffsetNs; 68 dispatchTime = CrossPlatform.timestamp.fromString( 69 entry.dispatchTimeNs.toString(), 70 null, 71 unixNs.toString() 72 ); 73 } 74 75 let mergeRequestTime = null; 76 if (entry.mergeRequestTimeNs && BigInt(entry.mergeRequestTimeNs.toString()) !== 0n) { 77 const unixNs = BigInt(entry.mergeRequestTimeNs.toString()) + this.realToElapsedTimeOffsetNs; 78 mergeRequestTime = CrossPlatform.timestamp.fromString( 79 entry.mergeRequestTimeNs.toString(), 80 null, 81 unixNs.toString() 82 ); 83 } 84 85 let mergeTime = null; 86 if (entry.mergeTimeNs && BigInt(entry.mergeTimeNs.toString()) !== 0n) { 87 const unixNs = BigInt(entry.mergeTimeNs.toString()) + this.realToElapsedTimeOffsetNs; 88 mergeTime = CrossPlatform.timestamp.fromString( 89 entry.mergeTimeNs.toString(), 90 null, 91 unixNs.toString() 92 ); 93 } 94 95 let abortTime = null; 96 if (entry.abortTimeNs && BigInt(entry.abortTimeNs.toString()) !== 0n) { 97 const unixNs = BigInt(entry.abortTimeNs.toString()) + this.realToElapsedTimeOffsetNs; 98 abortTime = CrossPlatform.timestamp.fromString( 99 entry.abortTimeNs.toString(), 100 null, 101 unixNs.toString() 102 ); 103 } 104 105 let mergedInto = null; 106 if (entry.mergedInto !== 0) { 107 mergedInto = entry.mergedInto; 108 } 109 110 if (this.handlerMapping === undefined) { 111 throw new Error('Missing handler mapping!'); 112 } 113 114 return new Transition( 115 entry.id, 116 new WmTransitionData(), 117 new ShellTransitionData( 118 dispatchTime, 119 mergeRequestTime, 120 mergeTime, 121 abortTime, 122 this.handlerMapping.get(entry.handler), 123 mergedInto 124 ) 125 ); 126 } 127 128 private validateShellTransitionEntry(entry: any) { 129 if (entry.id === 0) { 130 throw new Error('Entry need a non null id'); 131 } 132 if ( 133 !entry.dispatchTimeNs && 134 !entry.mergeRequestTimeNs && 135 !entry.mergeTimeNs && 136 !entry.abortTimeNs 137 ) { 138 throw new Error('Requires at least one non-null timestamp'); 139 } 140 } 141 142 protected getMagicNumber(): number[] | undefined { 143 return [0x09, 0x57, 0x4d, 0x53, 0x54, 0x52, 0x41, 0x43, 0x45]; // .WMSTRACE 144 } 145 146 override getTimestamp(type: TimestampType, decodedEntry: Transition): undefined | Timestamp { 147 decodedEntry = this.parseShellTransitionEntry(decodedEntry); 148 149 if (type === TimestampType.ELAPSED) { 150 return new ElapsedTimestamp(BigInt(decodedEntry.timestamp.elapsedNanos.toString())); 151 } 152 153 if (type === TimestampType.REAL) { 154 return new RealTimestamp(BigInt(decodedEntry.timestamp.unixNanos.toString())); 155 } 156 157 throw new Error('Timestamp type unsupported'); 158 } 159} 160