1#!/usr/bin/env python3 2# Copyright (C) 2021 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 create (table|view) is prefixed by 17# drop (table|view). 18 19from __future__ import absolute_import 20from __future__ import division 21from __future__ import print_function 22 23import os 24import sys 25from typing import Dict, Tuple, List 26 27ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 28sys.path.append(os.path.join(ROOT_DIR)) 29 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 match_pattern 34from python.generators.sql_processing.utils import DROP_TABLE_VIEW_PATTERN 35from python.generators.sql_processing.utils import CREATE_TABLE_VIEW_PATTERN 36 37# Allowlist path are relative to the metrics root. 38CREATE_TABLE_ALLOWLIST = { 39 ('/android' 40 '/android_blocking_calls_cuj_metric.sql'): [ 41 'android_cujs', 'relevant_binder_calls_with_names', 42 'android_blocking_calls_cuj_calls' 43 ], 44 ('/android' 45 '/android_blocking_calls_unagg.sql'): [ 46 'filtered_processes_with_non_zero_blocking_calls', 'process_info', 47 'android_blocking_calls_unagg_calls' 48 ], 49 '/android/jank/cujs.sql': ['android_jank_cuj'], 50 '/chrome/gesture_flow_event.sql': [ 51 '{{prefix}}_latency_info_flow_step_filtered' 52 ], 53 '/chrome/gesture_jank.sql': [ 54 '{{prefix}}_jank_maybe_null_prev_and_next_without_precompute' 55 ], 56 '/experimental/frame_times.sql': ['DisplayCompositorPresentationEvents'], 57} 58 59 60def match_create_table_pattern_to_dict( 61 sql: str, pattern: str) -> Dict[str, Tuple[int, str]]: 62 res = {} 63 for line_num, matches in match_pattern(pattern, sql).items(): 64 res[matches[3]] = [line_num, str(matches[2])] 65 return res 66 67 68def match_drop_view_pattern_to_dict(sql: str, 69 pattern: str) -> Dict[str, Tuple[int, str]]: 70 res = {} 71 for line_num, matches in match_pattern(pattern, sql).items(): 72 res[matches[1]] = [line_num, str(matches[0])] 73 return res 74 75 76def check(path: str, metrics_sources: str) -> List[str]: 77 errors = [] 78 with open(path) as f: 79 sql = f.read() 80 81 # Check that each function/macro is using "CREATE OR REPLACE" 82 lines = [l.strip() for l in sql.split('\n')] 83 for line in lines: 84 if line.startswith('--'): 85 continue 86 if 'create perfetto function' in line.casefold(): 87 errors.append( 88 f'Use "CREATE OR REPLACE PERFETTO FUNCTION" in Perfetto metrics, ' 89 f'to prevent the file from crashing if the metric is rerun.\n' 90 f'Offending file: {path}\n') 91 if 'create perfetto macro' in line.casefold(): 92 errors.append( 93 f'Use "CREATE OR REPLACE PERFETTO MACRO" in Perfetto metrics, to ' 94 f'prevent the file from crashing if the metric is rerun.\n' 95 f'Offending file: {path}\n') 96 97 # Check that CREATE VIEW/TABLE has a matching DROP VIEW/TABLE before it. 98 create_table_view_dir = match_create_table_pattern_to_dict( 99 sql, CREATE_TABLE_VIEW_PATTERN) 100 drop_table_view_dir = match_drop_view_pattern_to_dict( 101 sql, DROP_TABLE_VIEW_PATTERN) 102 errors += check_banned_create_table_as(sql, 103 path.split(ROOT_DIR)[1], 104 metrics_sources.split(ROOT_DIR)[1], 105 CREATE_TABLE_ALLOWLIST) 106 errors += check_banned_create_view_as(sql, path.split(ROOT_DIR)[1]) 107 for name, [line, type] in create_table_view_dir.items(): 108 if name not in drop_table_view_dir: 109 errors.append(f'Missing DROP before CREATE {type.upper()} "{name}"\n' 110 f'Offending file: {path}\n') 111 continue 112 drop_line, drop_type = drop_table_view_dir[name] 113 if drop_line > line: 114 errors.append(f'DROP has to be before CREATE {type.upper()} "{name}"\n' 115 f'Offending file: {path}\n') 116 continue 117 if drop_type != type: 118 errors.append(f'DROP type doesnt match CREATE {type.upper()} "{name}"\n' 119 f'Offending file: {path}\n') 120 121 errors += check_banned_words(sql, path) 122 return errors 123 124 125def main(): 126 errors = [] 127 metrics_sources = os.path.join(ROOT_DIR, 'src', 'trace_processor', 'metrics', 128 'sql') 129 for root, _, files in os.walk(metrics_sources, topdown=True): 130 for f in files: 131 path = os.path.join(root, f) 132 if path.endswith('.sql'): 133 errors += check(path, metrics_sources) 134 135 if errors: 136 sys.stderr.write("\n".join(errors)) 137 sys.stderr.write("\n") 138 return 0 if not errors else 1 139 140 141if __name__ == '__main__': 142 sys.exit(main()) 143