• 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 {EngineProxy} from './engine';
16import {STR} from './query_result';
17
18const CACHED_SCHEMAS = new WeakMap<EngineProxy, 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: EngineProxy, table: string): Promise<ColumnInfo[]> {
44  const result = await engine.query(`PRAGMA table_info(${table});`);
45  const it = result.iter({
46    name: STR,
47  });
48  const columns = [];
49  for (; it.valid(); it.next()) {
50    columns.push({name: it['name']});
51  }
52  return columns;
53}
54
55// Opinionated view on the schema of the given trace_processor instance
56// suitable for EventSets to use for query generation.
57export class DatabaseSchema {
58  private tableToKeys: Map<string, Set<string>>;
59
60  constructor(info: DatabaseInfo) {
61    this.tableToKeys = new Map();
62    for (const tableInfo of info.tables) {
63      const columns = new Set(tableInfo.columns.map((c) => c.name));
64      this.tableToKeys.set(tableInfo.name, columns);
65    }
66  }
67
68  // Return all the EventSet keys available for a given table. This
69  // includes the direct columns on the table (and all parent tables)
70  // as well as all direct and indirect joinable tables where the join
71  // is N:1 or 1:1. e.g. for the table thread_slice we also include
72  // the columns from thread, process, thread_track etc.
73  getKeys(tableName: string): Set<string> {
74    const columns = this.tableToKeys.get(tableName);
75    if (columns === undefined) {
76      throw new SchemaError(`Unknown table '${tableName}'`);
77    }
78    return columns;
79  }
80}
81
82// Deliberately not exported. Users should call getSchema below and
83// participate in cacheing.
84async function createSchema(engine: EngineProxy): Promise<DatabaseSchema> {
85  const tables: TableInfo[] = [];
86  const result = await engine.query(`SELECT name from perfetto_tables;`);
87  const it = result.iter({
88    name: STR,
89  });
90  for (; it.valid(); it.next()) {
91    const name = it['name'];
92    tables.push({
93      name,
94      columns: await getColumns(engine, name),
95    });
96  }
97
98  const database: DatabaseInfo = {
99    tables,
100  };
101
102  return new DatabaseSchema(database);
103}
104
105// Get the schema for the given engine (from the cache if possible).
106// The schemas are per-engine (i.e. they can't be statically determined
107// at build time) since we might be in httpd mode and not-running
108// against the version of trace_processor we build with.
109export async function getSchema(engine: EngineProxy): Promise<DatabaseSchema> {
110  const schema = CACHED_SCHEMAS.get(engine);
111  if (schema === undefined) {
112    const newSchema = await createSchema(engine);
113    CACHED_SCHEMAS.set(engine, newSchema);
114    return newSchema;
115  }
116  return schema;
117}
118