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/time'; 20import {TimestampUtils} from 'common/time/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 isPerfetto(): 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 = assertDefined(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 if (!firstTs) { 512 return false; 513 } 514 const firstDate = TimestampUtils.extractDateFromHumanTimestamp(firstTs); 515 if (firstDate) { 516 const lastDate = TimestampUtils.extractDateFromHumanTimestamp( 517 this.getEntry(this.lengthEntries - 1) 518 .getTimestamp() 519 .format(), 520 ); 521 return firstDate !== lastDate; 522 } 523 } 524 return false; 525 } 526 527 private getEntryInternal< 528 EntryType extends TraceEntryLazy<T> | TraceEntryEager<T, unknown>, 529 >( 530 index: RelativeEntryIndex, 531 makeEntry: ( 532 absoluteIndex: AbsoluteEntryIndex, 533 timestamp: Timestamp, 534 frames: FramesRange | undefined, 535 ) => EntryType, 536 ): EntryType { 537 const absoluteIndex = this.convertToAbsoluteEntryIndex( 538 index, 539 ) as AbsoluteEntryIndex; 540 if ( 541 absoluteIndex < this.entriesRange.start || 542 absoluteIndex >= this.entriesRange.end 543 ) { 544 throw new Error( 545 `${ 546 TRACE_INFO[this.type].name 547 } trace entry's index out of bounds. Input relative index: ${index}. Slice length: ${ 548 this.lengthEntries 549 }.`, 550 ); 551 } 552 const timestamp = this.getFullTraceTimestamps()[absoluteIndex]; 553 const frames = this.clampFramesRangeToSliceBounds( 554 this.frameMap?.getFramesRange({ 555 start: absoluteIndex, 556 end: absoluteIndex + 1, 557 }), 558 ); 559 return makeEntry(absoluteIndex, timestamp, frames); 560 } 561 562 private getFullTraceTimestamps(): Timestamp[] { 563 const timestamps = this.parser.getTimestamps(); 564 if (!timestamps) { 565 throw new Error( 566 `Timestamps expected to be available for this ${ 567 TRACE_INFO[this.type].name 568 } trace.`, 569 ); 570 } 571 return timestamps; 572 } 573 574 private convertToAbsoluteEntryIndex( 575 index: RelativeEntryIndex | undefined, 576 ): AbsoluteEntryIndex | undefined { 577 if (index === undefined) { 578 return undefined; 579 } 580 if (index < 0) { 581 return this.entriesRange.end + index; 582 } 583 return this.entriesRange.start + index; 584 } 585 586 private createSlice( 587 entries: EntriesRange | undefined, 588 frames: FramesRange | undefined, 589 ): Trace<T> { 590 entries = this.clampEntriesRangeToSliceBounds(entries); 591 frames = this.clampFramesRangeToSliceBounds(frames); 592 593 if (entries === undefined || entries.start >= entries.end) { 594 entries = { 595 start: this.entriesRange.end, 596 end: this.entriesRange.end, 597 }; 598 } 599 600 const slice = new Trace<T>( 601 this.type, 602 this.parser, 603 this.descriptors, 604 this.fullTrace, 605 entries, 606 ); 607 608 if (this.frameMap) { 609 slice.setFrameInfo(this.frameMap, frames); 610 } 611 612 return slice; 613 } 614 615 private clampEntriesRangeToSliceBounds( 616 entries: EntriesRange | undefined, 617 ): EntriesRange | undefined { 618 if (entries === undefined) { 619 return undefined; 620 } 621 return { 622 start: assertDefined(this.clampEntryToSliceBounds(entries.start)), 623 end: assertDefined(this.clampEntryToSliceBounds(entries.end)), 624 }; 625 } 626 627 private clampFramesRangeToSliceBounds( 628 frames: FramesRange | undefined, 629 ): FramesRange | undefined { 630 if (frames === undefined) { 631 return undefined; 632 } 633 return { 634 start: assertDefined(this.clampFrameToSliceBounds(frames.start)), 635 end: assertDefined(this.clampFrameToSliceBounds(frames.end)), 636 }; 637 } 638 639 private clampEntryToSliceBounds( 640 entry: AbsoluteEntryIndex | undefined, 641 ): AbsoluteEntryIndex | undefined { 642 if (entry === undefined) { 643 return undefined; 644 } 645 return Math.min( 646 Math.max(entry, this.entriesRange.start), 647 this.entriesRange.end, 648 ); 649 } 650 651 private clampFrameToSliceBounds( 652 frame: AbsoluteFrameIndex | undefined, 653 ): AbsoluteFrameIndex | undefined { 654 if (!this.framesRange || frame === undefined) { 655 return undefined; 656 } 657 658 if (frame < 0) { 659 throw new Error( 660 `Absolute frame index cannot be negative. Found '${frame}'`, 661 ); 662 } 663 664 return Math.min( 665 Math.max(frame, this.framesRange.start), 666 this.framesRange.end, 667 ); 668 } 669 670 private checkTraceCanBeAccessedInFrameDomain() { 671 if (!this.frameMap) { 672 throw new Error( 673 `Trace ${ 674 TRACE_INFO[this.type].name 675 } can't be accessed in frame domain (no frame mapping available)`, 676 ); 677 } 678 } 679} 680