• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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