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