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 {SetFormatters} from 'parsers/operations/set_formatters'; 21import {AbstractTracesParser} from 'parsers/traces/abstract_traces_parser'; 22import {CoarseVersion} from 'trace/coarse_version'; 23import {Trace} from 'trace/trace'; 24import {Traces} from 'trace/traces'; 25import {TraceType} from 'trace/trace_type'; 26import {CUJ_TYPE_FORMATTER} from 'trace/tree_node/formatters'; 27import {PropertyTreeBuilderFromProto} from 'trace/tree_node/property_tree_builder_from_proto'; 28import {PropertyTreeNode} from 'trace/tree_node/property_tree_node'; 29import {EventTag} from './event_tag'; 30import {AddCujProperties} from './operations/add_cuj_properties'; 31 32export class TracesParserCujs extends AbstractTracesParser<PropertyTreeNode> { 33 private static readonly AddCujProperties = new AddCujProperties(); 34 private readonly eventLogTrace: Trace<PropertyTreeNode> | undefined; 35 private readonly descriptors: string[]; 36 private decodedEntries: PropertyTreeNode[] | undefined; 37 38 constructor(traces: Traces, timestampConverter: ParserTimestampConverter) { 39 super(timestampConverter); 40 41 const eventlogTrace = traces.getTrace(TraceType.EVENT_LOG); 42 if (eventlogTrace !== undefined) { 43 this.eventLogTrace = eventlogTrace; 44 this.descriptors = this.eventLogTrace.getDescriptors(); 45 } else { 46 this.descriptors = []; 47 } 48 } 49 50 override getCoarseVersion(): CoarseVersion { 51 return CoarseVersion.LEGACY; 52 } 53 54 override async parse() { 55 if (this.eventLogTrace === undefined) { 56 throw new Error('EventLog trace not defined'); 57 } 58 59 const eventsPromises = this.eventLogTrace.mapEntry((entry) => 60 entry.getValue(), 61 ); 62 const events = await Promise.all(eventsPromises); 63 const cujEvents = events.filter((event) => { 64 const tag = assertDefined(event.getChildByName('tag')).getValue(); 65 return ( 66 tag === EventTag.JANK_CUJ_BEGIN_TAG || 67 tag === EventTag.JANK_CUJ_END_TAG || 68 tag === EventTag.JANK_CUJ_CANCEL_TAG 69 ); 70 }); 71 this.decodedEntries = this.makeCujsFromEvents(cujEvents); 72 await this.createTimestamps(); 73 } 74 75 override async createTimestamps() { 76 this.timestamps = []; 77 for (let index = 0; index < this.getLengthEntries(); index++) { 78 const entry = await this.getEntry(index); 79 const timestamp = entry?.getChildByName('startTimestamp')?.getValue(); 80 this.timestamps.push(timestamp); 81 } 82 } 83 84 getLengthEntries(): number { 85 return assertDefined(this.decodedEntries).length; 86 } 87 88 getEntry(index: number): Promise<PropertyTreeNode> { 89 const entry = assertDefined(this.decodedEntries)[index]; 90 return Promise.resolve(entry); 91 } 92 93 override getDescriptors(): string[] { 94 return this.descriptors; 95 } 96 97 getTraceType(): TraceType { 98 return TraceType.CUJS; 99 } 100 101 override getRealToMonotonicTimeOffsetNs(): bigint | undefined { 102 return undefined; 103 } 104 105 override getRealToBootTimeOffsetNs(): bigint | undefined { 106 return undefined; 107 } 108 109 private makeCujTimestampObject(timestamp: PropertyTreeNode): Timestamp { 110 return this.timestampConverter.makeTimestampFromRealNs( 111 assertDefined(timestamp.getChildByName('unixNanos')).getValue(), 112 ); 113 } 114 115 private makeCujsFromEvents(events: PropertyTreeNode[]): PropertyTreeNode[] { 116 events.forEach((event) => TracesParserCujs.AddCujProperties.apply(event)); 117 118 const startEvents = this.filterEventsByTag( 119 events, 120 EventTag.JANK_CUJ_BEGIN_TAG, 121 ); 122 const endEvents = this.filterEventsByTag(events, EventTag.JANK_CUJ_END_TAG); 123 const canceledEvents = this.filterEventsByTag( 124 events, 125 EventTag.JANK_CUJ_CANCEL_TAG, 126 ); 127 128 const cujs: PropertyTreeNode[] = []; 129 130 for (const startEvent of startEvents) { 131 const cujType = assertDefined( 132 startEvent.getChildByName('cujType'), 133 ).getValue(); 134 const startTimestamp = assertDefined( 135 startEvent.getChildByName('cujTimestamp'), 136 ); 137 138 const matchingEndEvent = this.findMatchingEvent( 139 endEvents, 140 cujType, 141 startTimestamp, 142 ); 143 const matchingCancelEvent = this.findMatchingEvent( 144 canceledEvents, 145 cujType, 146 startTimestamp, 147 ); 148 149 if (!matchingEndEvent && !matchingCancelEvent) { 150 continue; 151 } 152 153 const closingEvent = this.getClosingEvent( 154 matchingEndEvent, 155 matchingCancelEvent, 156 ); 157 158 const closingEventTimestamp = assertDefined( 159 closingEvent.getChildByName('cujTimestamp'), 160 ); 161 const canceled = 162 assertDefined(closingEvent.getChildByName('tag')?.getValue()) === 163 EventTag.JANK_CUJ_CANCEL_TAG; 164 165 const cuj: Cuj = { 166 cujType, 167 startTimestamp: this.makeCujTimestampObject(startTimestamp), 168 endTimestamp: this.makeCujTimestampObject(closingEventTimestamp), 169 canceled, 170 }; 171 172 cujs.push(this.makeCujPropertyTree(cuj)); 173 } 174 return cujs; 175 } 176 177 private filterEventsByTag( 178 events: PropertyTreeNode[], 179 targetTag: EventTag, 180 ): PropertyTreeNode[] { 181 return events.filter((event) => { 182 const tag = assertDefined(event.getChildByName('tag')).getValue(); 183 return tag === targetTag; 184 }); 185 } 186 187 private findMatchingEvent( 188 events: PropertyTreeNode[], 189 targetCujType: number, 190 startTimestamp: PropertyTreeNode, 191 ): PropertyTreeNode | undefined { 192 return events.find((event) => { 193 const cujType = assertDefined(event.getChildByName('cujType')).getValue(); 194 const timestamp = assertDefined(event.getChildByName('cujTimestamp')); 195 return ( 196 targetCujType === cujType && 197 this.cujTimestampIsGreaterThan(timestamp, startTimestamp) 198 ); 199 }); 200 } 201 202 private cujTimestampIsGreaterThan( 203 a: PropertyTreeNode, 204 b: PropertyTreeNode, 205 ): boolean { 206 const aUnixNanos: bigint = assertDefined( 207 a.getChildByName('unixNanos'), 208 ).getValue(); 209 const bUnixNanos: bigint = assertDefined( 210 b.getChildByName('unixNanos'), 211 ).getValue(); 212 return aUnixNanos > bUnixNanos; 213 } 214 215 private getClosingEvent( 216 endEvent: PropertyTreeNode | undefined, 217 cancelEvent: PropertyTreeNode | undefined, 218 ): PropertyTreeNode { 219 const endTimestamp = endEvent?.getChildByName('cujTimestamp'); 220 const cancelTimestamp = cancelEvent?.getChildByName('cujTimestamp'); 221 222 let closingEvent: PropertyTreeNode | undefined; 223 if (!endTimestamp) { 224 closingEvent = cancelEvent; 225 } else if (!cancelTimestamp) { 226 closingEvent = endEvent; 227 } else { 228 const canceledBeforeEnd = this.cujTimestampIsGreaterThan( 229 endTimestamp, 230 cancelTimestamp, 231 ); 232 closingEvent = canceledBeforeEnd ? cancelEvent : endEvent; 233 } 234 235 if (!closingEvent) { 236 throw new Error('Should have found one matching closing event for CUJ'); 237 } 238 239 return closingEvent; 240 } 241 242 private makeCujPropertyTree(cuj: Cuj): PropertyTreeNode { 243 const tree = new PropertyTreeBuilderFromProto() 244 .setData(cuj) 245 .setRootId('CujTrace') 246 .setRootName('cuj') 247 .build(); 248 249 new SetFormatters( 250 undefined, 251 new Map([['cujType', CUJ_TYPE_FORMATTER]]), 252 ).apply(tree); 253 return tree; 254 } 255} 256 257interface Cuj { 258 cujType: number; 259 startTimestamp: Timestamp; 260 endTimestamp: Timestamp; 261 canceled: boolean; 262} 263