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 {Timestamp} from 'common/time';
18import {AbsoluteFrameIndex} from './index_types';
19import {Trace} from './trace';
20import {TraceEntryTypeMap, TraceType} from './trace_type';
21
22export class Traces {
23  private traces = new Set<Trace<{}>>();
24
25  addTrace(trace: Trace<{}>) {
26    this.traces.add(trace);
27  }
28
29  getTrace<T extends TraceType>(
30    type: T,
31  ): Trace<TraceEntryTypeMap[T]> | undefined {
32    let longestTraceWithMatchingType: Trace<{}> | undefined;
33    this.traces.forEach((trace) => {
34      if (trace.type !== type) {
35        return;
36      }
37
38      // If multiple traces match the target type, return the longest one.
39      // Assuming that covering this scenario is good enough (hopefully):
40      // - Two traces have the same type because one is a dump (e.g. SF trace + dump)
41      // - A viewer needs to access another trace (e.g. IME viewers accesses SF trace)
42      if (
43        !longestTraceWithMatchingType ||
44        trace.lengthEntries > longestTraceWithMatchingType.lengthEntries
45      ) {
46        longestTraceWithMatchingType = trace;
47      }
48    });
49    return longestTraceWithMatchingType as
50      | Trace<TraceEntryTypeMap[T]>
51      | undefined;
52  }
53
54  getTraces<T extends TraceType>(type: T): Array<Trace<TraceEntryTypeMap[T]>> {
55    return Array.from(this.traces).filter(
56      (trace) => trace.type === type,
57    ) as Array<Trace<TraceEntryTypeMap[T]>>;
58  }
59
60  deleteTrace(trace: Trace<{}>) {
61    this.traces.delete(trace);
62  }
63
64  deleteTracesByType(type: TraceType) {
65    this.traces.forEach((trace) => {
66      if (trace.type !== type) {
67        return;
68      }
69      this.traces.delete(trace);
70    });
71  }
72
73  hasTrace(trace: Trace<{}>) {
74    return this.traces.has(trace);
75  }
76
77  sliceTime(start?: Timestamp, end?: Timestamp): Traces {
78    const slice = new Traces();
79    this.traces.forEach((trace) => {
80      slice.addTrace(trace.sliceTime(start, end));
81    });
82    return slice;
83  }
84
85  sliceFrames(start?: AbsoluteFrameIndex, end?: AbsoluteFrameIndex): Traces {
86    const slice = new Traces();
87    this.traces.forEach((trace) => {
88      slice.addTrace(trace.sliceFrames(start, end));
89    });
90    return slice;
91  }
92
93  forEachTrace(callback: (trace: Trace<{}>, type: TraceType) => void): void {
94    this.traces.forEach((trace, type) => {
95      callback(trace, trace.type);
96    });
97  }
98
99  mapTrace<T>(callback: (trace: Trace<{}>, type: TraceType) => T): T[] {
100    const result: T[] = [];
101    this.forEachTrace((trace, type) => {
102      result.push(callback(trace, type));
103    });
104    return result;
105  }
106
107  forEachFrame(
108    callback: (traces: Traces, index: AbsoluteFrameIndex) => void,
109  ): void {
110    let startFrameIndex: AbsoluteFrameIndex = Number.MAX_VALUE;
111    let endFrameIndex: AbsoluteFrameIndex = Number.MIN_VALUE;
112
113    this.traces.forEach((trace) => {
114      const framesRange = trace.getFramesRange();
115      if (framesRange && framesRange.start < framesRange.end) {
116        startFrameIndex = Math.min(startFrameIndex, framesRange.start);
117        endFrameIndex = Math.max(endFrameIndex, framesRange.end);
118      }
119    });
120
121    for (let i = startFrameIndex; i < endFrameIndex; ++i) {
122      callback(this.sliceFrames(i, i + 1), i);
123    }
124  }
125
126  mapFrame<T>(callback: (traces: Traces, index: AbsoluteFrameIndex) => T): T[] {
127    const result: T[] = [];
128    this.forEachFrame((traces, index) => {
129      result.push(callback(traces, index));
130    });
131    return result;
132  }
133
134  getSize(): number {
135    return this.traces.size;
136  }
137
138  [Symbol.iterator]() {
139    return this.traces.values();
140  }
141}
142