| // Copyright (C) 2020 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| // Generation of SQL table references from C++ headers. |
| |
| 'use strict'; |
| |
| const fs = require('fs'); |
| const argv = require('yargs').argv |
| |
| // Removes \n due to 80col wrapping and preserves only end-of-sentence line |
| // breaks. |
| // TODO dedupe, this is copied from the other gen_proto file. |
| function singleLineComment(comment) { |
| comment = comment || ''; |
| comment = comment.trim(); |
| comment = comment.replaceAll('|', '\\|'); |
| comment = comment.replace(/\.\n/g, '<br>'); |
| comment = comment.replace(/\n/g, ' '); |
| return comment; |
| } |
| |
| function parseTablesInJson(filePath) { |
| return JSON.parse(fs.readFileSync(filePath, 'UTF8')); |
| } |
| |
| function genLink(table) { |
| return `[${table.name}](#${table.name})`; |
| } |
| |
| function tableToMarkdown(table) { |
| let md = `### ${table.name}\n\n`; |
| if (table.parent) { |
| md += `_Extends ${genLink(table.parent)}_\n\n`; |
| } |
| md += table.comment + '\n\n'; |
| md += 'Column | Type | Description\n'; |
| md += '------ | ---- | -----------\n'; |
| |
| let curTable = table; |
| while (curTable) { |
| if (curTable != table) { |
| md += `||_Columns inherited from_ ${genLink(curTable)}\n` |
| } |
| for (const col of Object.values(curTable.cols)) { |
| const type = col.type + (col.optional ? '<br>`optional`' : ''); |
| let description = col.comment; |
| if (col.joinTable) { |
| description += `\nJoinable with ` + |
| `[${col.joinTable}.${col.joinCol}](#${col.joinTable})`; |
| } |
| md += `${col.name} | ${type} | ${singleLineComment(description)}\n` |
| } |
| curTable = curTable.parent; |
| } |
| md += '\n\n'; |
| return md; |
| } |
| |
| function main() { |
| const outFile = argv['o']; |
| const jsonFile = argv['j']; |
| if (!jsonFile) { |
| console.error('Usage: -j tbls.json -[-o out.md]'); |
| process.exit(1); |
| } |
| |
| // Can be either a string (-j single) or an array (-j one -j two). |
| const jsonFiles = (jsonFile instanceof Array) ? jsonFile : [jsonFile]; |
| const jsonTables = |
| Array.prototype.concat(...jsonFiles.map(parseTablesInJson)); |
| |
| // Resolve parents. |
| const tablesIndex = {}; // 'TP_SCHED_SLICE_TABLE_DEF' -> table |
| const tablesByGroup = {}; // 'profilers' => [table1, table2] |
| const tablesCppName = {}; // 'StackProfileMappingTable' => table |
| const tablesByName = {}; // 'profile_mapping' => table |
| for (const table of jsonTables) { |
| tablesIndex[table.defMacro] = table; |
| if (tablesByGroup[table.tablegroup] === undefined) { |
| tablesByGroup[table.tablegroup] = []; |
| } |
| tablesCppName[table.cppClassName] = table; |
| tablesByName[table.name] = table; |
| tablesByGroup[table.tablegroup].push(table); |
| } |
| const tableGroups = Object.keys(tablesByGroup).sort((a, b) => { |
| const keys = {'Tracks': '1', 'Events': '2', 'Misc': 'z'}; |
| a = `${keys[a]}_${a}`; |
| b = `${keys[b]}_${b}`; |
| return a.localeCompare(b); |
| }); |
| |
| for (const table of jsonTables) { |
| if (table.parentDefName) { |
| table.parent = tablesIndex[table.parentDefName]; |
| } |
| } |
| |
| // Builds a graph of the tables' relationship that can be rendererd with |
| // mermaid.js. |
| let graph = '## Tables diagram\n'; |
| const mkLabel = (table) => `${table.defMacro}["${table.name}"]`; |
| for (const tableGroup of tableGroups) { |
| let graphEdges = ''; |
| let graphLinks = ''; |
| graph += `#### ${tableGroup} tables\n`; |
| graph += '```mermaid\ngraph TD\n'; |
| graph += ` subgraph ${tableGroup}\n`; |
| for (const table of tablesByGroup[tableGroup]) { |
| graph += ` ${mkLabel(table)}\n`; |
| graphLinks += ` click ${table.defMacro} "#${table.name}"\n` |
| if (table.parent) { |
| graphEdges += ` ${mkLabel(table)} --> ${mkLabel(table.parent)}\n` |
| } |
| |
| for (const col of Object.values(table.cols)) { |
| let refTable = undefined; |
| if (col.refTableCppName) { |
| refTable = tablesCppName[col.refTableCppName]; |
| } else if (col.joinTable) { |
| refTable = tablesByName[col.joinTable]; |
| if (!refTable) { |
| throw new Error(`Cannot find @joinable table ${col.joinTable}`); |
| } |
| } |
| if (!refTable) |
| continue; |
| graphEdges += |
| ` ${mkLabel(table)} -. ${col.name} .-> ${mkLabel(refTable)}\n` |
| graphLinks += ` click ${refTable.defMacro} "#${refTable.name}"\n` |
| } |
| } |
| graph += ` end\n`; |
| graph += graphEdges; |
| graph += graphLinks; |
| graph += '\n```\n'; |
| } |
| |
| let title = '# PerfettoSQL Prelude\n' |
| let md = title + graph; |
| for (const tableGroup of tableGroups) { |
| md += `## ${tableGroup}\n` |
| for (const table of tablesByGroup[tableGroup]) { |
| md += tableToMarkdown(table); |
| } |
| } |
| |
| if (outFile) { |
| fs.writeFileSync(outFile, md); |
| } else { |
| console.log(md); |
| } |
| process.exit(0); |
| } |
| |
| main(); |