• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2023 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import {Engine} from '../trace_processor/engine';
16import {STR} from '../trace_processor/query_result';
17
18const CACHED_SCHEMAS = new WeakMap<Engine, DatabaseSchema>();
19
20export class SchemaError extends Error {
21  constructor(message: string) {
22    super(message);
23  }
24}
25
26// POJO representing the table structure of trace_processor.
27// Exposed for testing.
28export interface DatabaseInfo {
29  tables: TableInfo[];
30}
31
32interface TableInfo {
33  name: string;
34  parent?: TableInfo;
35  columns: ColumnInfo[];
36}
37
38interface ColumnInfo {
39  name: string;
40}
41
42async function getColumns(
43  engine: Engine,
44  table: string,
45): Promise<ColumnInfo[]> {
46  const result = await engine.query(`PRAGMA table_info(${table});`);
47  const it = result.iter({
48    name: STR,
49  });
50  const columns = [];
51  for (; it.valid(); it.next()) {
52    columns.push({name: it['name']});
53  }
54  return columns;
55}
56
57// Opinionated view on the schema of the given trace_processor instance
58// suitable for EventSets to use for query generation.
59export class DatabaseSchema {
60  private tableToKeys: Map<string, Set<string>>;
61
62  constructor(info: DatabaseInfo) {
63    this.tableToKeys = new Map();
64    for (const tableInfo of info.tables) {
65      const columns = new Set(tableInfo.columns.map((c) => c.name));
66      this.tableToKeys.set(tableInfo.name, columns);
67    }
68  }
69
70  // Return all the EventSet keys available for a given table. This
71  // includes the direct columns on the table (and all parent tables)
72  // as well as all direct and indirect joinable tables where the join
73  // is N:1 or 1:1. e.g. for the table thread_slice we also include
74  // the columns from thread, process, thread_track etc.
75  getKeys(tableName: string): Set<string> {
76    const columns = this.tableToKeys.get(tableName);
77    if (columns === undefined) {
78      throw new SchemaError(`Unknown table '${tableName}'`);
79    }
80    return columns;
81  }
82}
83
84// Deliberately not exported. Users should call getSchema below and
85// participate in cacheing.
86async function createSchema(engine: Engine): Promise<DatabaseSchema> {
87  const tables: TableInfo[] = [];
88  const result = await engine.query(`SELECT name from perfetto_tables;`);
89  const it = result.iter({
90    name: STR,
91  });
92  for (; it.valid(); it.next()) {
93    const name = it['name'];
94    tables.push({
95      name,
96      columns: await getColumns(engine, name),
97    });
98  }
99
100  const database: DatabaseInfo = {
101    tables,
102  };
103
104  return new DatabaseSchema(database);
105}
106
107// Get the schema for the given engine (from the cache if possible).
108// The schemas are per-engine (i.e. they can't be statically determined
109// at build time) since we might be in httpd mode and not-running
110// against the version of trace_processor we build with.
111export async function getSchema(engine: Engine): Promise<DatabaseSchema> {
112  const schema = CACHED_SCHEMAS.get(engine);
113  if (schema === undefined) {
114    const newSchema = await createSchema(engine);
115    CACHED_SCHEMAS.set(engine, newSchema);
116    return newSchema;
117  }
118  return schema;
119}
120