xref: /aosp_15_r20/development/tools/winscope/src/app/loaded_parsers.ts (revision 90c8c64db3049935a07c6143d7fd006e26f8ecca)
1*90c8c64dSAndroid Build Coastguard Worker/*
2*90c8c64dSAndroid Build Coastguard Worker * Copyright (C) 2023 The Android Open Source Project
3*90c8c64dSAndroid Build Coastguard Worker *
4*90c8c64dSAndroid Build Coastguard Worker * Licensed under the Apache License, Version 2.0 (the "License");
5*90c8c64dSAndroid Build Coastguard Worker * you may not use this file except in compliance with the License.
6*90c8c64dSAndroid Build Coastguard Worker * You may obtain a copy of the License at
7*90c8c64dSAndroid Build Coastguard Worker *
8*90c8c64dSAndroid Build Coastguard Worker *      http://www.apache.org/licenses/LICENSE-2.0
9*90c8c64dSAndroid Build Coastguard Worker *
10*90c8c64dSAndroid Build Coastguard Worker * Unless required by applicable law or agreed to in writing, software
11*90c8c64dSAndroid Build Coastguard Worker * distributed under the License is distributed on an "AS IS" BASIS,
12*90c8c64dSAndroid Build Coastguard Worker * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*90c8c64dSAndroid Build Coastguard Worker * See the License for the specific language governing permissions and
14*90c8c64dSAndroid Build Coastguard Worker * limitations under the License.
15*90c8c64dSAndroid Build Coastguard Worker */
16*90c8c64dSAndroid Build Coastguard Worker
17*90c8c64dSAndroid Build Coastguard Workerimport {assertDefined} from 'common/assert_utils';
18*90c8c64dSAndroid Build Coastguard Workerimport {FileUtils} from 'common/file_utils';
19*90c8c64dSAndroid Build Coastguard Workerimport {OnProgressUpdateType} from 'common/function_utils';
20*90c8c64dSAndroid Build Coastguard Workerimport {INVALID_TIME_NS, TimeRange, Timestamp} from 'common/time';
21*90c8c64dSAndroid Build Coastguard Workerimport {TIME_UNIT_TO_NANO} from 'common/time_units';
22*90c8c64dSAndroid Build Coastguard Workerimport {UserNotifier} from 'common/user_notifier';
23*90c8c64dSAndroid Build Coastguard Workerimport {TraceHasOldData, TraceOverridden} from 'messaging/user_warnings';
24*90c8c64dSAndroid Build Coastguard Workerimport {FileAndParser} from 'parsers/file_and_parser';
25*90c8c64dSAndroid Build Coastguard Workerimport {FileAndParsers} from 'parsers/file_and_parsers';
26*90c8c64dSAndroid Build Coastguard Workerimport {Parser} from 'trace/parser';
27*90c8c64dSAndroid Build Coastguard Workerimport {TraceFile} from 'trace/trace_file';
28*90c8c64dSAndroid Build Coastguard Workerimport {TRACE_INFO} from 'trace/trace_info';
29*90c8c64dSAndroid Build Coastguard Workerimport {TraceEntryTypeMap, TraceType} from 'trace/trace_type';
30*90c8c64dSAndroid Build Coastguard Worker
31*90c8c64dSAndroid Build Coastguard Workerexport class LoadedParsers {
32*90c8c64dSAndroid Build Coastguard Worker  static readonly MAX_ALLOWED_TIME_GAP_BETWEEN_TRACES_NS = BigInt(
33*90c8c64dSAndroid Build Coastguard Worker    5 * TIME_UNIT_TO_NANO.m,
34*90c8c64dSAndroid Build Coastguard Worker  ); // 5m
35*90c8c64dSAndroid Build Coastguard Worker  static readonly MAX_ALLOWED_TIME_GAP_BETWEEN_RTE_OFFSET = BigInt(
36*90c8c64dSAndroid Build Coastguard Worker    5 * TIME_UNIT_TO_NANO.s,
37*90c8c64dSAndroid Build Coastguard Worker  ); // 5s
38*90c8c64dSAndroid Build Coastguard Worker  static readonly REAL_TIME_TRACES_WITHOUT_RTE_OFFSET = [
39*90c8c64dSAndroid Build Coastguard Worker    TraceType.CUJS,
40*90c8c64dSAndroid Build Coastguard Worker    TraceType.EVENT_LOG,
41*90c8c64dSAndroid Build Coastguard Worker  ];
42*90c8c64dSAndroid Build Coastguard Worker
43*90c8c64dSAndroid Build Coastguard Worker  private legacyParsers = new Array<FileAndParser>();
44*90c8c64dSAndroid Build Coastguard Worker  private perfettoParsers = new Array<FileAndParser>();
45*90c8c64dSAndroid Build Coastguard Worker  private legacyParsersKeptForDownload = new Array<FileAndParser>();
46*90c8c64dSAndroid Build Coastguard Worker  private perfettoParsersKeptForDownload = new Array<FileAndParser>();
47*90c8c64dSAndroid Build Coastguard Worker
48*90c8c64dSAndroid Build Coastguard Worker  addParsers(
49*90c8c64dSAndroid Build Coastguard Worker    legacyParsers: FileAndParser[],
50*90c8c64dSAndroid Build Coastguard Worker    perfettoParsers: FileAndParsers | undefined,
51*90c8c64dSAndroid Build Coastguard Worker  ) {
52*90c8c64dSAndroid Build Coastguard Worker    if (perfettoParsers) {
53*90c8c64dSAndroid Build Coastguard Worker      this.addPerfettoParsers(perfettoParsers);
54*90c8c64dSAndroid Build Coastguard Worker    }
55*90c8c64dSAndroid Build Coastguard Worker    // Traces were simultaneously upgraded to contain real-to-boottime or real-to-monotonic offsets.
56*90c8c64dSAndroid Build Coastguard Worker    // If we have a mix of parsers with and without offsets, the ones without must be dangling
57*90c8c64dSAndroid Build Coastguard Worker    // trace files with old data, and should be filtered out.
58*90c8c64dSAndroid Build Coastguard Worker    legacyParsers = this.filterOutParsersWithoutOffsetsIfRequired(
59*90c8c64dSAndroid Build Coastguard Worker      legacyParsers,
60*90c8c64dSAndroid Build Coastguard Worker      perfettoParsers,
61*90c8c64dSAndroid Build Coastguard Worker    );
62*90c8c64dSAndroid Build Coastguard Worker    legacyParsers = this.filterOutLegacyParsersWithOldData(legacyParsers);
63*90c8c64dSAndroid Build Coastguard Worker    legacyParsers = this.filterScreenshotParsersIfRequired(legacyParsers);
64*90c8c64dSAndroid Build Coastguard Worker
65*90c8c64dSAndroid Build Coastguard Worker    this.addLegacyParsers(legacyParsers);
66*90c8c64dSAndroid Build Coastguard Worker  }
67*90c8c64dSAndroid Build Coastguard Worker
68*90c8c64dSAndroid Build Coastguard Worker  getParsers(): Array<Parser<object>> {
69*90c8c64dSAndroid Build Coastguard Worker    const fileAndParsers = [
70*90c8c64dSAndroid Build Coastguard Worker      ...this.legacyParsers.values(),
71*90c8c64dSAndroid Build Coastguard Worker      ...this.perfettoParsers.values(),
72*90c8c64dSAndroid Build Coastguard Worker    ];
73*90c8c64dSAndroid Build Coastguard Worker    return fileAndParsers.map((fileAndParser) => fileAndParser.parser);
74*90c8c64dSAndroid Build Coastguard Worker  }
75*90c8c64dSAndroid Build Coastguard Worker
76*90c8c64dSAndroid Build Coastguard Worker  remove<T extends TraceType>(
77*90c8c64dSAndroid Build Coastguard Worker    parser: Parser<TraceEntryTypeMap[T]>,
78*90c8c64dSAndroid Build Coastguard Worker    keepForDownload = false,
79*90c8c64dSAndroid Build Coastguard Worker  ) {
80*90c8c64dSAndroid Build Coastguard Worker    const predicate = (
81*90c8c64dSAndroid Build Coastguard Worker      fileAndParser: FileAndParser,
82*90c8c64dSAndroid Build Coastguard Worker      parsersToKeep: FileAndParser[],
83*90c8c64dSAndroid Build Coastguard Worker    ) => {
84*90c8c64dSAndroid Build Coastguard Worker      const shouldRemove = fileAndParser.parser === parser;
85*90c8c64dSAndroid Build Coastguard Worker      if (shouldRemove && keepForDownload) {
86*90c8c64dSAndroid Build Coastguard Worker        parsersToKeep.push(fileAndParser);
87*90c8c64dSAndroid Build Coastguard Worker      }
88*90c8c64dSAndroid Build Coastguard Worker      return !shouldRemove;
89*90c8c64dSAndroid Build Coastguard Worker    };
90*90c8c64dSAndroid Build Coastguard Worker    this.legacyParsers = this.legacyParsers.filter(
91*90c8c64dSAndroid Build Coastguard Worker      (fileAndParser: FileAndParser) =>
92*90c8c64dSAndroid Build Coastguard Worker        predicate(fileAndParser, this.legacyParsersKeptForDownload),
93*90c8c64dSAndroid Build Coastguard Worker    );
94*90c8c64dSAndroid Build Coastguard Worker    this.perfettoParsers = this.perfettoParsers.filter(
95*90c8c64dSAndroid Build Coastguard Worker      (fileAndParser: FileAndParser) =>
96*90c8c64dSAndroid Build Coastguard Worker        predicate(fileAndParser, this.perfettoParsersKeptForDownload),
97*90c8c64dSAndroid Build Coastguard Worker    );
98*90c8c64dSAndroid Build Coastguard Worker  }
99*90c8c64dSAndroid Build Coastguard Worker
100*90c8c64dSAndroid Build Coastguard Worker  clear() {
101*90c8c64dSAndroid Build Coastguard Worker    this.legacyParsers = [];
102*90c8c64dSAndroid Build Coastguard Worker    this.perfettoParsers = [];
103*90c8c64dSAndroid Build Coastguard Worker  }
104*90c8c64dSAndroid Build Coastguard Worker
105*90c8c64dSAndroid Build Coastguard Worker  async makeZipArchive(onProgressUpdate?: OnProgressUpdateType): Promise<Blob> {
106*90c8c64dSAndroid Build Coastguard Worker    const outputFilesSoFar = new Set<File>();
107*90c8c64dSAndroid Build Coastguard Worker    const outputFilenameToFiles = new Map<string, File[]>();
108*90c8c64dSAndroid Build Coastguard Worker
109*90c8c64dSAndroid Build Coastguard Worker    if (onProgressUpdate) onProgressUpdate(0);
110*90c8c64dSAndroid Build Coastguard Worker    const totalParsers =
111*90c8c64dSAndroid Build Coastguard Worker      this.perfettoParsers.length +
112*90c8c64dSAndroid Build Coastguard Worker      this.perfettoParsersKeptForDownload.length +
113*90c8c64dSAndroid Build Coastguard Worker      this.legacyParsers.length +
114*90c8c64dSAndroid Build Coastguard Worker      this.legacyParsersKeptForDownload.length;
115*90c8c64dSAndroid Build Coastguard Worker    let progress = 0;
116*90c8c64dSAndroid Build Coastguard Worker
117*90c8c64dSAndroid Build Coastguard Worker    const tryPushOutputFile = (file: File, filename: string) => {
118*90c8c64dSAndroid Build Coastguard Worker      // Remove duplicates because some parsers (e.g. view capture) could share the same file
119*90c8c64dSAndroid Build Coastguard Worker      if (outputFilesSoFar.has(file)) {
120*90c8c64dSAndroid Build Coastguard Worker        return;
121*90c8c64dSAndroid Build Coastguard Worker      }
122*90c8c64dSAndroid Build Coastguard Worker      outputFilesSoFar.add(file);
123*90c8c64dSAndroid Build Coastguard Worker
124*90c8c64dSAndroid Build Coastguard Worker      if (outputFilenameToFiles.get(filename) === undefined) {
125*90c8c64dSAndroid Build Coastguard Worker        outputFilenameToFiles.set(filename, []);
126*90c8c64dSAndroid Build Coastguard Worker      }
127*90c8c64dSAndroid Build Coastguard Worker      assertDefined(outputFilenameToFiles.get(filename)).push(file);
128*90c8c64dSAndroid Build Coastguard Worker    };
129*90c8c64dSAndroid Build Coastguard Worker
130*90c8c64dSAndroid Build Coastguard Worker    const makeArchiveFile = (
131*90c8c64dSAndroid Build Coastguard Worker      filename: string,
132*90c8c64dSAndroid Build Coastguard Worker      file: File,
133*90c8c64dSAndroid Build Coastguard Worker      clashCount: number,
134*90c8c64dSAndroid Build Coastguard Worker    ): File => {
135*90c8c64dSAndroid Build Coastguard Worker      if (clashCount === 0) {
136*90c8c64dSAndroid Build Coastguard Worker        return new File([file], filename);
137*90c8c64dSAndroid Build Coastguard Worker      }
138*90c8c64dSAndroid Build Coastguard Worker
139*90c8c64dSAndroid Build Coastguard Worker      const filenameWithoutExt =
140*90c8c64dSAndroid Build Coastguard Worker        FileUtils.removeExtensionFromFilename(filename);
141*90c8c64dSAndroid Build Coastguard Worker      const extension = FileUtils.getFileExtension(filename);
142*90c8c64dSAndroid Build Coastguard Worker
143*90c8c64dSAndroid Build Coastguard Worker      if (extension === undefined) {
144*90c8c64dSAndroid Build Coastguard Worker        return new File([file], `${filename} (${clashCount})`);
145*90c8c64dSAndroid Build Coastguard Worker      }
146*90c8c64dSAndroid Build Coastguard Worker
147*90c8c64dSAndroid Build Coastguard Worker      return new File(
148*90c8c64dSAndroid Build Coastguard Worker        [file],
149*90c8c64dSAndroid Build Coastguard Worker        `${filenameWithoutExt} (${clashCount}).${extension}`,
150*90c8c64dSAndroid Build Coastguard Worker      );
151*90c8c64dSAndroid Build Coastguard Worker    };
152*90c8c64dSAndroid Build Coastguard Worker
153*90c8c64dSAndroid Build Coastguard Worker    const tryPushOutPerfettoFile = (parsers: FileAndParser[]) => {
154*90c8c64dSAndroid Build Coastguard Worker      const file: TraceFile = parsers.values().next().value.file;
155*90c8c64dSAndroid Build Coastguard Worker      let outputFilename = FileUtils.removeDirFromFileName(file.file.name);
156*90c8c64dSAndroid Build Coastguard Worker      if (FileUtils.getFileExtension(file.file.name) === undefined) {
157*90c8c64dSAndroid Build Coastguard Worker        outputFilename += '.perfetto-trace';
158*90c8c64dSAndroid Build Coastguard Worker      }
159*90c8c64dSAndroid Build Coastguard Worker      tryPushOutputFile(file.file, outputFilename);
160*90c8c64dSAndroid Build Coastguard Worker    };
161*90c8c64dSAndroid Build Coastguard Worker
162*90c8c64dSAndroid Build Coastguard Worker    if (this.perfettoParsers.length > 0) {
163*90c8c64dSAndroid Build Coastguard Worker      tryPushOutPerfettoFile(this.perfettoParsers);
164*90c8c64dSAndroid Build Coastguard Worker    } else if (this.perfettoParsersKeptForDownload.length > 0) {
165*90c8c64dSAndroid Build Coastguard Worker      tryPushOutPerfettoFile(this.perfettoParsersKeptForDownload);
166*90c8c64dSAndroid Build Coastguard Worker    }
167*90c8c64dSAndroid Build Coastguard Worker    if (onProgressUpdate) {
168*90c8c64dSAndroid Build Coastguard Worker      progress =
169*90c8c64dSAndroid Build Coastguard Worker        this.perfettoParsers.length +
170*90c8c64dSAndroid Build Coastguard Worker        this.perfettoParsersKeptForDownload.length;
171*90c8c64dSAndroid Build Coastguard Worker      onProgressUpdate((0.5 * progress) / totalParsers);
172*90c8c64dSAndroid Build Coastguard Worker    }
173*90c8c64dSAndroid Build Coastguard Worker
174*90c8c64dSAndroid Build Coastguard Worker    const tryPushOutputLegacyFile = (fileAndParser: FileAndParser) => {
175*90c8c64dSAndroid Build Coastguard Worker      const {file, parser} = fileAndParser;
176*90c8c64dSAndroid Build Coastguard Worker      const traceType = parser.getTraceType();
177*90c8c64dSAndroid Build Coastguard Worker      const archiveDir =
178*90c8c64dSAndroid Build Coastguard Worker        TRACE_INFO[traceType].downloadArchiveDir.length > 0
179*90c8c64dSAndroid Build Coastguard Worker          ? TRACE_INFO[traceType].downloadArchiveDir + '/'
180*90c8c64dSAndroid Build Coastguard Worker          : '';
181*90c8c64dSAndroid Build Coastguard Worker      let outputFilename =
182*90c8c64dSAndroid Build Coastguard Worker        archiveDir + FileUtils.removeDirFromFileName(file.file.name);
183*90c8c64dSAndroid Build Coastguard Worker      if (FileUtils.getFileExtension(file.file.name) === undefined) {
184*90c8c64dSAndroid Build Coastguard Worker        outputFilename += TRACE_INFO[traceType].legacyExt;
185*90c8c64dSAndroid Build Coastguard Worker      }
186*90c8c64dSAndroid Build Coastguard Worker      tryPushOutputFile(file.file, outputFilename);
187*90c8c64dSAndroid Build Coastguard Worker      if (onProgressUpdate) {
188*90c8c64dSAndroid Build Coastguard Worker        progress++;
189*90c8c64dSAndroid Build Coastguard Worker        onProgressUpdate((0.5 * progress) / totalParsers);
190*90c8c64dSAndroid Build Coastguard Worker      }
191*90c8c64dSAndroid Build Coastguard Worker    };
192*90c8c64dSAndroid Build Coastguard Worker
193*90c8c64dSAndroid Build Coastguard Worker    this.legacyParsers.forEach(tryPushOutputLegacyFile);
194*90c8c64dSAndroid Build Coastguard Worker    this.legacyParsersKeptForDownload.forEach(tryPushOutputLegacyFile);
195*90c8c64dSAndroid Build Coastguard Worker
196*90c8c64dSAndroid Build Coastguard Worker    const archiveFiles = [...outputFilenameToFiles.entries()]
197*90c8c64dSAndroid Build Coastguard Worker      .map(([filename, files]) => {
198*90c8c64dSAndroid Build Coastguard Worker        return files.map((file, clashCount) =>
199*90c8c64dSAndroid Build Coastguard Worker          makeArchiveFile(filename, file, clashCount),
200*90c8c64dSAndroid Build Coastguard Worker        );
201*90c8c64dSAndroid Build Coastguard Worker      })
202*90c8c64dSAndroid Build Coastguard Worker      .flat();
203*90c8c64dSAndroid Build Coastguard Worker
204*90c8c64dSAndroid Build Coastguard Worker    return await FileUtils.createZipArchive(
205*90c8c64dSAndroid Build Coastguard Worker      archiveFiles,
206*90c8c64dSAndroid Build Coastguard Worker      onProgressUpdate
207*90c8c64dSAndroid Build Coastguard Worker        ? (perc: number) => onProgressUpdate(0.5 * (1 + perc))
208*90c8c64dSAndroid Build Coastguard Worker        : undefined,
209*90c8c64dSAndroid Build Coastguard Worker    );
210*90c8c64dSAndroid Build Coastguard Worker  }
211*90c8c64dSAndroid Build Coastguard Worker
212*90c8c64dSAndroid Build Coastguard Worker  getLatestRealToMonotonicOffset(
213*90c8c64dSAndroid Build Coastguard Worker    parsers: Array<Parser<object>>,
214*90c8c64dSAndroid Build Coastguard Worker  ): bigint | undefined {
215*90c8c64dSAndroid Build Coastguard Worker    const p = parsers
216*90c8c64dSAndroid Build Coastguard Worker      .filter((offset) => offset.getRealToMonotonicTimeOffsetNs() !== undefined)
217*90c8c64dSAndroid Build Coastguard Worker      .sort((a, b) => {
218*90c8c64dSAndroid Build Coastguard Worker        return Number(
219*90c8c64dSAndroid Build Coastguard Worker          (a.getRealToMonotonicTimeOffsetNs() ?? 0n) -
220*90c8c64dSAndroid Build Coastguard Worker            (b.getRealToMonotonicTimeOffsetNs() ?? 0n),
221*90c8c64dSAndroid Build Coastguard Worker        );
222*90c8c64dSAndroid Build Coastguard Worker      })
223*90c8c64dSAndroid Build Coastguard Worker      .at(-1);
224*90c8c64dSAndroid Build Coastguard Worker    return p?.getRealToMonotonicTimeOffsetNs();
225*90c8c64dSAndroid Build Coastguard Worker  }
226*90c8c64dSAndroid Build Coastguard Worker
227*90c8c64dSAndroid Build Coastguard Worker  getLatestRealToBootTimeOffset(
228*90c8c64dSAndroid Build Coastguard Worker    parsers: Array<Parser<object>>,
229*90c8c64dSAndroid Build Coastguard Worker  ): bigint | undefined {
230*90c8c64dSAndroid Build Coastguard Worker    const p = parsers
231*90c8c64dSAndroid Build Coastguard Worker      .filter((offset) => offset.getRealToBootTimeOffsetNs() !== undefined)
232*90c8c64dSAndroid Build Coastguard Worker      .sort((a, b) => {
233*90c8c64dSAndroid Build Coastguard Worker        return Number(
234*90c8c64dSAndroid Build Coastguard Worker          (a.getRealToBootTimeOffsetNs() ?? 0n) -
235*90c8c64dSAndroid Build Coastguard Worker            (b.getRealToBootTimeOffsetNs() ?? 0n),
236*90c8c64dSAndroid Build Coastguard Worker        );
237*90c8c64dSAndroid Build Coastguard Worker      })
238*90c8c64dSAndroid Build Coastguard Worker      .at(-1);
239*90c8c64dSAndroid Build Coastguard Worker    return p?.getRealToBootTimeOffsetNs();
240*90c8c64dSAndroid Build Coastguard Worker  }
241*90c8c64dSAndroid Build Coastguard Worker
242*90c8c64dSAndroid Build Coastguard Worker  private addLegacyParsers(parsers: FileAndParser[]) {
243*90c8c64dSAndroid Build Coastguard Worker    const legacyParsersBeingLoaded = new Map<TraceType, Parser<object>>();
244*90c8c64dSAndroid Build Coastguard Worker
245*90c8c64dSAndroid Build Coastguard Worker    parsers.forEach((fileAndParser) => {
246*90c8c64dSAndroid Build Coastguard Worker      const {parser} = fileAndParser;
247*90c8c64dSAndroid Build Coastguard Worker      if (this.shouldUseLegacyParser(parser)) {
248*90c8c64dSAndroid Build Coastguard Worker        legacyParsersBeingLoaded.set(parser.getTraceType(), parser);
249*90c8c64dSAndroid Build Coastguard Worker        this.legacyParsers.push(fileAndParser);
250*90c8c64dSAndroid Build Coastguard Worker      }
251*90c8c64dSAndroid Build Coastguard Worker    });
252*90c8c64dSAndroid Build Coastguard Worker  }
253*90c8c64dSAndroid Build Coastguard Worker
254*90c8c64dSAndroid Build Coastguard Worker  private addPerfettoParsers({file, parsers}: FileAndParsers) {
255*90c8c64dSAndroid Build Coastguard Worker    // We currently run only one Perfetto TP WebWorker at a time, so Perfetto parsers previously
256*90c8c64dSAndroid Build Coastguard Worker    // loaded are now invalid and must be removed (previous WebWorker is not running anymore).
257*90c8c64dSAndroid Build Coastguard Worker    this.perfettoParsers = [];
258*90c8c64dSAndroid Build Coastguard Worker
259*90c8c64dSAndroid Build Coastguard Worker    parsers.forEach((parser) => {
260*90c8c64dSAndroid Build Coastguard Worker      this.perfettoParsers.push(new FileAndParser(file, parser));
261*90c8c64dSAndroid Build Coastguard Worker
262*90c8c64dSAndroid Build Coastguard Worker      // While transitioning to the Perfetto format, devices might still have old legacy trace files
263*90c8c64dSAndroid Build Coastguard Worker      // dangling in the disk that get automatically included into bugreports. Hence, Perfetto
264*90c8c64dSAndroid Build Coastguard Worker      // parsers must always override legacy ones so that dangling legacy files are ignored.
265*90c8c64dSAndroid Build Coastguard Worker      this.legacyParsers = this.legacyParsers.filter((fileAndParser) => {
266*90c8c64dSAndroid Build Coastguard Worker        const isOverriddenByPerfettoParser =
267*90c8c64dSAndroid Build Coastguard Worker          fileAndParser.parser.getTraceType() === parser.getTraceType();
268*90c8c64dSAndroid Build Coastguard Worker        if (isOverriddenByPerfettoParser) {
269*90c8c64dSAndroid Build Coastguard Worker          UserNotifier.add(
270*90c8c64dSAndroid Build Coastguard Worker            new TraceOverridden(fileAndParser.parser.getDescriptors().join()),
271*90c8c64dSAndroid Build Coastguard Worker          );
272*90c8c64dSAndroid Build Coastguard Worker        }
273*90c8c64dSAndroid Build Coastguard Worker        return !isOverriddenByPerfettoParser;
274*90c8c64dSAndroid Build Coastguard Worker      });
275*90c8c64dSAndroid Build Coastguard Worker    });
276*90c8c64dSAndroid Build Coastguard Worker  }
277*90c8c64dSAndroid Build Coastguard Worker
278*90c8c64dSAndroid Build Coastguard Worker  private shouldUseLegacyParser(newParser: Parser<object>): boolean {
279*90c8c64dSAndroid Build Coastguard Worker    // While transitioning to the Perfetto format, devices might still have old legacy trace files
280*90c8c64dSAndroid Build Coastguard Worker    // dangling in the disk that get automatically included into bugreports. Hence, Perfetto parsers
281*90c8c64dSAndroid Build Coastguard Worker    // must always override legacy ones so that dangling legacy files are ignored.
282*90c8c64dSAndroid Build Coastguard Worker    const isOverriddenByPerfettoParser = this.perfettoParsers.some(
283*90c8c64dSAndroid Build Coastguard Worker      (fileAndParser) =>
284*90c8c64dSAndroid Build Coastguard Worker        fileAndParser.parser.getTraceType() === newParser.getTraceType(),
285*90c8c64dSAndroid Build Coastguard Worker    );
286*90c8c64dSAndroid Build Coastguard Worker    if (isOverriddenByPerfettoParser) {
287*90c8c64dSAndroid Build Coastguard Worker      UserNotifier.add(new TraceOverridden(newParser.getDescriptors().join()));
288*90c8c64dSAndroid Build Coastguard Worker      return false;
289*90c8c64dSAndroid Build Coastguard Worker    }
290*90c8c64dSAndroid Build Coastguard Worker
291*90c8c64dSAndroid Build Coastguard Worker    return true;
292*90c8c64dSAndroid Build Coastguard Worker  }
293*90c8c64dSAndroid Build Coastguard Worker
294*90c8c64dSAndroid Build Coastguard Worker  private filterOutLegacyParsersWithOldData(
295*90c8c64dSAndroid Build Coastguard Worker    newLegacyParsers: FileAndParser[],
296*90c8c64dSAndroid Build Coastguard Worker  ): FileAndParser[] {
297*90c8c64dSAndroid Build Coastguard Worker    let allParsers = [
298*90c8c64dSAndroid Build Coastguard Worker      ...newLegacyParsers,
299*90c8c64dSAndroid Build Coastguard Worker      ...this.legacyParsers.values(),
300*90c8c64dSAndroid Build Coastguard Worker      ...this.perfettoParsers.values(),
301*90c8c64dSAndroid Build Coastguard Worker    ];
302*90c8c64dSAndroid Build Coastguard Worker
303*90c8c64dSAndroid Build Coastguard Worker    const latestMonotonicOffset = this.getLatestRealToMonotonicOffset(
304*90c8c64dSAndroid Build Coastguard Worker      allParsers.map(({parser, file}) => parser),
305*90c8c64dSAndroid Build Coastguard Worker    );
306*90c8c64dSAndroid Build Coastguard Worker    const latestBootTimeOffset = this.getLatestRealToBootTimeOffset(
307*90c8c64dSAndroid Build Coastguard Worker      allParsers.map(({parser, file}) => parser),
308*90c8c64dSAndroid Build Coastguard Worker    );
309*90c8c64dSAndroid Build Coastguard Worker
310*90c8c64dSAndroid Build Coastguard Worker    newLegacyParsers = newLegacyParsers.filter(({parser, file}) => {
311*90c8c64dSAndroid Build Coastguard Worker      const monotonicOffset = parser.getRealToMonotonicTimeOffsetNs();
312*90c8c64dSAndroid Build Coastguard Worker      if (monotonicOffset && latestMonotonicOffset) {
313*90c8c64dSAndroid Build Coastguard Worker        const isOldData =
314*90c8c64dSAndroid Build Coastguard Worker          Math.abs(Number(monotonicOffset - latestMonotonicOffset)) >
315*90c8c64dSAndroid Build Coastguard Worker          LoadedParsers.MAX_ALLOWED_TIME_GAP_BETWEEN_RTE_OFFSET;
316*90c8c64dSAndroid Build Coastguard Worker        if (isOldData) {
317*90c8c64dSAndroid Build Coastguard Worker          UserNotifier.add(new TraceHasOldData(file.getDescriptor()));
318*90c8c64dSAndroid Build Coastguard Worker          return false;
319*90c8c64dSAndroid Build Coastguard Worker        }
320*90c8c64dSAndroid Build Coastguard Worker      }
321*90c8c64dSAndroid Build Coastguard Worker
322*90c8c64dSAndroid Build Coastguard Worker      const bootTimeOffset = parser.getRealToBootTimeOffsetNs();
323*90c8c64dSAndroid Build Coastguard Worker      if (bootTimeOffset && latestBootTimeOffset) {
324*90c8c64dSAndroid Build Coastguard Worker        const isOldData =
325*90c8c64dSAndroid Build Coastguard Worker          Math.abs(Number(bootTimeOffset - latestBootTimeOffset)) >
326*90c8c64dSAndroid Build Coastguard Worker          LoadedParsers.MAX_ALLOWED_TIME_GAP_BETWEEN_RTE_OFFSET;
327*90c8c64dSAndroid Build Coastguard Worker        if (isOldData) {
328*90c8c64dSAndroid Build Coastguard Worker          UserNotifier.add(new TraceHasOldData(file.getDescriptor()));
329*90c8c64dSAndroid Build Coastguard Worker          return false;
330*90c8c64dSAndroid Build Coastguard Worker        }
331*90c8c64dSAndroid Build Coastguard Worker      }
332*90c8c64dSAndroid Build Coastguard Worker
333*90c8c64dSAndroid Build Coastguard Worker      return true;
334*90c8c64dSAndroid Build Coastguard Worker    });
335*90c8c64dSAndroid Build Coastguard Worker
336*90c8c64dSAndroid Build Coastguard Worker    allParsers = [
337*90c8c64dSAndroid Build Coastguard Worker      ...newLegacyParsers,
338*90c8c64dSAndroid Build Coastguard Worker      ...this.legacyParsers.values(),
339*90c8c64dSAndroid Build Coastguard Worker      ...this.perfettoParsers.values(),
340*90c8c64dSAndroid Build Coastguard Worker    ];
341*90c8c64dSAndroid Build Coastguard Worker
342*90c8c64dSAndroid Build Coastguard Worker    const timeRanges = allParsers
343*90c8c64dSAndroid Build Coastguard Worker      .map(({parser}) => {
344*90c8c64dSAndroid Build Coastguard Worker        const timestamps = parser.getTimestamps();
345*90c8c64dSAndroid Build Coastguard Worker        if (!timestamps || timestamps.length === 0) {
346*90c8c64dSAndroid Build Coastguard Worker          return undefined;
347*90c8c64dSAndroid Build Coastguard Worker        }
348*90c8c64dSAndroid Build Coastguard Worker        return new TimeRange(timestamps[0], timestamps[timestamps.length - 1]);
349*90c8c64dSAndroid Build Coastguard Worker      })
350*90c8c64dSAndroid Build Coastguard Worker      .filter((range) => range !== undefined) as TimeRange[];
351*90c8c64dSAndroid Build Coastguard Worker
352*90c8c64dSAndroid Build Coastguard Worker    const timeGap = this.findLastTimeGapAboveThreshold(timeRanges);
353*90c8c64dSAndroid Build Coastguard Worker    if (!timeGap) {
354*90c8c64dSAndroid Build Coastguard Worker      return newLegacyParsers;
355*90c8c64dSAndroid Build Coastguard Worker    }
356*90c8c64dSAndroid Build Coastguard Worker
357*90c8c64dSAndroid Build Coastguard Worker    return newLegacyParsers.filter(({parser, file}) => {
358*90c8c64dSAndroid Build Coastguard Worker      // Only Shell Transition data used to set timestamps of merged Transition trace,
359*90c8c64dSAndroid Build Coastguard Worker      // so WM Transition data should not be considered by "old data" policy
360*90c8c64dSAndroid Build Coastguard Worker      if (parser.getTraceType() === TraceType.WM_TRANSITION) {
361*90c8c64dSAndroid Build Coastguard Worker        return true;
362*90c8c64dSAndroid Build Coastguard Worker      }
363*90c8c64dSAndroid Build Coastguard Worker
364*90c8c64dSAndroid Build Coastguard Worker      let timestamps = parser.getTimestamps();
365*90c8c64dSAndroid Build Coastguard Worker      if (!this.hasValidTimestamps(timestamps)) {
366*90c8c64dSAndroid Build Coastguard Worker        return true;
367*90c8c64dSAndroid Build Coastguard Worker      }
368*90c8c64dSAndroid Build Coastguard Worker      timestamps = assertDefined(timestamps);
369*90c8c64dSAndroid Build Coastguard Worker
370*90c8c64dSAndroid Build Coastguard Worker      const endTimestamp = timestamps[timestamps.length - 1];
371*90c8c64dSAndroid Build Coastguard Worker      const isOldData = endTimestamp.getValueNs() <= timeGap.from.getValueNs();
372*90c8c64dSAndroid Build Coastguard Worker      if (isOldData) {
373*90c8c64dSAndroid Build Coastguard Worker        UserNotifier.add(new TraceHasOldData(file.getDescriptor(), timeGap));
374*90c8c64dSAndroid Build Coastguard Worker        return false;
375*90c8c64dSAndroid Build Coastguard Worker      }
376*90c8c64dSAndroid Build Coastguard Worker
377*90c8c64dSAndroid Build Coastguard Worker      return true;
378*90c8c64dSAndroid Build Coastguard Worker    });
379*90c8c64dSAndroid Build Coastguard Worker  }
380*90c8c64dSAndroid Build Coastguard Worker
381*90c8c64dSAndroid Build Coastguard Worker  private filterScreenshotParsersIfRequired(
382*90c8c64dSAndroid Build Coastguard Worker    newLegacyParsers: FileAndParser[],
383*90c8c64dSAndroid Build Coastguard Worker  ): FileAndParser[] {
384*90c8c64dSAndroid Build Coastguard Worker    const hasOldScreenRecordingParsers = this.legacyParsers.some(
385*90c8c64dSAndroid Build Coastguard Worker      (entry) => entry.parser.getTraceType() === TraceType.SCREEN_RECORDING,
386*90c8c64dSAndroid Build Coastguard Worker    );
387*90c8c64dSAndroid Build Coastguard Worker    const hasNewScreenRecordingParsers = newLegacyParsers.some(
388*90c8c64dSAndroid Build Coastguard Worker      (entry) => entry.parser.getTraceType() === TraceType.SCREEN_RECORDING,
389*90c8c64dSAndroid Build Coastguard Worker    );
390*90c8c64dSAndroid Build Coastguard Worker    const hasScreenRecordingParsers =
391*90c8c64dSAndroid Build Coastguard Worker      hasOldScreenRecordingParsers || hasNewScreenRecordingParsers;
392*90c8c64dSAndroid Build Coastguard Worker
393*90c8c64dSAndroid Build Coastguard Worker    if (!hasScreenRecordingParsers) {
394*90c8c64dSAndroid Build Coastguard Worker      return newLegacyParsers;
395*90c8c64dSAndroid Build Coastguard Worker    }
396*90c8c64dSAndroid Build Coastguard Worker
397*90c8c64dSAndroid Build Coastguard Worker    const oldScreenshotParsers = this.legacyParsers.filter(
398*90c8c64dSAndroid Build Coastguard Worker      (fileAndParser) =>
399*90c8c64dSAndroid Build Coastguard Worker        fileAndParser.parser.getTraceType() === TraceType.SCREENSHOT,
400*90c8c64dSAndroid Build Coastguard Worker    );
401*90c8c64dSAndroid Build Coastguard Worker    const newScreenshotParsers = newLegacyParsers.filter(
402*90c8c64dSAndroid Build Coastguard Worker      (fileAndParser) =>
403*90c8c64dSAndroid Build Coastguard Worker        fileAndParser.parser.getTraceType() === TraceType.SCREENSHOT,
404*90c8c64dSAndroid Build Coastguard Worker    );
405*90c8c64dSAndroid Build Coastguard Worker
406*90c8c64dSAndroid Build Coastguard Worker    oldScreenshotParsers.forEach((fileAndParser) => {
407*90c8c64dSAndroid Build Coastguard Worker      UserNotifier.add(
408*90c8c64dSAndroid Build Coastguard Worker        new TraceOverridden(
409*90c8c64dSAndroid Build Coastguard Worker          fileAndParser.parser.getDescriptors().join(),
410*90c8c64dSAndroid Build Coastguard Worker          TraceType.SCREEN_RECORDING,
411*90c8c64dSAndroid Build Coastguard Worker        ),
412*90c8c64dSAndroid Build Coastguard Worker      );
413*90c8c64dSAndroid Build Coastguard Worker      this.remove(fileAndParser.parser);
414*90c8c64dSAndroid Build Coastguard Worker    });
415*90c8c64dSAndroid Build Coastguard Worker
416*90c8c64dSAndroid Build Coastguard Worker    newScreenshotParsers.forEach((newScreenshotParser) => {
417*90c8c64dSAndroid Build Coastguard Worker      UserNotifier.add(
418*90c8c64dSAndroid Build Coastguard Worker        new TraceOverridden(
419*90c8c64dSAndroid Build Coastguard Worker          newScreenshotParser.parser.getDescriptors().join(),
420*90c8c64dSAndroid Build Coastguard Worker          TraceType.SCREEN_RECORDING,
421*90c8c64dSAndroid Build Coastguard Worker        ),
422*90c8c64dSAndroid Build Coastguard Worker      );
423*90c8c64dSAndroid Build Coastguard Worker    });
424*90c8c64dSAndroid Build Coastguard Worker
425*90c8c64dSAndroid Build Coastguard Worker    return newLegacyParsers.filter(
426*90c8c64dSAndroid Build Coastguard Worker      (fileAndParser) =>
427*90c8c64dSAndroid Build Coastguard Worker        fileAndParser.parser.getTraceType() !== TraceType.SCREENSHOT,
428*90c8c64dSAndroid Build Coastguard Worker    );
429*90c8c64dSAndroid Build Coastguard Worker  }
430*90c8c64dSAndroid Build Coastguard Worker
431*90c8c64dSAndroid Build Coastguard Worker  private filterOutParsersWithoutOffsetsIfRequired(
432*90c8c64dSAndroid Build Coastguard Worker    newLegacyParsers: FileAndParser[],
433*90c8c64dSAndroid Build Coastguard Worker    perfettoParsers: FileAndParsers | undefined,
434*90c8c64dSAndroid Build Coastguard Worker  ): FileAndParser[] {
435*90c8c64dSAndroid Build Coastguard Worker    const hasParserWithOffset =
436*90c8c64dSAndroid Build Coastguard Worker      perfettoParsers ||
437*90c8c64dSAndroid Build Coastguard Worker      newLegacyParsers.find(({parser, file}) => {
438*90c8c64dSAndroid Build Coastguard Worker        return (
439*90c8c64dSAndroid Build Coastguard Worker          parser.getRealToBootTimeOffsetNs() !== undefined ||
440*90c8c64dSAndroid Build Coastguard Worker          parser.getRealToMonotonicTimeOffsetNs() !== undefined
441*90c8c64dSAndroid Build Coastguard Worker        );
442*90c8c64dSAndroid Build Coastguard Worker      });
443*90c8c64dSAndroid Build Coastguard Worker    const hasParserWithoutOffset = newLegacyParsers.find(({parser, file}) => {
444*90c8c64dSAndroid Build Coastguard Worker      const timestamps = parser.getTimestamps();
445*90c8c64dSAndroid Build Coastguard Worker      return (
446*90c8c64dSAndroid Build Coastguard Worker        this.hasValidTimestamps(timestamps) &&
447*90c8c64dSAndroid Build Coastguard Worker        parser.getRealToBootTimeOffsetNs() === undefined &&
448*90c8c64dSAndroid Build Coastguard Worker        parser.getRealToMonotonicTimeOffsetNs() === undefined
449*90c8c64dSAndroid Build Coastguard Worker      );
450*90c8c64dSAndroid Build Coastguard Worker    });
451*90c8c64dSAndroid Build Coastguard Worker
452*90c8c64dSAndroid Build Coastguard Worker    if (hasParserWithOffset && hasParserWithoutOffset) {
453*90c8c64dSAndroid Build Coastguard Worker      return newLegacyParsers.filter(({parser, file}) => {
454*90c8c64dSAndroid Build Coastguard Worker        if (
455*90c8c64dSAndroid Build Coastguard Worker          LoadedParsers.REAL_TIME_TRACES_WITHOUT_RTE_OFFSET.some(
456*90c8c64dSAndroid Build Coastguard Worker            (traceType) => parser.getTraceType() === traceType,
457*90c8c64dSAndroid Build Coastguard Worker          )
458*90c8c64dSAndroid Build Coastguard Worker        ) {
459*90c8c64dSAndroid Build Coastguard Worker          return true;
460*90c8c64dSAndroid Build Coastguard Worker        }
461*90c8c64dSAndroid Build Coastguard Worker        const hasOffset =
462*90c8c64dSAndroid Build Coastguard Worker          parser.getRealToMonotonicTimeOffsetNs() !== undefined ||
463*90c8c64dSAndroid Build Coastguard Worker          parser.getRealToBootTimeOffsetNs() !== undefined;
464*90c8c64dSAndroid Build Coastguard Worker        if (!hasOffset) {
465*90c8c64dSAndroid Build Coastguard Worker          UserNotifier.add(new TraceHasOldData(parser.getDescriptors().join()));
466*90c8c64dSAndroid Build Coastguard Worker        }
467*90c8c64dSAndroid Build Coastguard Worker        return hasOffset;
468*90c8c64dSAndroid Build Coastguard Worker      });
469*90c8c64dSAndroid Build Coastguard Worker    }
470*90c8c64dSAndroid Build Coastguard Worker
471*90c8c64dSAndroid Build Coastguard Worker    return newLegacyParsers;
472*90c8c64dSAndroid Build Coastguard Worker  }
473*90c8c64dSAndroid Build Coastguard Worker
474*90c8c64dSAndroid Build Coastguard Worker  private findLastTimeGapAboveThreshold(
475*90c8c64dSAndroid Build Coastguard Worker    ranges: readonly TimeRange[],
476*90c8c64dSAndroid Build Coastguard Worker  ): TimeRange | undefined {
477*90c8c64dSAndroid Build Coastguard Worker    const rangesSortedByEnd = ranges
478*90c8c64dSAndroid Build Coastguard Worker      .slice()
479*90c8c64dSAndroid Build Coastguard Worker      .sort((a, b) => (a.to.getValueNs() < b.to.getValueNs() ? -1 : +1));
480*90c8c64dSAndroid Build Coastguard Worker
481*90c8c64dSAndroid Build Coastguard Worker    for (let i = rangesSortedByEnd.length - 2; i >= 0; --i) {
482*90c8c64dSAndroid Build Coastguard Worker      const curr = rangesSortedByEnd[i];
483*90c8c64dSAndroid Build Coastguard Worker      const next = rangesSortedByEnd[i + 1];
484*90c8c64dSAndroid Build Coastguard Worker      const gap = next.from.getValueNs() - curr.to.getValueNs();
485*90c8c64dSAndroid Build Coastguard Worker      if (gap > LoadedParsers.MAX_ALLOWED_TIME_GAP_BETWEEN_TRACES_NS) {
486*90c8c64dSAndroid Build Coastguard Worker        return new TimeRange(curr.to, next.from);
487*90c8c64dSAndroid Build Coastguard Worker      }
488*90c8c64dSAndroid Build Coastguard Worker    }
489*90c8c64dSAndroid Build Coastguard Worker
490*90c8c64dSAndroid Build Coastguard Worker    return undefined;
491*90c8c64dSAndroid Build Coastguard Worker  }
492*90c8c64dSAndroid Build Coastguard Worker
493*90c8c64dSAndroid Build Coastguard Worker  private hasValidTimestamps(timestamps: Timestamp[] | undefined): boolean {
494*90c8c64dSAndroid Build Coastguard Worker    if (!timestamps || timestamps.length === 0) {
495*90c8c64dSAndroid Build Coastguard Worker      return false;
496*90c8c64dSAndroid Build Coastguard Worker    }
497*90c8c64dSAndroid Build Coastguard Worker
498*90c8c64dSAndroid Build Coastguard Worker    const isDump =
499*90c8c64dSAndroid Build Coastguard Worker      timestamps.length === 1 && timestamps[0].getValueNs() === INVALID_TIME_NS;
500*90c8c64dSAndroid Build Coastguard Worker    if (isDump) {
501*90c8c64dSAndroid Build Coastguard Worker      return false;
502*90c8c64dSAndroid Build Coastguard Worker    }
503*90c8c64dSAndroid Build Coastguard Worker    return true;
504*90c8c64dSAndroid Build Coastguard Worker  }
505*90c8c64dSAndroid Build Coastguard Worker}
506