xref: /aosp_15_r20/external/perfetto/tools/check_sql_modules.py (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1#!/usr/bin/env python3
2# Copyright (C) 2022 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#      http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16# This tool checks that every SQL object created without prefix
17# '_' is documented with proper schema.
18
19import argparse
20from typing import List, Tuple, Dict
21import os
22import sys
23import re
24
25ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
26sys.path.append(os.path.join(ROOT_DIR))
27
28from python.generators.sql_processing.docs_parse import ParsedModule
29from python.generators.sql_processing.docs_parse import parse_file
30from python.generators.sql_processing.utils import check_banned_create_table_as
31from python.generators.sql_processing.utils import check_banned_create_view_as
32from python.generators.sql_processing.utils import check_banned_words
33from python.generators.sql_processing.utils import check_banned_drop
34from python.generators.sql_processing.utils import check_banned_include_all
35
36
37def main():
38  parser = argparse.ArgumentParser()
39  parser.add_argument(
40      '--stdlib-sources',
41      default=os.path.join(ROOT_DIR, "src", "trace_processor", "perfetto_sql",
42                           "stdlib"))
43  parser.add_argument(
44      '--verbose',
45      action='store_true',
46      default=False,
47      help='Enable additional logging')
48  parser.add_argument(
49      '--name-filter',
50      default=None,
51      type=str,
52      help='Filter the name of the modules to check (regex syntax)')
53
54  args = parser.parse_args()
55  modules: List[Tuple[str, str, ParsedModule]] = []
56  for root, _, files in os.walk(args.stdlib_sources, topdown=True):
57    for f in files:
58      path = os.path.join(root, f)
59      if not path.endswith(".sql"):
60        continue
61      rel_path = os.path.relpath(path, args.stdlib_sources)
62      if args.name_filter is not None:
63        pattern = re.compile(args.name_filter)
64        if not pattern.match(rel_path):
65          continue
66
67      with open(path, 'r') as f:
68        sql = f.read()
69
70      parsed = parse_file(rel_path, sql)
71
72      # Some modules (i.e. `deprecated`) should not be checked.
73      if not parsed:
74        continue
75
76      modules.append((path, sql, parsed))
77
78      if args.verbose:
79        obj_count = len(parsed.functions) + len(parsed.table_functions) + len(
80            parsed.table_views) + len(parsed.macros)
81        print(
82            f"Parsing '{rel_path}' ({obj_count} objects, "
83            f"{len(parsed.errors)} errors) - "
84            f"{len(parsed.functions)} functions, "
85            f"{len(parsed.table_functions)} table functions, "
86            f"{len(parsed.table_views)} tables/views, "
87            f"{len(parsed.macros)} macros.")
88
89  all_errors = 0
90  for path, sql, parsed in modules:
91    errors = []
92
93    # Check for banned statements.
94    lines = [l.strip() for l in sql.split('\n')]
95    for line in lines:
96      if line.startswith('--'):
97        continue
98      if 'run_metric' in line.casefold():
99        errors.append("RUN_METRIC is banned in standard library.")
100      if 'insert into' in line.casefold():
101        errors.append("INSERT INTO table is not allowed in standard library.")
102
103    # Validate includes.
104    package = parsed.package_name
105    for include in parsed.includes:
106      package = package.lower()
107      include_package = include.package.lower()
108
109      if (include_package == "common"):
110        errors.append(
111            "Common module has been deprecated in the standard library. "
112            "Please check `slices.with_context` for a replacement for "
113            "`common.slices` and `time.conversion` for replacement for "
114            "`common.timestamps`")
115
116      if (package != "viz" and include_package == "viz"):
117        errors.append("No modules can depend on 'viz' outside 'viz' package.")
118
119      if (package == "chrome" and include_package == "android"):
120        errors.append(
121            f"Modules from package 'chrome' can't include '{include.module}' "
122            f"from package 'android'")
123
124      if (package == "android" and include_package == "chrome"):
125        errors.append(
126            f"Modules from package 'android' can't include '{include.module}' "
127            f"from package 'chrome'")
128
129    errors += [
130        *parsed.errors, *check_banned_words(sql),
131        *check_banned_create_table_as(sql), *check_banned_create_view_as(sql),
132        *check_banned_include_all(sql), *check_banned_drop(sql)
133    ]
134
135    if errors:
136      sys.stderr.write(f"\nFound {len(errors)} errors in file "
137                       f"'{os.path.normpath(path)}':\n- ")
138      sys.stderr.write("\n- ".join(errors))
139      sys.stderr.write("\n\n")
140
141    all_errors += len(errors)
142
143  return 0 if not all_errors else 1
144
145
146if __name__ == "__main__":
147  sys.exit(main())
148