1// Copyright (C) 2020 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 15// Generation of SQL table references from C++ headers. 16 17'use strict'; 18 19const fs = require('fs'); 20const argv = require('yargs').argv 21 22// Removes \n due to 80col wrapping and preserves only end-of-sentence line 23// breaks. 24// TODO dedupe, this is copied from the other gen_proto file. 25function singleLineComment(comment) { 26 comment = comment || ''; 27 comment = comment.trim(); 28 comment = comment.replaceAll('|', '\\|'); 29 comment = comment.replace(/\.\n/g, '<br>'); 30 comment = comment.replace(/\n/g, ' '); 31 return comment; 32} 33 34function parseTablesInJson(filePath) { 35 return JSON.parse(fs.readFileSync(filePath, 'UTF8')); 36} 37 38function genLink(table) { 39 return `[${table.name}](#${table.name})`; 40} 41 42function tableToMarkdown(table) { 43 let md = `### ${table.name}\n\n`; 44 if (table.parent) { 45 md += `_Extends ${genLink(table.parent)}_\n\n`; 46 } 47 md += table.comment + '\n\n'; 48 md += 'Column | Type | Description\n'; 49 md += '------ | ---- | -----------\n'; 50 51 let curTable = table; 52 while (curTable) { 53 if (curTable != table) { 54 md += `||_Columns inherited from_ ${genLink(curTable)}\n` 55 } 56 for (const col of Object.values(curTable.cols)) { 57 const type = col.type + (col.optional ? '<br>`optional`' : ''); 58 let description = col.comment; 59 if (col.joinTable) { 60 description += `\nJoinable with ` + 61 `[${col.joinTable}.${col.joinCol}](#${col.joinTable})`; 62 } 63 md += `${col.name} | ${type} | ${singleLineComment(description)}\n` 64 } 65 curTable = curTable.parent; 66 } 67 md += '\n\n'; 68 return md; 69} 70 71function main() { 72 const outFile = argv['o']; 73 const jsonFile = argv['j']; 74 if (!jsonFile) { 75 console.error('Usage: -j tbls.json -[-o out.md]'); 76 process.exit(1); 77 } 78 79 // Can be either a string (-j single) or an array (-j one -j two). 80 const jsonFiles = (jsonFile instanceof Array) ? jsonFile : [jsonFile]; 81 const jsonTables = 82 Array.prototype.concat(...jsonFiles.map(parseTablesInJson)); 83 84 // Resolve parents. 85 const tablesIndex = {}; // 'TP_SCHED_SLICE_TABLE_DEF' -> table 86 const tablesByGroup = {}; // 'profilers' => [table1, table2] 87 const tablesCppName = {}; // 'StackProfileMappingTable' => table 88 const tablesByName = {}; // 'profile_mapping' => table 89 for (const table of jsonTables) { 90 tablesIndex[table.defMacro] = table; 91 if (tablesByGroup[table.tablegroup] === undefined) { 92 tablesByGroup[table.tablegroup] = []; 93 } 94 tablesCppName[table.cppClassName] = table; 95 tablesByName[table.name] = table; 96 tablesByGroup[table.tablegroup].push(table); 97 } 98 const tableGroups = Object.keys(tablesByGroup).sort((a, b) => { 99 const keys = {'Tracks': '1', 'Events': '2', 'Misc': 'z'}; 100 a = `${keys[a]}_${a}`; 101 b = `${keys[b]}_${b}`; 102 return a.localeCompare(b); 103 }); 104 105 for (const table of jsonTables) { 106 if (table.parentDefName) { 107 table.parent = tablesIndex[table.parentDefName]; 108 } 109 } 110 111 // Builds a graph of the tables' relationship that can be rendererd with 112 // mermaid.js. 113 let graph = '## Tables diagram\n'; 114 const mkLabel = (table) => `${table.defMacro}["${table.name}"]`; 115 for (const tableGroup of tableGroups) { 116 let graphEdges = ''; 117 let graphLinks = ''; 118 graph += `#### ${tableGroup} tables\n`; 119 graph += '```mermaid\ngraph TD\n'; 120 graph += ` subgraph ${tableGroup}\n`; 121 for (const table of tablesByGroup[tableGroup]) { 122 graph += ` ${mkLabel(table)}\n`; 123 graphLinks += ` click ${table.defMacro} "#${table.name}"\n` 124 if (table.parent) { 125 graphEdges += ` ${mkLabel(table)} --> ${mkLabel(table.parent)}\n` 126 } 127 128 for (const col of Object.values(table.cols)) { 129 let refTable = undefined; 130 if (col.refTableCppName) { 131 refTable = tablesCppName[col.refTableCppName]; 132 } else if (col.joinTable) { 133 refTable = tablesByName[col.joinTable]; 134 if (!refTable) { 135 throw new Error(`Cannot find @joinable table ${col.joinTable}`); 136 } 137 } 138 if (!refTable) 139 continue; 140 graphEdges += 141 ` ${mkLabel(table)} -. ${col.name} .-> ${mkLabel(refTable)}\n` 142 graphLinks += ` click ${refTable.defMacro} "#${refTable.name}"\n` 143 } 144 } 145 graph += ` end\n`; 146 graph += graphEdges; 147 graph += graphLinks; 148 graph += '\n```\n'; 149 } 150 151 let title = '# PerfettoSQL Prelude\n' 152 let md = title + graph; 153 for (const tableGroup of tableGroups) { 154 md += `## ${tableGroup}\n` 155 for (const table of tablesByGroup[tableGroup]) { 156 md += tableToMarkdown(table); 157 } 158 } 159 160 if (outFile) { 161 fs.writeFileSync(outFile, md); 162 } else { 163 console.log(md); 164 } 165 process.exit(0); 166} 167 168main(); 169