• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 reference from protos
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.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
63  // 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
69  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
129  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
137  // 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) {
267    let gaphEdges = '';
268    let gaphLinks = '';
269    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`;
274      gaphLinks += `  click ${table.defMacro} "#${table.name}"\n`
275      if (table.parent) {
276        gaphEdges += ` ${mkLabel(table)} --> ${mkLabel(table.parent)}\n`
277      }
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;
291        gaphEdges +=
292            `  ${mkLabel(table)} -. ${col.name} .-> ${mkLabel(refTable)}\n`
293        gaphLinks += `  click ${refTable.defMacro} "#${refTable.name}"\n`
294      }
295    }
296    graph += `  end\n`;
297    graph += gaphEdges;
298    graph += gaphLinks;
299    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();
319