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 {assertDefined, assertTrue} from 'common/assert_utils';
18import {Timestamp} from 'common/time';
19import {ParserTimestampConverter} from 'common/timestamp_converter';
20import {CoarseVersion} from 'trace/coarse_version';
21import {
22  CustomQueryParamTypeMap,
23  CustomQueryParserResultTypeMap,
24  CustomQueryType,
25} from 'trace/custom_query';
26import {AbsoluteEntryIndex, EntriesRange} from 'trace/index_types';
27import {Parser} from 'trace/parser';
28import {TraceFile} from 'trace/trace_file';
29import {TRACE_INFO} from 'trace/trace_info';
30import {TraceType} from 'trace/trace_type';
31import {WasmEngineProxy} from 'trace_processor/wasm_engine_proxy';
32
33export abstract class AbstractParser<T> implements Parser<T> {
34  protected traceProcessor: WasmEngineProxy;
35  protected realToBootTimeOffsetNs?: bigint;
36  protected timestampConverter: ParserTimestampConverter;
37  protected entryIndexToRowIdMap: number[] = [];
38
39  private lengthEntries = 0;
40  private traceFile: TraceFile;
41  private bootTimeTimestampsNs: Array<bigint> = [];
42  private timestamps: Timestamp[] | undefined;
43
44  constructor(
45    traceFile: TraceFile,
46    traceProcessor: WasmEngineProxy,
47    timestampConverter: ParserTimestampConverter,
48  ) {
49    this.traceFile = traceFile;
50    this.traceProcessor = traceProcessor;
51    this.timestampConverter = timestampConverter;
52  }
53
54  async parse() {
55    const module = this.getStdLibModuleName();
56    if (module) {
57      await this.traceProcessor.query(`INCLUDE PERFETTO MODULE ${module};`);
58    }
59
60    this.entryIndexToRowIdMap = await this.buildEntryIndexToRowIdMap();
61    const rowBootTimeTimestampsNs = await this.queryRowBootTimeTimestamps();
62    this.bootTimeTimestampsNs = this.entryIndexToRowIdMap.map(
63      (rowId) => rowBootTimeTimestampsNs[rowId],
64    );
65    this.lengthEntries = this.bootTimeTimestampsNs.length;
66    assertTrue(
67      this.lengthEntries > 0,
68      () =>
69        `Perfetto trace has no ${TRACE_INFO[this.getTraceType()].name} entries`,
70    );
71
72    let lastNonZeroTimestamp: bigint | undefined;
73    for (let i = this.bootTimeTimestampsNs.length - 1; i >= 0; i--) {
74      if (this.bootTimeTimestampsNs[i] !== 0n) {
75        lastNonZeroTimestamp = this.bootTimeTimestampsNs[i];
76        break;
77      }
78    }
79    this.realToBootTimeOffsetNs = await this.queryRealToBootTimeOffset(
80      assertDefined(lastNonZeroTimestamp),
81    );
82  }
83
84  createTimestamps() {
85    this.timestamps = this.bootTimeTimestampsNs.map((ns) => {
86      return this.timestampConverter.makeTimestampFromBootTimeNs(ns);
87    });
88  }
89
90  getLengthEntries(): number {
91    return this.lengthEntries;
92  }
93
94  getTimestamps(): Timestamp[] | undefined {
95    return this.timestamps;
96  }
97
98  getCoarseVersion(): CoarseVersion {
99    return CoarseVersion.LATEST;
100  }
101
102  customQuery<Q extends CustomQueryType>(
103    type: Q,
104    entriesRange: EntriesRange,
105    param?: CustomQueryParamTypeMap[Q],
106  ): Promise<CustomQueryParserResultTypeMap[Q]> {
107    throw new Error('Not implemented');
108  }
109
110  getDescriptors(): string[] {
111    return [this.traceFile.getDescriptor()];
112  }
113
114  getRealToMonotonicTimeOffsetNs(): bigint | undefined {
115    return undefined;
116  }
117
118  getRealToBootTimeOffsetNs(): bigint | undefined {
119    return this.realToBootTimeOffsetNs;
120  }
121
122  protected async buildEntryIndexToRowIdMap(): Promise<AbsoluteEntryIndex[]> {
123    const sqlRowIdAndTimestamp = `
124     SELECT DISTINCT tbl.id AS id, tbl.ts
125     FROM ${this.getTableName()} AS tbl
126     ORDER BY tbl.ts;
127   `;
128    const result = await this.traceProcessor
129      .query(sqlRowIdAndTimestamp)
130      .waitAllRows();
131    const entryIndexToRowId: AbsoluteEntryIndex[] = [];
132    for (const it = result.iter({}); it.valid(); it.next()) {
133      const rowId = Number(it.get('id') as bigint);
134      entryIndexToRowId.push(rowId);
135    }
136    return entryIndexToRowId;
137  }
138
139  async queryRowBootTimeTimestamps(): Promise<Array<bigint>> {
140    const sql = `SELECT ts FROM ${this.getTableName()} ORDER BY id;`;
141    const result = await this.traceProcessor.query(sql).waitAllRows();
142    const timestamps: Array<bigint> = [];
143    for (const it = result.iter({}); it.valid(); it.next()) {
144      timestamps.push(it.get('ts') as bigint);
145    }
146    return timestamps;
147  }
148
149  // Query the real-to-boot time offset at the specified time
150  // (timestamp parameter).
151  // The timestamp parameter must be a non-zero timestamp queried/provided by TP,
152  // otherwise the TO_REALTIME() SQL function might return invalid values.
153  private async queryRealToBootTimeOffset(bootTimeNs: bigint): Promise<bigint> {
154    const sql = `
155      SELECT TO_REALTIME(${bootTimeNs}) as realtime;
156    `;
157
158    const result = await this.traceProcessor.query(sql).waitAllRows();
159    assertTrue(
160      result.numRows() === 1,
161      () => 'Failed to query realtime timestamp',
162    );
163
164    const real = result.iter({}).get('realtime') as bigint;
165    return real - bootTimeNs;
166  }
167
168  protected getStdLibModuleName(): string | undefined {
169    return undefined;
170  }
171
172  protected abstract getTableName(): string;
173  abstract getEntry(index: AbsoluteEntryIndex): Promise<T>;
174  abstract getTraceType(): TraceType;
175}
176