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