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 {ArrayUtils} from 'common/array_utils'; 18import {assertDefined} from 'common/assert_utils'; 19import {INVALID_TIME_NS, Timestamp} from 'common/time'; 20import {TimestampUtils} from 'common/timestamp_utils'; 21import {TracesParserInput} from 'parsers/input/perfetto/traces_parser_input'; 22import {AbstractParser as AbstractPerfettoParser} from 'parsers/perfetto/abstract_parser'; 23import { 24 CustomQueryParamTypeMap, 25 CustomQueryParserResultTypeMap, 26 CustomQueryResultTypeMap, 27 CustomQueryType, 28 ProcessParserResult, 29} from './custom_query'; 30import {FrameMap} from './frame_map'; 31import { 32 AbsoluteEntryIndex, 33 AbsoluteFrameIndex, 34 EntriesRange, 35 FramesRange, 36 RelativeEntryIndex, 37} from './index_types'; 38import {Parser} from './parser'; 39import {TRACE_INFO} from './trace_info'; 40import {TraceType} from './trace_type'; 41 42export { 43 AbsoluteEntryIndex, 44 AbsoluteFrameIndex, 45 EntriesRange, 46 FramesRange, 47 RelativeEntryIndex, 48} from './index_types'; 49 50export abstract class TraceEntry<T> { 51 constructor( 52 protected readonly fullTrace: Trace<T>, 53 protected readonly parser: Parser<T>, 54 protected readonly index: AbsoluteEntryIndex, 55 protected readonly timestamp: Timestamp, 56 protected readonly framesRange: FramesRange | undefined, 57 ) {} 58 59 getFullTrace(): Trace<T> { 60 return this.fullTrace; 61 } 62 63 getIndex(): AbsoluteEntryIndex { 64 return this.index; 65 } 66 67 getTimestamp(): Timestamp { 68 return this.timestamp; 69 } 70 71 hasValidTimestamp() { 72 return this.timestamp.getValueNs() !== INVALID_TIME_NS; 73 } 74 75 getFramesRange(): FramesRange | undefined { 76 if (!this.fullTrace.hasFrameInfo()) { 77 throw new Error( 78 `Trace ${ 79 TRACE_INFO[this.fullTrace.type].name 80 } can't be accessed in frame domain (no frame info available)`, 81 ); 82 } 83 return this.framesRange; 84 } 85 86 abstract getValue(): any; 87} 88 89export class TraceEntryLazy<T> extends TraceEntry<T> { 90 constructor( 91 fullTrace: Trace<T>, 92 parser: Parser<T>, 93 index: AbsoluteEntryIndex, 94 timestamp: Timestamp, 95 framesRange: FramesRange | undefined, 96 ) { 97 super(fullTrace, parser, index, timestamp, framesRange); 98 } 99 100 override async getValue(): Promise<T> { 101 try { 102 return await this.parser.getEntry(this.index); 103 } catch (e) { 104 this.fullTrace.setCorruptedState( 105 true, 106 `Cannot parse entry at index ${this.index}`, 107 ); 108 throw e; 109 } 110 } 111} 112 113export class TraceEntryEager<T, U> extends TraceEntry<T> { 114 private readonly value: U; 115 116 constructor( 117 fullTrace: Trace<T>, 118 parser: Parser<T>, 119 index: AbsoluteEntryIndex, 120 timestamp: Timestamp, 121 framesRange: FramesRange | undefined, 122 value: U, 123 ) { 124 super(fullTrace, parser, index, timestamp, framesRange); 125 this.value = value; 126 } 127 128 override getValue(): U { 129 return this.value; 130 } 131} 132 133export class Trace<T> { 134 readonly type: TraceType; 135 readonly lengthEntries: number; 136 137 private readonly parser: Parser<T>; 138 private readonly descriptors: string[]; 139 private readonly fullTrace: Trace<T>; 140 private readonly entriesRange: EntriesRange; 141 private frameMap?: FrameMap; 142 private framesRange?: FramesRange; 143 private corruptedState = false; 144 private corruptedReason: string | undefined; 145 146 static fromParser<T>(parser: Parser<T>): Trace<T> { 147 return new Trace( 148 parser.getTraceType(), 149 parser, 150 parser.getDescriptors(), 151 undefined, 152 undefined, 153 ); 154 } 155 156 constructor( 157 type: TraceType, 158 parser: Parser<T>, 159 descriptors: string[], 160 fullTrace: Trace<T> | undefined, 161 entriesRange: EntriesRange | undefined, 162 ) { 163 this.type = type; 164 this.parser = parser; 165 this.descriptors = descriptors; 166 this.fullTrace = fullTrace ?? this; 167 this.entriesRange = entriesRange ?? { 168 start: 0, 169 end: parser.getLengthEntries(), 170 }; 171 this.lengthEntries = this.entriesRange.end - this.entriesRange.start; 172 } 173 174 getDescriptors(): string[] { 175 return this.parser.getDescriptors(); 176 } 177 178 getParser(): Parser<T> { 179 return this.parser; 180 } 181 182 canSearch(): boolean { 183 return [AbstractPerfettoParser, TracesParserInput].some( 184 (ParserType) => this.parser instanceof ParserType, 185 ); 186 } 187 188 setFrameInfo(frameMap: FrameMap, framesRange: FramesRange | undefined) { 189 if (frameMap.lengthEntries !== this.fullTrace.lengthEntries) { 190 throw new Error( 191 `Attempted to set a frame map for ${ 192 TRACE_INFO[this.type].name 193 } trace with incompatible number of entries`, 194 ); 195 } 196 this.frameMap = frameMap; 197 this.framesRange = framesRange; 198 } 199 200 hasFrameInfo(): boolean { 201 return this.frameMap !== undefined; 202 } 203 204 getEntry(index: RelativeEntryIndex): TraceEntryLazy<T> { 205 return this.getEntryInternal(index, (index, timestamp, frames) => { 206 return new TraceEntryLazy<T>( 207 this.fullTrace, 208 this.parser, 209 index, 210 timestamp, 211 frames, 212 ); 213 }); 214 } 215 216 async customQuery<Q extends CustomQueryType>( 217 type: Q, 218 param?: CustomQueryParamTypeMap[Q], 219 ): Promise<CustomQueryResultTypeMap<T>[Q]> { 220 const makeTraceEntry = <U>( 221 index: RelativeEntryIndex, 222 value: U, 223 ): TraceEntryEager<T, U> => { 224 return this.getEntryInternal(index, (index, timestamp, frames) => { 225 return new TraceEntryEager<T, U>( 226 this.fullTrace, 227 this.parser, 228 index, 229 timestamp, 230 frames, 231 value, 232 ); 233 }); 234 }; 235 236 const processParserResult = ProcessParserResult[type] as ( 237 parserResult: CustomQueryParserResultTypeMap[Q], 238 make: typeof makeTraceEntry, 239 ) => CustomQueryResultTypeMap<T>[Q]; 240 241 const parserResult = (await this.parser.customQuery( 242 type, 243 this.entriesRange, 244 param, 245 )) as CustomQueryParserResultTypeMap[Q]; 246 const finalResult = processParserResult(parserResult, makeTraceEntry); 247 return Promise.resolve(finalResult); 248 } 249 250 getFrame(frame: AbsoluteFrameIndex): Trace<T> { 251 this.checkTraceCanBeAccessedInFrameDomain(); 252 const entries = this.frameMap!.getEntriesRange({ 253 start: frame, 254 end: frame + 1, 255 }); 256 return this.createSlice(entries, {start: frame, end: frame + 1}); 257 } 258 259 findClosestEntry(time: Timestamp): TraceEntryLazy<T> | undefined { 260 if (this.lengthEntries === 0) { 261 return undefined; 262 } 263 264 const entry = this.clampEntryToSliceBounds( 265 ArrayUtils.binarySearchFirstGreaterOrEqual( 266 this.getFullTraceTimestamps(), 267 time, 268 ), 269 ); 270 if (entry === undefined || entry === this.entriesRange.end) { 271 return this.getEntry(this.lengthEntries - 1); 272 } 273 274 if (entry === this.entriesRange.start) { 275 return this.getEntry(0); 276 } 277 278 const abs = (time: bigint) => (time < 0 ? -time : time); 279 const timeDiff = abs( 280 this.getFullTraceTimestamps()[entry].getValueNs() - time.getValueNs(), 281 ); 282 const prevEntry = entry - 1; 283 const prevTimeDiff = abs( 284 this.getFullTraceTimestamps()[prevEntry].getValueNs() - time.getValueNs(), 285 ); 286 if (prevTimeDiff < timeDiff) { 287 return this.getEntry(prevEntry - this.entriesRange.start); 288 } 289 return this.getEntry(entry - this.entriesRange.start); 290 } 291 292 findFirstGreaterOrEqualEntry(time: Timestamp): TraceEntryLazy<T> | undefined { 293 if (this.lengthEntries === 0) { 294 return undefined; 295 } 296 297 const pos = this.clampEntryToSliceBounds( 298 ArrayUtils.binarySearchFirstGreaterOrEqual( 299 this.getFullTraceTimestamps(), 300 time, 301 ), 302 ); 303 if (pos === undefined || pos === this.entriesRange.end) { 304 return undefined; 305 } 306 307 const entry = this.getEntry(pos - this.entriesRange.start); 308 if (entry.getTimestamp().getValueNs() < time.getValueNs()) { 309 return undefined; 310 } 311 312 return entry; 313 } 314 315 findFirstGreaterEntry(time: Timestamp): TraceEntryLazy<T> | undefined { 316 if (this.lengthEntries === 0) { 317 return undefined; 318 } 319 320 const pos = this.clampEntryToSliceBounds( 321 ArrayUtils.binarySearchFirstGreater(this.getFullTraceTimestamps(), time), 322 ); 323 if (pos === undefined || pos === this.entriesRange.end) { 324 return undefined; 325 } 326 327 const entry = this.getEntry(pos - this.entriesRange.start); 328 if (entry.getTimestamp().getValueNs() <= time.getValueNs()) { 329 return undefined; 330 } 331 332 return entry; 333 } 334 335 findLastLowerOrEqualEntry( 336 timestamp: Timestamp, 337 ): TraceEntryLazy<T> | undefined { 338 if (this.lengthEntries === 0) { 339 return undefined; 340 } 341 const firstGreater = this.findFirstGreaterEntry(timestamp); 342 if (!firstGreater) { 343 return this.getEntry(this.lengthEntries - 1); 344 } 345 if (firstGreater.getIndex() === this.entriesRange.start) { 346 return undefined; 347 } 348 return this.getEntry(firstGreater.getIndex() - this.entriesRange.start - 1); 349 } 350 351 findLastLowerEntry(timestamp: Timestamp): TraceEntryLazy<T> | undefined { 352 if (this.lengthEntries === 0) { 353 return undefined; 354 } 355 const firstGreaterOrEqual = this.findFirstGreaterOrEqualEntry(timestamp); 356 if (!firstGreaterOrEqual) { 357 return this.getEntry(this.lengthEntries - 1); 358 } 359 if (firstGreaterOrEqual.getIndex() === this.entriesRange.start) { 360 return undefined; 361 } 362 return this.getEntry( 363 firstGreaterOrEqual.getIndex() - this.entriesRange.start - 1, 364 ); 365 } 366 367 sliceEntries(start?: RelativeEntryIndex, end?: RelativeEntryIndex): Trace<T> { 368 const startEntry = 369 this.clampEntryToSliceBounds(this.convertToAbsoluteEntryIndex(start)) ?? 370 this.entriesRange.start; 371 const endEntry = 372 this.clampEntryToSliceBounds(this.convertToAbsoluteEntryIndex(end)) ?? 373 this.entriesRange.end; 374 const entries: EntriesRange = { 375 start: startEntry, 376 end: endEntry, 377 }; 378 const frames = this.frameMap?.getFramesRange(entries); 379 return this.createSlice(entries, frames); 380 } 381 382 sliceTime(start?: Timestamp, end?: Timestamp): Trace<T> { 383 const startEntry = 384 start === undefined 385 ? this.entriesRange.start 386 : this.clampEntryToSliceBounds( 387 ArrayUtils.binarySearchFirstGreaterOrEqual( 388 this.getFullTraceTimestamps(), 389 start, 390 ), 391 ) ?? this.entriesRange.end; 392 const endEntry = 393 end === undefined 394 ? this.entriesRange.end 395 : this.clampEntryToSliceBounds( 396 ArrayUtils.binarySearchFirstGreaterOrEqual( 397 this.getFullTraceTimestamps(), 398 end, 399 ), 400 ) ?? this.entriesRange.end; 401 const entries: EntriesRange = { 402 start: startEntry, 403 end: endEntry, 404 }; 405 const frames = this.frameMap?.getFramesRange(entries); 406 return this.createSlice(entries, frames); 407 } 408 409 sliceFrames(start?: AbsoluteFrameIndex, end?: AbsoluteFrameIndex): Trace<T> { 410 this.checkTraceCanBeAccessedInFrameDomain(); 411 if (!this.framesRange) { 412 return this.createSlice(undefined, undefined); 413 } 414 const frames: FramesRange = { 415 start: this.clampFrameToSliceBounds(start) ?? this.framesRange.start, 416 end: this.clampFrameToSliceBounds(end) ?? this.framesRange.end, 417 }; 418 const entries = this.frameMap!.getEntriesRange(frames); 419 return this.createSlice(entries, frames); 420 } 421 422 forEachEntry( 423 callback: (pos: TraceEntryLazy<T>, index: RelativeEntryIndex) => void, 424 ) { 425 for (let index = 0; index < this.lengthEntries; ++index) { 426 callback(this.getEntry(index), index); 427 } 428 } 429 430 mapEntry<U>( 431 callback: (entry: TraceEntryLazy<T>, index: RelativeEntryIndex) => U, 432 ): U[] { 433 const result: U[] = []; 434 this.forEachEntry((entry, index) => { 435 result.push(callback(entry, index)); 436 }); 437 return result; 438 } 439 440 forEachTimestamp( 441 callback: (timestamp: Timestamp, index: RelativeEntryIndex) => void, 442 ) { 443 const timestamps = this.getFullTraceTimestamps(); 444 for (let index = 0; index < this.lengthEntries; ++index) { 445 callback(timestamps[this.entriesRange.start + index], index); 446 } 447 } 448 449 forEachFrame(callback: (frame: Trace<T>, index: AbsoluteFrameIndex) => void) { 450 this.checkTraceCanBeAccessedInFrameDomain(); 451 if (!this.framesRange) { 452 return; 453 } 454 for ( 455 let frame = this.framesRange.start; 456 frame < this.framesRange.end; 457 ++frame 458 ) { 459 callback(this.getFrame(frame), frame); 460 } 461 } 462 463 mapFrame<U>( 464 callback: (frame: Trace<T>, index: AbsoluteFrameIndex) => U, 465 ): U[] { 466 const result: U[] = []; 467 this.forEachFrame((traces, index) => { 468 result.push(callback(traces, index)); 469 }); 470 return result; 471 } 472 473 getFramesRange(): FramesRange | undefined { 474 this.checkTraceCanBeAccessedInFrameDomain(); 475 return this.framesRange; 476 } 477 478 isDump(): boolean { 479 return this.lengthEntries === 1; 480 } 481 482 isDumpWithoutTimestamp(): boolean { 483 return this.isDump() && !this.getEntry(0).hasValidTimestamp(); 484 } 485 486 isCorrupted(): boolean { 487 return this.corruptedState; 488 } 489 490 getCorruptedReason(): string | undefined { 491 return this.corruptedReason; 492 } 493 494 setCorruptedState(value: boolean, reason?: string) { 495 this.corruptedState = value; 496 this.corruptedReason = reason; 497 } 498 499 spansMultipleDates(): boolean { 500 if (this.lengthEntries > 0) { 501 let firstTs: string | undefined; 502 let i = 0; 503 while (firstTs === undefined && i < this.lengthEntries) { 504 const entry = this.getEntry(i); 505 if (entry.hasValidTimestamp()) { 506 firstTs = entry.getTimestamp().format(); 507 break; 508 } 509 i++; 510 } 511 const firstDate = TimestampUtils.extractDateFromHumanTimestamp( 512 assertDefined(firstTs), 513 ); 514 if (firstDate) { 515 const lastDate = TimestampUtils.extractDateFromHumanTimestamp( 516 this.getEntry(this.lengthEntries - 1) 517 .getTimestamp() 518 .format(), 519 ); 520 return firstDate !== lastDate; 521 } 522 } 523 return false; 524 } 525 526 private getEntryInternal< 527 EntryType extends TraceEntryLazy<T> | TraceEntryEager<T, any>, 528 >( 529 index: RelativeEntryIndex, 530 makeEntry: ( 531 absoluteIndex: AbsoluteEntryIndex, 532 timestamp: Timestamp, 533 frames: FramesRange | undefined, 534 ) => EntryType, 535 ): EntryType { 536 const absoluteIndex = this.convertToAbsoluteEntryIndex( 537 index, 538 ) as AbsoluteEntryIndex; 539 if ( 540 absoluteIndex < this.entriesRange.start || 541 absoluteIndex >= this.entriesRange.end 542 ) { 543 throw new Error( 544 `${ 545 TRACE_INFO[this.type].name 546 } trace entry's index out of bounds. Input relative index: ${index}. Slice length: ${ 547 this.lengthEntries 548 }.`, 549 ); 550 } 551 const timestamp = this.getFullTraceTimestamps()[absoluteIndex]; 552 const frames = this.clampFramesRangeToSliceBounds( 553 this.frameMap?.getFramesRange({ 554 start: absoluteIndex, 555 end: absoluteIndex + 1, 556 }), 557 ); 558 return makeEntry(absoluteIndex, timestamp, frames); 559 } 560 561 private getFullTraceTimestamps(): Timestamp[] { 562 const timestamps = this.parser.getTimestamps(); 563 if (!timestamps) { 564 throw new Error( 565 `Timestamps expected to be available for this ${ 566 TRACE_INFO[this.type].name 567 } trace.`, 568 ); 569 } 570 return timestamps; 571 } 572 573 private convertToAbsoluteEntryIndex( 574 index: RelativeEntryIndex | undefined, 575 ): AbsoluteEntryIndex | undefined { 576 if (index === undefined) { 577 return undefined; 578 } 579 if (index < 0) { 580 return this.entriesRange.end + index; 581 } 582 return this.entriesRange.start + index; 583 } 584 585 private createSlice( 586 entries: EntriesRange | undefined, 587 frames: FramesRange | undefined, 588 ): Trace<T> { 589 entries = this.clampEntriesRangeToSliceBounds(entries); 590 frames = this.clampFramesRangeToSliceBounds(frames); 591 592 if (entries === undefined || entries.start >= entries.end) { 593 entries = { 594 start: this.entriesRange.end, 595 end: this.entriesRange.end, 596 }; 597 } 598 599 const slice = new Trace<T>( 600 this.type, 601 this.parser, 602 this.descriptors, 603 this.fullTrace, 604 entries, 605 ); 606 607 if (this.frameMap) { 608 slice.setFrameInfo(this.frameMap, frames); 609 } 610 611 return slice; 612 } 613 614 private clampEntriesRangeToSliceBounds( 615 entries: EntriesRange | undefined, 616 ): EntriesRange | undefined { 617 if (entries === undefined) { 618 return undefined; 619 } 620 return { 621 start: this.clampEntryToSliceBounds(entries.start) as AbsoluteEntryIndex, 622 end: this.clampEntryToSliceBounds(entries.end) as AbsoluteEntryIndex, 623 }; 624 } 625 626 private clampFramesRangeToSliceBounds( 627 frames: FramesRange | undefined, 628 ): FramesRange | undefined { 629 if (frames === undefined) { 630 return undefined; 631 } 632 return { 633 start: this.clampFrameToSliceBounds(frames.start) as AbsoluteFrameIndex, 634 end: this.clampFrameToSliceBounds(frames.end) as AbsoluteFrameIndex, 635 }; 636 } 637 638 private clampEntryToSliceBounds( 639 entry: AbsoluteEntryIndex | undefined, 640 ): AbsoluteEntryIndex | undefined { 641 if (entry === undefined) { 642 return undefined; 643 } 644 return Math.min( 645 Math.max(entry, this.entriesRange.start), 646 this.entriesRange.end, 647 ); 648 } 649 650 private clampFrameToSliceBounds( 651 frame: AbsoluteFrameIndex | undefined, 652 ): AbsoluteFrameIndex | undefined { 653 if (!this.framesRange || frame === undefined) { 654 return undefined; 655 } 656 657 if (frame < 0) { 658 throw new Error( 659 `Absolute frame index cannot be negative. Found '${frame}'`, 660 ); 661 } 662 663 return Math.min( 664 Math.max(frame, this.framesRange.start), 665 this.framesRange.end, 666 ); 667 } 668 669 private checkTraceCanBeAccessedInFrameDomain() { 670 if (!this.frameMap) { 671 throw new Error( 672 `Trace ${ 673 TRACE_INFO[this.type].name 674 } can't be accessed in frame domain (no frame mapping available)`, 675 ); 676 } 677 } 678} 679