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