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