• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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