• 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 
17 import {ArrayUtils} from 'common/array_utils';
18 import {INVALID_TIME_NS, Timestamp} from 'common/time';
19 import {
20   CustomQueryParamTypeMap,
21   CustomQueryParserResultTypeMap,
22   CustomQueryResultTypeMap,
23   CustomQueryType,
24   ProcessParserResult,
25 } from './custom_query';
26 import {FrameMap} from './frame_map';
27 import {
28   AbsoluteEntryIndex,
29   AbsoluteFrameIndex,
30   EntriesRange,
31   FramesRange,
32   RelativeEntryIndex,
33 } from './index_types';
34 import {Parser} from './parser';
35 import {TraceType} from './trace_type';
36 
37 export {
38   AbsoluteEntryIndex,
39   AbsoluteFrameIndex,
40   EntriesRange,
41   FramesRange,
42   RelativeEntryIndex,
43 } from './index_types';
44 
45 export 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 
78 export 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 
94 export 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 
114 export 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