blob: 5a85e534a5b021e4181a5f16de580f482cf90d9d [file] [log] [blame]
Primiano Tuccie70df3f2020-05-21 19:48:08 +01001// 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
Joseph Koshy8795e082022-10-10 16:11:00 +010015// Generation of SQL table references from C++ headers.
Primiano Tuccie70df3f2020-05-21 19:48:08 +010016
17'use strict';
18
19const fs = require('fs');
Primiano Tuccie70df3f2020-05-21 19:48:08 +010020const 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.replace(/\.\n/g, '<br>');
29 comment = comment.replace(/\n/g, ' ');
30 return comment;
31}
32
33// Returns an object describing the table as follows:
34// { name: 'HeapGraphObjectTable',
35// cols: [ {name: 'upid', type: 'uint32_t', optional: false },
36// {name: 'graph_sample_ts', type: 'int64_t', optional: false },
37function parseTableDef(tableDefName, tableDef) {
38 const tableDesc = {
39 name: '', // The SQL table name, e.g. stack_profile_mapping.
40 cppClassName: '', // e.g., StackProfileMappingTable.
41 defMacro: tableDefName, // e.g., PERFETTO_TP_STACK_PROFILE_MAPPING_DEF.
42 comment: '',
43 parent: undefined, // Will be filled afterwards in the resolution phase.
44 parentDefName: '', // e.g., PERFETTO_TP_STACK_PROFILE_MAPPING_DEF.
45 tablegroup: 'Misc', // From @tablegroup in comments.
46 cols: {},
47 };
48 const getOrCreateColumn = (name) => {
49 if (name in tableDesc.cols)
50 return tableDesc.cols[name];
51 tableDesc.cols[name] = {
52 name: name,
53 type: '',
54 comment: '',
55 optional: false,
56 refTableCppName: undefined,
57 joinTable: undefined,
58 joinCol: undefined,
59 };
60 return tableDesc.cols[name];
61 };
62
Lalit Magantiad2326d2020-06-08 19:15:15 +010063 // Reserve the id and type columns so they appear first in the column list
64 // They will only be kept in case this is a root table - otherwise they will
65 // be deleted below..
66 const id = getOrCreateColumn('id');
67 const type = getOrCreateColumn('type');
68
Primiano Tuccie70df3f2020-05-21 19:48:08 +010069 let lastColumn = undefined;
70 for (const line of tableDef.split('\n')) {
71 if (line.startsWith('#define'))
72 continue; // Skip the first line.
73 let m;
74 if (line.startsWith('//')) {
75 let comm = line.replace(/^\s*\/\/\s*/, '');
76 if (m = comm.match(/@tablegroup (.*)/)) {
77 tableDesc.tablegroup = m[1];
78 continue;
79 }
80 if (m = comm.match(/@name (\w+)/)) {
81 tableDesc.name = m[1];
82 continue;
83 }
84 if (m = comm.match(/@param\s+([^ ]+)\s*({\w+})?\s*(.*)/)) {
85 lastColumn = getOrCreateColumn(/*name=*/ m[1]);
86 lastColumn.type = (m[2] || '').replace(/(^{)|(}$)/g, '');
87 lastColumn.comment = m[3];
88 continue;
89 }
90 if (lastColumn === undefined) {
91 tableDesc.comment += `${comm}\n`;
92 } else {
93 lastColumn.comment = `${lastColumn.comment}${comm}\n`;
94 }
95 continue;
96 }
97 if (m = line.match(/^\s*NAME\((\w+)\s*,\s*"(\w+)"/)) {
98 tableDesc.cppClassName = m[1];
99 if (tableDesc.name === '') {
100 tableDesc.name = m[2]; // Set only if not overridden by @name.
101 }
102 continue;
103 }
104 if (m = line.match(/(PERFETTO_TP_ROOT_TABLE|PARENT)\((\w+)/)) {
105 if (m[1] === 'PARENT') {
106 tableDesc.parentDefName = m[2];
107 }
108 continue;
109 }
110 if (m = line.match(/^\s*C\(([^,]+)\s*,\s*(\w+)/)) {
111 const col = getOrCreateColumn(/*name=*/ m[2]);
112 col.type = m[1];
113 if (m = col.type.match(/Optional<(.*)>/)) {
114 col.type = m[1];
115 col.optional = true;
116 }
117 if (col.type === 'StringPool::Id') {
118 col.type = 'string';
119 }
120 const sep = col.type.indexOf('::');
121 if (sep > 0) {
122 col.refTableCppName = col.type.substr(0, sep);
123 }
124 continue;
125 }
126 throw new Error(`Cannot parse line "${line}" from ${tableDefName}`);
127 }
128
Lalit Magantiad2326d2020-06-08 19:15:15 +0100129 if (tableDesc.parentDefName === '') {
130 id.type = `${tableDesc.cppClassName}::Id`;
131 type.type = 'string';
132 } else {
133 delete tableDesc.cols['id'];
134 delete tableDesc.cols['type'];
135 }
136
Primiano Tuccie70df3f2020-05-21 19:48:08 +0100137 // Process {@joinable xxx} annotations.
138 const regex = /\s?\{@joinable\s*(\w+)\.(\w+)\s*\}/;
139 for (const col of Object.values(tableDesc.cols)) {
140 const m = col.comment.match(regex)
141 if (m) {
142 col.joinTable = m[1];
143 col.joinCol = m[2];
144 col.comment = col.comment.replace(regex, '');
145 }
146 }
147 return tableDesc;
148}
149
150
151function parseTablesInCppFile(filePath) {
152 const hdr = fs.readFileSync(filePath, 'UTF8');
153 const regex = /^\s*PERFETTO_TP_TABLE\((\w+)\)/mg;
154 let match = regex.exec(hdr);
155 const tables = [];
156 while (match != null) {
157 const tableDefName = match[1];
158 match = regex.exec(hdr);
159
160 // Now let's extract the table definition, that looks like this:
161 // // Some
162 // // Multiline
163 // // Comment
164 // #define PERFETTO_TP_STACK_PROFILE_FRAME_DEF(NAME, PARENT, C) \
165 // NAME(StackProfileFrameTable, "stack_profile_frame") \
166 // PERFETTO_TP_ROOT_TABLE(PARENT, C) \
167 // C(StringPool::Id, name) \
168 // C(StackProfileMappingTable::Id, mapping) \
169 // C(int64_t, rel_pc) \
170 // C(base::Optional<uint32_t>, symbol_set_id)
171 //
172 // Where PERFETTO_TP_STACK_PROFILE_FRAME_DEF is |tableDefName|.
173 let pattern = `(^[ ]*//.*\n)*`;
174 pattern += `^\s*#define\\s+${tableDefName}\\s*\\(`;
175 pattern += `(.*\\\\\\s*\n)+`;
176 pattern += `.+`;
177 const r = new RegExp(pattern, 'mi');
178 const tabMatch = r.exec(hdr);
179 if (!tabMatch) {
180 console.error(`could not find table ${tableDefName}`);
181 continue;
182 }
183 tables.push(parseTableDef(tableDefName, tabMatch[0]));
184 }
185 return tables;
186}
187
188
189function genLink(table) {
190 return `[${table.name}](#${table.name})`;
191}
192
193function tableToMarkdown(table) {
194 let md = `### ${table.name}\n\n`;
195 if (table.parent) {
196 md += `_Extends ${genLink(table.parent)}_\n\n`;
197 }
198 md += table.comment + '\n\n';
199 md += 'Column | Type | Description\n';
200 md += '------ | ---- | -----------\n';
201
202 let curTable = table;
203 while (curTable) {
204 if (curTable != table) {
205 md += `||_Columns inherited from_ ${genLink(curTable)}\n`
206 }
207 for (const col of Object.values(curTable.cols)) {
208 const type = col.type + (col.optional ? '<br>`optional`' : '');
209 let description = col.comment;
210 if (col.joinTable) {
211 description += `\nJoinable with ` +
212 `[${col.joinTable}.${col.joinCol}](#${col.joinTable})`;
213 }
214 md += `${col.name} | ${type} | ${singleLineComment(description)}\n`
215 }
216 curTable = curTable.parent;
217 }
218 md += '\n\n';
219 return md;
220}
221
222function main() {
223 const inFile = argv['i'];
224 const outFile = argv['o'];
225 if (!inFile) {
226 console.error('Usage: -i hdr1.h -i hdr2.h -[-o out.md]');
227 process.exit(1);
228 }
229
230 // Can be either a string (-i single) or an array (-i one -i two).
231 const inFiles = (inFile instanceof Array) ? inFile : [inFile];
232
233 const tables = Array.prototype.concat(...inFiles.map(parseTablesInCppFile));
234
235 // Resolve parents.
236 const tablesIndex = {}; // 'TP_SCHED_SLICE_TABLE_DEF' -> table
237 const tablesByGroup = {}; // 'profilers' => [table1, table2]
238 const tablesCppName = {}; // 'StackProfileMappingTable' => table
239 const tablesByName = {}; // 'profile_mapping' => table
240 for (const table of tables) {
241 tablesIndex[table.defMacro] = table;
242 if (tablesByGroup[table.tablegroup] === undefined) {
243 tablesByGroup[table.tablegroup] = [];
244 }
245 tablesCppName[table.cppClassName] = table;
246 tablesByName[table.name] = table;
247 tablesByGroup[table.tablegroup].push(table);
248 }
249 const tableGroups = Object.keys(tablesByGroup).sort((a, b) => {
250 const keys = {'Tracks': '1', 'Events': '2', 'Misc': 'z'};
251 a = `${keys[a]}_${a}`;
252 b = `${keys[b]}_${b}`;
253 return a.localeCompare(b);
254 });
255
256 for (const table of tables) {
257 if (table.parentDefName) {
258 table.parent = tablesIndex[table.parentDefName];
259 }
260 }
261
262 // Builds a graph of the tables' relationship that can be rendererd with
263 // mermaid.js.
264 let graph = '## Tables diagram\n';
265 const mkLabel = (table) => `${table.defMacro}["${table.name}"]`;
266 for (const tableGroup of tableGroups) {
Joseph Koshy8795e082022-10-10 16:11:00 +0100267 let graphEdges = '';
268 let graphLinks = '';
Primiano Tuccie70df3f2020-05-21 19:48:08 +0100269 graph += `#### ${tableGroup} tables\n`;
270 graph += '```mermaid\ngraph TD\n';
271 graph += ` subgraph ${tableGroup}\n`;
272 for (const table of tablesByGroup[tableGroup]) {
273 graph += ` ${mkLabel(table)}\n`;
Joseph Koshy8795e082022-10-10 16:11:00 +0100274 graphLinks += ` click ${table.defMacro} "#${table.name}"\n`
Primiano Tuccie70df3f2020-05-21 19:48:08 +0100275 if (table.parent) {
Joseph Koshy8795e082022-10-10 16:11:00 +0100276 graphEdges += ` ${mkLabel(table)} --> ${mkLabel(table.parent)}\n`
Primiano Tuccie70df3f2020-05-21 19:48:08 +0100277 }
278
279 for (const col of Object.values(table.cols)) {
280 let refTable = undefined;
281 if (col.refTableCppName) {
282 refTable = tablesCppName[col.refTableCppName];
283 } else if (col.joinTable) {
284 refTable = tablesByName[col.joinTable];
285 if (!refTable) {
286 throw new Error(`Cannot find @joinable table ${col.joinTable}`);
287 }
288 }
289 if (!refTable)
290 continue;
Joseph Koshy8795e082022-10-10 16:11:00 +0100291 graphEdges +=
Primiano Tuccie70df3f2020-05-21 19:48:08 +0100292 ` ${mkLabel(table)} -. ${col.name} .-> ${mkLabel(refTable)}\n`
Joseph Koshy8795e082022-10-10 16:11:00 +0100293 graphLinks += ` click ${refTable.defMacro} "#${refTable.name}"\n`
Primiano Tuccie70df3f2020-05-21 19:48:08 +0100294 }
295 }
296 graph += ` end\n`;
Joseph Koshy8795e082022-10-10 16:11:00 +0100297 graph += graphEdges;
298 graph += graphLinks;
Primiano Tuccie70df3f2020-05-21 19:48:08 +0100299 graph += '\n```\n';
300 }
301
302 let md = graph;
303 for (const tableGroup of tableGroups) {
304 md += `## ${tableGroup}\n`
305 for (const table of tablesByGroup[tableGroup]) {
306 md += tableToMarkdown(table);
307 }
308 }
309
310 if (outFile) {
311 fs.writeFileSync(outFile, md);
312 } else {
313 console.log(md);
314 }
315 process.exit(0);
316}
317
318main();