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 {FrameMap} from './frame_map'; 19import { 20 AbsoluteEntryIndex, 21 AbsoluteFrameIndex, 22 EntriesRange, 23 FramesRange, 24 RelativeEntryIndex, 25} from './index_types'; 26import {Parser} from './parser'; 27import {Timestamp, TimestampType} from './timestamp'; 28import {TraceType} from './trace_type'; 29 30export { 31 AbsoluteEntryIndex, 32 AbsoluteFrameIndex, 33 EntriesRange, 34 FramesRange, 35 RelativeEntryIndex, 36} from './index_types'; 37 38export class TraceEntry<T> { 39 constructor( 40 private readonly fullTrace: Trace<T>, 41 private readonly parser: Parser<T>, 42 private readonly index: AbsoluteEntryIndex, 43 private readonly timestamp: Timestamp, 44 private readonly framesRange: FramesRange | undefined 45 ) {} 46 47 getFullTrace(): Trace<T> { 48 return this.fullTrace; 49 } 50 51 getIndex(): AbsoluteEntryIndex { 52 return this.index; 53 } 54 55 getTimestamp(): Timestamp { 56 return this.timestamp; 57 } 58 59 getFramesRange(): FramesRange | undefined { 60 if (!this.fullTrace.hasFrameInfo()) { 61 throw new Error( 62 `Trace ${this.fullTrace.type} can't be accessed in frame domain (no frame info available)` 63 ); 64 } 65 return this.framesRange; 66 } 67 68 async getValue(): Promise<T> { 69 return await this.parser.getEntry(this.index, this.timestamp.getType()); 70 } 71} 72 73export class Trace<T> { 74 readonly type: TraceType; 75 readonly lengthEntries: number; 76 77 private readonly parser: Parser<T>; 78 private readonly descriptors: string[]; 79 private readonly fullTrace: Trace<T>; 80 private timestampType: TimestampType | undefined; 81 private readonly entriesRange: EntriesRange; 82 private frameMap?: FrameMap; 83 private framesRange?: FramesRange; 84 85 static newUninitializedTrace<T>(parser: Parser<T>): Trace<T> { 86 return new Trace( 87 parser.getTraceType(), 88 parser, 89 parser.getDescriptors(), 90 undefined, 91 undefined, 92 undefined 93 ); 94 } 95 96 static newInitializedTrace<T>( 97 type: TraceType, 98 entryProvider: Parser<T>, 99 descriptors: string[], 100 timestampType: TimestampType, 101 entriesRange: EntriesRange 102 ): Trace<T> { 103 return new Trace(type, entryProvider, descriptors, undefined, timestampType, entriesRange); 104 } 105 106 init(timestampType: TimestampType) { 107 this.timestampType = timestampType; 108 } 109 110 private constructor( 111 type: TraceType, 112 parser: Parser<T>, 113 descriptors: string[], 114 fullTrace: Trace<T> | undefined, 115 timestampType: TimestampType | undefined, 116 entriesRange: EntriesRange | undefined 117 ) { 118 this.type = type; 119 this.parser = parser; 120 this.descriptors = descriptors; 121 this.fullTrace = fullTrace ?? this; 122 this.entriesRange = entriesRange ?? {start: 0, end: parser.getLengthEntries()}; 123 this.lengthEntries = this.entriesRange.end - this.entriesRange.start; 124 this.timestampType = timestampType; 125 } 126 127 getDescriptors(): string[] { 128 return this.parser.getDescriptors(); 129 } 130 131 getTimestampType(): TimestampType { 132 if (this.timestampType === undefined) { 133 throw new Error('Trace no fully initialized yet!'); 134 } 135 return this.timestampType; 136 } 137 138 setFrameInfo(frameMap: FrameMap, framesRange: FramesRange | undefined) { 139 if (frameMap.lengthEntries !== this.fullTrace.lengthEntries) { 140 throw new Error('Attemped to set a frame map with incompatible number of entries'); 141 } 142 this.frameMap = frameMap; 143 this.framesRange = framesRange; 144 } 145 146 hasFrameInfo(): boolean { 147 return this.frameMap !== undefined; 148 } 149 150 getEntry(index: RelativeEntryIndex): TraceEntry<T> { 151 const entry = this.convertToAbsoluteEntryIndex(index) as AbsoluteEntryIndex; 152 if (entry < this.entriesRange.start || entry >= this.entriesRange.end) { 153 throw new Error( 154 `Trace entry's index out of bounds. Input relative index: ${index}. Slice length: ${this.lengthEntries}.` 155 ); 156 } 157 const frames = this.clampFramesRangeToSliceBounds( 158 this.frameMap?.getFramesRange({start: entry, end: entry + 1}) 159 ); 160 return new TraceEntry<T>( 161 this.fullTrace, 162 this.parser, 163 entry, 164 this.getFullTraceTimestamps()[entry], 165 frames 166 ); 167 } 168 169 getFrame(frame: AbsoluteFrameIndex): Trace<T> { 170 this.checkTraceCanBeAccessedInFrameDomain(); 171 const entries = this.frameMap!.getEntriesRange({start: frame, end: frame + 1}); 172 return this.createSlice(entries, {start: frame, end: frame + 1}); 173 } 174 175 findClosestEntry(time: Timestamp): TraceEntry<T> | undefined { 176 this.checkTimestampIsCompatible(time); 177 if (this.lengthEntries === 0) { 178 return undefined; 179 } 180 181 const entry = this.clampEntryToSliceBounds( 182 ArrayUtils.binarySearchFirstGreaterOrEqual(this.getFullTraceTimestamps(), time) 183 ); 184 if (entry === undefined || entry === this.entriesRange.end) { 185 return this.getEntry(this.lengthEntries - 1); 186 } 187 188 if (entry === this.entriesRange.start) { 189 return this.getEntry(0); 190 } 191 192 const abs = (time: bigint) => (time < 0 ? -time : time); 193 const timeDiff = abs(this.getFullTraceTimestamps()[entry].getValueNs() - time.getValueNs()); 194 const prevEntry = entry - 1; 195 const prevTimeDiff = abs( 196 this.getFullTraceTimestamps()[prevEntry].getValueNs() - time.getValueNs() 197 ); 198 if (prevTimeDiff < timeDiff) { 199 return this.getEntry(prevEntry - this.entriesRange.start); 200 } 201 return this.getEntry(entry - this.entriesRange.start); 202 } 203 204 findFirstGreaterOrEqualEntry(time: Timestamp): TraceEntry<T> | undefined { 205 this.checkTimestampIsCompatible(time); 206 if (this.lengthEntries === 0) { 207 return undefined; 208 } 209 210 const pos = this.clampEntryToSliceBounds( 211 ArrayUtils.binarySearchFirstGreaterOrEqual(this.getFullTraceTimestamps(), time) 212 ); 213 if (pos === undefined || pos === this.entriesRange.end) { 214 return undefined; 215 } 216 217 const entry = this.getEntry(pos - this.entriesRange.start); 218 if (entry.getTimestamp() < time) { 219 return undefined; 220 } 221 222 return entry; 223 } 224 225 findFirstGreaterEntry(time: Timestamp): TraceEntry<T> | undefined { 226 this.checkTimestampIsCompatible(time); 227 if (this.lengthEntries === 0) { 228 return undefined; 229 } 230 231 const pos = this.clampEntryToSliceBounds( 232 ArrayUtils.binarySearchFirstGreater(this.getFullTraceTimestamps(), time) 233 ); 234 if (pos === undefined || pos === this.entriesRange.end) { 235 return undefined; 236 } 237 238 const entry = this.getEntry(pos - this.entriesRange.start); 239 if (entry.getTimestamp() <= time) { 240 return undefined; 241 } 242 243 return entry; 244 } 245 246 findLastLowerOrEqualEntry(timestamp: Timestamp): TraceEntry<T> | undefined { 247 if (this.lengthEntries === 0) { 248 return undefined; 249 } 250 const firstGreater = this.findFirstGreaterEntry(timestamp); 251 if (!firstGreater) { 252 return this.getEntry(this.lengthEntries - 1); 253 } 254 if (firstGreater.getIndex() === this.entriesRange.start) { 255 return undefined; 256 } 257 return this.getEntry(firstGreater.getIndex() - this.entriesRange.start - 1); 258 } 259 260 findLastLowerEntry(timestamp: Timestamp): TraceEntry<T> | undefined { 261 if (this.lengthEntries === 0) { 262 return undefined; 263 } 264 const firstGreaterOrEqual = this.findFirstGreaterOrEqualEntry(timestamp); 265 if (!firstGreaterOrEqual) { 266 return this.getEntry(this.lengthEntries - 1); 267 } 268 if (firstGreaterOrEqual.getIndex() === this.entriesRange.start) { 269 return undefined; 270 } 271 return this.getEntry(firstGreaterOrEqual.getIndex() - this.entriesRange.start - 1); 272 } 273 274 sliceEntries(start?: RelativeEntryIndex, end?: RelativeEntryIndex): Trace<T> { 275 const startEntry = 276 this.clampEntryToSliceBounds(this.convertToAbsoluteEntryIndex(start)) ?? 277 this.entriesRange.start; 278 const endEntry = 279 this.clampEntryToSliceBounds(this.convertToAbsoluteEntryIndex(end)) ?? this.entriesRange.end; 280 const entries: EntriesRange = { 281 start: startEntry, 282 end: endEntry, 283 }; 284 const frames = this.frameMap?.getFramesRange(entries); 285 return this.createSlice(entries, frames); 286 } 287 288 sliceTime(start?: Timestamp, end?: Timestamp): Trace<T> { 289 this.checkTimestampIsCompatible(start); 290 this.checkTimestampIsCompatible(end); 291 const startEntry = 292 start === undefined 293 ? this.entriesRange.start 294 : this.clampEntryToSliceBounds( 295 ArrayUtils.binarySearchFirstGreaterOrEqual(this.getFullTraceTimestamps(), start) 296 ) ?? this.entriesRange.end; 297 const endEntry = 298 end === undefined 299 ? this.entriesRange.end 300 : this.clampEntryToSliceBounds( 301 ArrayUtils.binarySearchFirstGreaterOrEqual(this.getFullTraceTimestamps(), end) 302 ) ?? this.entriesRange.end; 303 const entries: EntriesRange = { 304 start: startEntry, 305 end: endEntry, 306 }; 307 const frames = this.frameMap?.getFramesRange(entries); 308 return this.createSlice(entries, frames); 309 } 310 311 sliceFrames(start?: AbsoluteFrameIndex, end?: AbsoluteFrameIndex): Trace<T> { 312 this.checkTraceCanBeAccessedInFrameDomain(); 313 if (!this.framesRange) { 314 return this.createSlice(undefined, undefined); 315 } 316 const frames: FramesRange = { 317 start: this.clampFrameToSliceBounds(start) ?? this.framesRange.start, 318 end: this.clampFrameToSliceBounds(end) ?? this.framesRange.end, 319 }; 320 const entries = this.frameMap!.getEntriesRange(frames); 321 return this.createSlice(entries, frames); 322 } 323 324 forEachEntry(callback: (pos: TraceEntry<T>, index: RelativeEntryIndex) => void) { 325 for (let index = 0; index < this.lengthEntries; ++index) { 326 callback(this.getEntry(index), index); 327 } 328 } 329 330 mapEntry<U>(callback: (entry: TraceEntry<T>, index: RelativeEntryIndex) => U): U[] { 331 const result: U[] = []; 332 this.forEachEntry((entry, index) => { 333 result.push(callback(entry, index)); 334 }); 335 return result; 336 } 337 338 forEachTimestamp(callback: (timestamp: Timestamp, index: RelativeEntryIndex) => void) { 339 const timestamps = this.getFullTraceTimestamps(); 340 for (let index = 0; index < this.lengthEntries; ++index) { 341 callback(timestamps[this.entriesRange.start + index], index); 342 } 343 } 344 345 forEachFrame(callback: (frame: Trace<T>, index: AbsoluteFrameIndex) => void) { 346 this.checkTraceCanBeAccessedInFrameDomain(); 347 if (!this.framesRange) { 348 return; 349 } 350 for (let frame = this.framesRange.start; frame < this.framesRange.end; ++frame) { 351 callback(this.getFrame(frame), frame); 352 } 353 } 354 355 mapFrame<U>(callback: (frame: Trace<T>, index: AbsoluteFrameIndex) => U): U[] { 356 const result: U[] = []; 357 this.forEachFrame((traces, index) => { 358 result.push(callback(traces, index)); 359 }); 360 return result; 361 } 362 363 getFramesRange(): FramesRange | undefined { 364 this.checkTraceCanBeAccessedInFrameDomain(); 365 return this.framesRange; 366 } 367 368 private getFullTraceTimestamps(): Timestamp[] { 369 if (this.timestampType === undefined) { 370 throw new Error('Forgot to initialize trace?'); 371 } 372 373 const timestamps = this.parser.getTimestamps(this.timestampType); 374 if (!timestamps) { 375 throw new Error(`Timestamp type ${this.timestampType} is expected to be available`); 376 } 377 return timestamps; 378 } 379 380 private convertToAbsoluteEntryIndex( 381 index: RelativeEntryIndex | undefined 382 ): AbsoluteEntryIndex | undefined { 383 if (index === undefined) { 384 return undefined; 385 } 386 if (index < 0) { 387 return this.entriesRange.end + index; 388 } 389 return this.entriesRange.start + index; 390 } 391 392 private createSlice( 393 entries: EntriesRange | undefined, 394 frames: FramesRange | undefined 395 ): Trace<T> { 396 entries = this.clampEntriesRangeToSliceBounds(entries); 397 frames = this.clampFramesRangeToSliceBounds(frames); 398 399 if (entries === undefined || entries.start >= entries.end) { 400 entries = { 401 start: this.entriesRange.end, 402 end: this.entriesRange.end, 403 }; 404 } 405 406 const slice = new Trace<T>( 407 this.type, 408 this.parser, 409 this.descriptors, 410 this.fullTrace, 411 this.timestampType, 412 entries 413 ); 414 415 if (this.frameMap) { 416 slice.setFrameInfo(this.frameMap, frames); 417 } 418 419 return slice; 420 } 421 422 private clampEntriesRangeToSliceBounds( 423 entries: EntriesRange | undefined 424 ): EntriesRange | undefined { 425 if (entries === undefined) { 426 return undefined; 427 } 428 return { 429 start: this.clampEntryToSliceBounds(entries.start) as AbsoluteEntryIndex, 430 end: this.clampEntryToSliceBounds(entries.end) as AbsoluteEntryIndex, 431 }; 432 } 433 434 private clampFramesRangeToSliceBounds(frames: FramesRange | undefined): FramesRange | undefined { 435 if (frames === undefined) { 436 return undefined; 437 } 438 return { 439 start: this.clampFrameToSliceBounds(frames.start) as AbsoluteFrameIndex, 440 end: this.clampFrameToSliceBounds(frames.end) as AbsoluteFrameIndex, 441 }; 442 } 443 444 private clampEntryToSliceBounds( 445 entry: AbsoluteEntryIndex | undefined 446 ): AbsoluteEntryIndex | undefined { 447 if (entry === undefined) { 448 return undefined; 449 } 450 return Math.min(Math.max(entry, this.entriesRange.start), this.entriesRange.end); 451 } 452 453 private clampFrameToSliceBounds( 454 frame: AbsoluteFrameIndex | undefined 455 ): AbsoluteFrameIndex | undefined { 456 if (!this.framesRange || frame === undefined) { 457 return undefined; 458 } 459 460 if (frame < 0) { 461 throw new Error(`Absolute frame index cannot be negative. Found '${frame}'`); 462 } 463 464 return Math.min(Math.max(frame, this.framesRange.start), this.framesRange.end); 465 } 466 467 private checkTraceCanBeAccessedInFrameDomain() { 468 if (!this.frameMap) { 469 throw new Error( 470 `Trace ${this.type} can't be accessed in frame domain (no frame mapping available)` 471 ); 472 } 473 } 474 475 private checkTimestampIsCompatible(timestamp?: Timestamp) { 476 if (!timestamp) { 477 return; 478 } 479 const timestamps = this.parser.getTimestamps(timestamp.getType()); 480 if (timestamps === undefined) { 481 throw new Error( 482 `Trace ${this.type} can't be accessed using timestamp of type ${timestamp.getType()}` 483 ); 484 } 485 } 486} 487