xref: /aosp_15_r20/external/perfetto/tools/check_sql_metrics.py (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1*6dbdd20aSAndroid Build Coastguard Worker#!/usr/bin/env python3
2*6dbdd20aSAndroid Build Coastguard Worker# Copyright (C) 2021 The Android Open Source Project
3*6dbdd20aSAndroid Build Coastguard Worker#
4*6dbdd20aSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
5*6dbdd20aSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
6*6dbdd20aSAndroid Build Coastguard Worker# You may obtain a copy of the License at
7*6dbdd20aSAndroid Build Coastguard Worker#
8*6dbdd20aSAndroid Build Coastguard Worker#      http://www.apache.org/licenses/LICENSE-2.0
9*6dbdd20aSAndroid Build Coastguard Worker#
10*6dbdd20aSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
11*6dbdd20aSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
12*6dbdd20aSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*6dbdd20aSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
14*6dbdd20aSAndroid Build Coastguard Worker# limitations under the License.
15*6dbdd20aSAndroid Build Coastguard Worker
16*6dbdd20aSAndroid Build Coastguard Worker# This tool checks that every create (table|view) is prefixed by
17*6dbdd20aSAndroid Build Coastguard Worker# drop (table|view).
18*6dbdd20aSAndroid Build Coastguard Worker
19*6dbdd20aSAndroid Build Coastguard Workerfrom __future__ import absolute_import
20*6dbdd20aSAndroid Build Coastguard Workerfrom __future__ import division
21*6dbdd20aSAndroid Build Coastguard Workerfrom __future__ import print_function
22*6dbdd20aSAndroid Build Coastguard Worker
23*6dbdd20aSAndroid Build Coastguard Workerimport os
24*6dbdd20aSAndroid Build Coastguard Workerimport sys
25*6dbdd20aSAndroid Build Coastguard Workerfrom typing import Dict, Tuple, List
26*6dbdd20aSAndroid Build Coastguard Worker
27*6dbdd20aSAndroid Build Coastguard WorkerROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
28*6dbdd20aSAndroid Build Coastguard Workersys.path.append(os.path.join(ROOT_DIR))
29*6dbdd20aSAndroid Build Coastguard Worker
30*6dbdd20aSAndroid Build Coastguard Workerfrom python.generators.sql_processing.utils import check_banned_create_view_as
31*6dbdd20aSAndroid Build Coastguard Workerfrom python.generators.sql_processing.utils import check_banned_words
32*6dbdd20aSAndroid Build Coastguard Workerfrom python.generators.sql_processing.utils import match_pattern
33*6dbdd20aSAndroid Build Coastguard Workerfrom python.generators.sql_processing.utils import DROP_TABLE_VIEW_PATTERN
34*6dbdd20aSAndroid Build Coastguard Workerfrom python.generators.sql_processing.utils import CREATE_TABLE_VIEW_PATTERN
35*6dbdd20aSAndroid Build Coastguard Workerfrom python.generators.sql_processing.utils import CREATE_TABLE_AS_PATTERN
36*6dbdd20aSAndroid Build Coastguard Worker
37*6dbdd20aSAndroid Build Coastguard Worker
38*6dbdd20aSAndroid Build Coastguard Workerdef check_if_create_table_allowlisted(
39*6dbdd20aSAndroid Build Coastguard Worker    sql: str, filename: str, stdlib_path: str,
40*6dbdd20aSAndroid Build Coastguard Worker    allowlist: Dict[str, List[str]]) -> List[str]:
41*6dbdd20aSAndroid Build Coastguard Worker  errors = []
42*6dbdd20aSAndroid Build Coastguard Worker  for _, matches in match_pattern(CREATE_TABLE_AS_PATTERN, sql).items():
43*6dbdd20aSAndroid Build Coastguard Worker    name = matches[0]
44*6dbdd20aSAndroid Build Coastguard Worker    # Normalize paths before checking presence in the allowlist so it will
45*6dbdd20aSAndroid Build Coastguard Worker    # work on Windows for the Chrome stdlib presubmit.
46*6dbdd20aSAndroid Build Coastguard Worker    allowlist_normpath = dict(
47*6dbdd20aSAndroid Build Coastguard Worker        (os.path.normpath(path), tables) for path, tables in allowlist.items())
48*6dbdd20aSAndroid Build Coastguard Worker    allowlist_key = os.path.normpath(filename[len(stdlib_path):])
49*6dbdd20aSAndroid Build Coastguard Worker    if allowlist_key not in allowlist_normpath:
50*6dbdd20aSAndroid Build Coastguard Worker      errors.append(f"CREATE TABLE '{name}' is deprecated. "
51*6dbdd20aSAndroid Build Coastguard Worker                    "Use CREATE PERFETTO TABLE instead.\n"
52*6dbdd20aSAndroid Build Coastguard Worker                    f"Offending file: {filename}\n")
53*6dbdd20aSAndroid Build Coastguard Worker      continue
54*6dbdd20aSAndroid Build Coastguard Worker    if name not in allowlist_normpath[allowlist_key]:
55*6dbdd20aSAndroid Build Coastguard Worker      errors.append(
56*6dbdd20aSAndroid Build Coastguard Worker          f"Table '{name}' uses CREATE TABLE which is deprecated "
57*6dbdd20aSAndroid Build Coastguard Worker          "and this table is not allowlisted. Use CREATE PERFETTO TABLE.\n"
58*6dbdd20aSAndroid Build Coastguard Worker          f"Offending file: {filename}\n")
59*6dbdd20aSAndroid Build Coastguard Worker  return errors
60*6dbdd20aSAndroid Build Coastguard Worker
61*6dbdd20aSAndroid Build Coastguard Worker# Allowlist path are relative to the metrics root.
62*6dbdd20aSAndroid Build Coastguard WorkerCREATE_TABLE_ALLOWLIST = {
63*6dbdd20aSAndroid Build Coastguard Worker    ('/android'
64*6dbdd20aSAndroid Build Coastguard Worker     '/android_blocking_calls_cuj_metric.sql'): [
65*6dbdd20aSAndroid Build Coastguard Worker        'android_cujs', 'relevant_binder_calls_with_names',
66*6dbdd20aSAndroid Build Coastguard Worker        'android_blocking_calls_cuj_calls'
67*6dbdd20aSAndroid Build Coastguard Worker    ],
68*6dbdd20aSAndroid Build Coastguard Worker    ('/android'
69*6dbdd20aSAndroid Build Coastguard Worker     '/android_blocking_calls_unagg.sql'): [
70*6dbdd20aSAndroid Build Coastguard Worker        'filtered_processes_with_non_zero_blocking_calls', 'process_info',
71*6dbdd20aSAndroid Build Coastguard Worker        'android_blocking_calls_unagg_calls'
72*6dbdd20aSAndroid Build Coastguard Worker    ],
73*6dbdd20aSAndroid Build Coastguard Worker    '/android/jank/cujs.sql': ['android_jank_cuj'],
74*6dbdd20aSAndroid Build Coastguard Worker    '/chrome/gesture_flow_event.sql': [
75*6dbdd20aSAndroid Build Coastguard Worker        '{{prefix}}_latency_info_flow_step_filtered'
76*6dbdd20aSAndroid Build Coastguard Worker    ],
77*6dbdd20aSAndroid Build Coastguard Worker    '/chrome/gesture_jank.sql': [
78*6dbdd20aSAndroid Build Coastguard Worker        '{{prefix}}_jank_maybe_null_prev_and_next_without_precompute'
79*6dbdd20aSAndroid Build Coastguard Worker    ],
80*6dbdd20aSAndroid Build Coastguard Worker    '/experimental/frame_times.sql': ['DisplayCompositorPresentationEvents'],
81*6dbdd20aSAndroid Build Coastguard Worker}
82*6dbdd20aSAndroid Build Coastguard Worker
83*6dbdd20aSAndroid Build Coastguard Worker
84*6dbdd20aSAndroid Build Coastguard Workerdef match_create_table_pattern_to_dict(
85*6dbdd20aSAndroid Build Coastguard Worker    sql: str, pattern: str) -> Dict[str, Tuple[int, str]]:
86*6dbdd20aSAndroid Build Coastguard Worker  res = {}
87*6dbdd20aSAndroid Build Coastguard Worker  for line_num, matches in match_pattern(pattern, sql).items():
88*6dbdd20aSAndroid Build Coastguard Worker    res[matches[3]] = [line_num, str(matches[2])]
89*6dbdd20aSAndroid Build Coastguard Worker  return res
90*6dbdd20aSAndroid Build Coastguard Worker
91*6dbdd20aSAndroid Build Coastguard Worker
92*6dbdd20aSAndroid Build Coastguard Workerdef match_drop_view_pattern_to_dict(sql: str,
93*6dbdd20aSAndroid Build Coastguard Worker                                    pattern: str) -> Dict[str, Tuple[int, str]]:
94*6dbdd20aSAndroid Build Coastguard Worker  res = {}
95*6dbdd20aSAndroid Build Coastguard Worker  for line_num, matches in match_pattern(pattern, sql).items():
96*6dbdd20aSAndroid Build Coastguard Worker    res[matches[1]] = [line_num, str(matches[0])]
97*6dbdd20aSAndroid Build Coastguard Worker  return res
98*6dbdd20aSAndroid Build Coastguard Worker
99*6dbdd20aSAndroid Build Coastguard Worker
100*6dbdd20aSAndroid Build Coastguard Workerdef check(path: str, metrics_sources: str) -> List[str]:
101*6dbdd20aSAndroid Build Coastguard Worker  errors = []
102*6dbdd20aSAndroid Build Coastguard Worker  with open(path) as f:
103*6dbdd20aSAndroid Build Coastguard Worker    sql = f.read()
104*6dbdd20aSAndroid Build Coastguard Worker
105*6dbdd20aSAndroid Build Coastguard Worker  # Check that each function/macro is using "CREATE OR REPLACE"
106*6dbdd20aSAndroid Build Coastguard Worker  lines = [l.strip() for l in sql.split('\n')]
107*6dbdd20aSAndroid Build Coastguard Worker  for line in lines:
108*6dbdd20aSAndroid Build Coastguard Worker    if line.startswith('--'):
109*6dbdd20aSAndroid Build Coastguard Worker      continue
110*6dbdd20aSAndroid Build Coastguard Worker    if 'create perfetto function' in line.casefold():
111*6dbdd20aSAndroid Build Coastguard Worker      errors.append(
112*6dbdd20aSAndroid Build Coastguard Worker          f'Use "CREATE OR REPLACE PERFETTO FUNCTION" in Perfetto metrics, '
113*6dbdd20aSAndroid Build Coastguard Worker          f'to prevent the file from crashing if the metric is rerun.\n'
114*6dbdd20aSAndroid Build Coastguard Worker          f'Offending file: {path}\n')
115*6dbdd20aSAndroid Build Coastguard Worker    if 'create perfetto macro' in line.casefold():
116*6dbdd20aSAndroid Build Coastguard Worker      errors.append(
117*6dbdd20aSAndroid Build Coastguard Worker          f'Use "CREATE OR REPLACE PERFETTO MACRO" in Perfetto metrics, to '
118*6dbdd20aSAndroid Build Coastguard Worker          f'prevent the file from crashing if the metric is rerun.\n'
119*6dbdd20aSAndroid Build Coastguard Worker          f'Offending file: {path}\n')
120*6dbdd20aSAndroid Build Coastguard Worker
121*6dbdd20aSAndroid Build Coastguard Worker  # Check that CREATE VIEW/TABLE has a matching DROP VIEW/TABLE before it.
122*6dbdd20aSAndroid Build Coastguard Worker  create_table_view_dir = match_create_table_pattern_to_dict(
123*6dbdd20aSAndroid Build Coastguard Worker      sql, CREATE_TABLE_VIEW_PATTERN)
124*6dbdd20aSAndroid Build Coastguard Worker  drop_table_view_dir = match_drop_view_pattern_to_dict(
125*6dbdd20aSAndroid Build Coastguard Worker      sql, DROP_TABLE_VIEW_PATTERN)
126*6dbdd20aSAndroid Build Coastguard Worker  errors += check_if_create_table_allowlisted(
127*6dbdd20aSAndroid Build Coastguard Worker      sql,
128*6dbdd20aSAndroid Build Coastguard Worker      path.split(ROOT_DIR)[1],
129*6dbdd20aSAndroid Build Coastguard Worker      metrics_sources.split(ROOT_DIR)[1], CREATE_TABLE_ALLOWLIST)
130*6dbdd20aSAndroid Build Coastguard Worker  errors += check_banned_create_view_as(sql)
131*6dbdd20aSAndroid Build Coastguard Worker  for name, [line, type] in create_table_view_dir.items():
132*6dbdd20aSAndroid Build Coastguard Worker    if name not in drop_table_view_dir:
133*6dbdd20aSAndroid Build Coastguard Worker      errors.append(f'Missing DROP before CREATE {type.upper()} "{name}"\n'
134*6dbdd20aSAndroid Build Coastguard Worker                    f'Offending file: {path}\n')
135*6dbdd20aSAndroid Build Coastguard Worker      continue
136*6dbdd20aSAndroid Build Coastguard Worker    drop_line, drop_type = drop_table_view_dir[name]
137*6dbdd20aSAndroid Build Coastguard Worker    if drop_line > line:
138*6dbdd20aSAndroid Build Coastguard Worker      errors.append(f'DROP has to be before CREATE {type.upper()} "{name}"\n'
139*6dbdd20aSAndroid Build Coastguard Worker                    f'Offending file: {path}\n')
140*6dbdd20aSAndroid Build Coastguard Worker      continue
141*6dbdd20aSAndroid Build Coastguard Worker    if drop_type != type:
142*6dbdd20aSAndroid Build Coastguard Worker      errors.append(f'DROP type doesnt match CREATE {type.upper()} "{name}"\n'
143*6dbdd20aSAndroid Build Coastguard Worker                    f'Offending file: {path}\n')
144*6dbdd20aSAndroid Build Coastguard Worker
145*6dbdd20aSAndroid Build Coastguard Worker  errors += check_banned_words(sql)
146*6dbdd20aSAndroid Build Coastguard Worker  return errors
147*6dbdd20aSAndroid Build Coastguard Worker
148*6dbdd20aSAndroid Build Coastguard Worker
149*6dbdd20aSAndroid Build Coastguard Workerdef main():
150*6dbdd20aSAndroid Build Coastguard Worker  errors = []
151*6dbdd20aSAndroid Build Coastguard Worker  metrics_sources = os.path.join(ROOT_DIR, 'src', 'trace_processor', 'metrics',
152*6dbdd20aSAndroid Build Coastguard Worker                                 'sql')
153*6dbdd20aSAndroid Build Coastguard Worker  for root, _, files in os.walk(metrics_sources, topdown=True):
154*6dbdd20aSAndroid Build Coastguard Worker    for f in files:
155*6dbdd20aSAndroid Build Coastguard Worker      path = os.path.join(root, f)
156*6dbdd20aSAndroid Build Coastguard Worker      if path.endswith('.sql'):
157*6dbdd20aSAndroid Build Coastguard Worker        errors += check(path, metrics_sources)
158*6dbdd20aSAndroid Build Coastguard Worker
159*6dbdd20aSAndroid Build Coastguard Worker  if errors:
160*6dbdd20aSAndroid Build Coastguard Worker    sys.stderr.write("\n".join(errors))
161*6dbdd20aSAndroid Build Coastguard Worker    sys.stderr.write("\n")
162*6dbdd20aSAndroid Build Coastguard Worker  return 0 if not errors else 1
163*6dbdd20aSAndroid Build Coastguard Worker
164*6dbdd20aSAndroid Build Coastguard Worker
165*6dbdd20aSAndroid Build Coastguard Workerif __name__ == '__main__':
166*6dbdd20aSAndroid Build Coastguard Worker  sys.exit(main())
167