xref: /aosp_15_r20/external/perfetto/python/tools/check_ratchet.py (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1*6dbdd20aSAndroid Build Coastguard Worker#!/usr/bin/env python3
2*6dbdd20aSAndroid Build Coastguard Worker# Copyright (C) 2023 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 WorkerForce the reduction in use of some methods/types over time.
17*6dbdd20aSAndroid Build Coastguard WorkerOften a method ('LEGACY_registerTrackController') or a type ('any')
18*6dbdd20aSAndroid Build Coastguard Workergets replaced by a better alternative ('registerTrack', 'unknown') and
19*6dbdd20aSAndroid Build Coastguard Workerwe want to a. replace all existing uses, b. prevent the introduction of
20*6dbdd20aSAndroid Build Coastguard Workernew uses. This presubmit helps with both. It keeps a count of the
21*6dbdd20aSAndroid Build Coastguard Workernumber of instances of "FOO" in the codebase. At presubmit time we run
22*6dbdd20aSAndroid Build Coastguard Workerthe script. If the "FOO" count has gone up we encourage the author to
23*6dbdd20aSAndroid Build Coastguard Workeruse the alternative. If the "FOO" count has gone down we congratulate
24*6dbdd20aSAndroid Build Coastguard Workerthem and prompt them to reduce the expected count.
25*6dbdd20aSAndroid Build Coastguard WorkerSince the number of "FOO"s can only go down eventually they will all
26*6dbdd20aSAndroid Build Coastguard Workerbe gone - completing the migration.
27*6dbdd20aSAndroid Build Coastguard WorkerSee also https://qntm.org/ratchet.
28*6dbdd20aSAndroid Build Coastguard Worker"""
29*6dbdd20aSAndroid Build Coastguard Worker
30*6dbdd20aSAndroid Build Coastguard Workerimport sys
31*6dbdd20aSAndroid Build Coastguard Workerimport os
32*6dbdd20aSAndroid Build Coastguard Workerimport re
33*6dbdd20aSAndroid Build Coastguard Workerimport argparse
34*6dbdd20aSAndroid Build Coastguard Workerimport collections
35*6dbdd20aSAndroid Build Coastguard Workerimport dataclasses
36*6dbdd20aSAndroid Build Coastguard Worker
37*6dbdd20aSAndroid Build Coastguard Workerfrom dataclasses import dataclass
38*6dbdd20aSAndroid Build Coastguard Worker
39*6dbdd20aSAndroid Build Coastguard WorkerEXPECTED_ANY_COUNT = 50
40*6dbdd20aSAndroid Build Coastguard WorkerEXPECTED_RUN_METRIC_COUNT = 4
41*6dbdd20aSAndroid Build Coastguard Worker
42*6dbdd20aSAndroid Build Coastguard WorkerROOT_DIR = os.path.dirname(
43*6dbdd20aSAndroid Build Coastguard Worker    os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
44*6dbdd20aSAndroid Build Coastguard WorkerUI_SRC_DIR = os.path.join(ROOT_DIR, 'ui', 'src')
45*6dbdd20aSAndroid Build Coastguard Worker
46*6dbdd20aSAndroid Build Coastguard Worker
47*6dbdd20aSAndroid Build Coastguard Worker@dataclasses.dataclass
48*6dbdd20aSAndroid Build Coastguard Workerclass Check:
49*6dbdd20aSAndroid Build Coastguard Worker  regex: str
50*6dbdd20aSAndroid Build Coastguard Worker  expected_count: int
51*6dbdd20aSAndroid Build Coastguard Worker  expected_variable_name: str
52*6dbdd20aSAndroid Build Coastguard Worker  description: str
53*6dbdd20aSAndroid Build Coastguard Worker
54*6dbdd20aSAndroid Build Coastguard Worker
55*6dbdd20aSAndroid Build Coastguard WorkerCHECKS = [
56*6dbdd20aSAndroid Build Coastguard Worker    # 'any' is too generic. It will show up in many comments etc. So
57*6dbdd20aSAndroid Build Coastguard Worker    # instead of counting any directly we forbid it using eslint and count
58*6dbdd20aSAndroid Build Coastguard Worker    # the number of suppressions.
59*6dbdd20aSAndroid Build Coastguard Worker    Check(r"// eslint-disable-next-line @typescript-eslint/no-explicit-any",
60*6dbdd20aSAndroid Build Coastguard Worker          EXPECTED_ANY_COUNT, "EXPECTED_ANY_COUNT",
61*6dbdd20aSAndroid Build Coastguard Worker          "We should avoid using any whenever possible. Prefer unknown."),
62*6dbdd20aSAndroid Build Coastguard Worker    Check(
63*6dbdd20aSAndroid Build Coastguard Worker        r"RUN_METRIC\(", EXPECTED_RUN_METRIC_COUNT, "EXPECTED_RUN_METRIC_COUNT",
64*6dbdd20aSAndroid Build Coastguard Worker        "RUN_METRIC() is not a stable trace_processor API. Use a stdlib function or macro. See https://perfetto.dev/docs/analysis/perfetto-sql-syntax#defining-functions."
65*6dbdd20aSAndroid Build Coastguard Worker    ),
66*6dbdd20aSAndroid Build Coastguard Worker]
67*6dbdd20aSAndroid Build Coastguard Worker
68*6dbdd20aSAndroid Build Coastguard Worker
69*6dbdd20aSAndroid Build Coastguard Workerdef all_source_files():
70*6dbdd20aSAndroid Build Coastguard Worker  for root, dirs, files in os.walk(UI_SRC_DIR, followlinks=False):
71*6dbdd20aSAndroid Build Coastguard Worker    for name in files:
72*6dbdd20aSAndroid Build Coastguard Worker      if name.endswith('.ts'):
73*6dbdd20aSAndroid Build Coastguard Worker        yield os.path.join(root, name)
74*6dbdd20aSAndroid Build Coastguard Worker
75*6dbdd20aSAndroid Build Coastguard Worker
76*6dbdd20aSAndroid Build Coastguard Workerdef do_check(options):
77*6dbdd20aSAndroid Build Coastguard Worker  c = collections.Counter()
78*6dbdd20aSAndroid Build Coastguard Worker
79*6dbdd20aSAndroid Build Coastguard Worker  for path in all_source_files():
80*6dbdd20aSAndroid Build Coastguard Worker    with open(path) as f:
81*6dbdd20aSAndroid Build Coastguard Worker      s = f.read()
82*6dbdd20aSAndroid Build Coastguard Worker    for check in CHECKS:
83*6dbdd20aSAndroid Build Coastguard Worker      count = len(re.findall(check.regex, s))
84*6dbdd20aSAndroid Build Coastguard Worker      c[check.expected_variable_name] += count
85*6dbdd20aSAndroid Build Coastguard Worker
86*6dbdd20aSAndroid Build Coastguard Worker  for check in CHECKS:
87*6dbdd20aSAndroid Build Coastguard Worker    actual_count = c[check.expected_variable_name]
88*6dbdd20aSAndroid Build Coastguard Worker
89*6dbdd20aSAndroid Build Coastguard Worker    if actual_count > check.expected_count:
90*6dbdd20aSAndroid Build Coastguard Worker      print(f'More "{check.regex}" {check.expected_count} -> {actual_count}')
91*6dbdd20aSAndroid Build Coastguard Worker      print(
92*6dbdd20aSAndroid Build Coastguard Worker          f'  Expected to find {check.expected_count} instances of "{check.regex}" accross the .ts & .d.ts files in the code base.'
93*6dbdd20aSAndroid Build Coastguard Worker      )
94*6dbdd20aSAndroid Build Coastguard Worker      print(f'  Instead found {actual_count}.')
95*6dbdd20aSAndroid Build Coastguard Worker      print(
96*6dbdd20aSAndroid Build Coastguard Worker          f'  It it likely your CL introduces additional uses of "{check.regex}".'
97*6dbdd20aSAndroid Build Coastguard Worker      )
98*6dbdd20aSAndroid Build Coastguard Worker      print(f'  {check.description}')
99*6dbdd20aSAndroid Build Coastguard Worker      return 1
100*6dbdd20aSAndroid Build Coastguard Worker    elif actual_count < check.expected_count:
101*6dbdd20aSAndroid Build Coastguard Worker      print(f'Less "{check.regex}" {check.expected_count} -> {actual_count}')
102*6dbdd20aSAndroid Build Coastguard Worker      print(
103*6dbdd20aSAndroid Build Coastguard Worker          f'  Congratulations your CL reduces the instances of "{check.regex}" in the code base from {check.expected_count} to {actual_count}.'
104*6dbdd20aSAndroid Build Coastguard Worker      )
105*6dbdd20aSAndroid Build Coastguard Worker      print(
106*6dbdd20aSAndroid Build Coastguard Worker          f'  Please go to {__file__} and set {check.expected_variable_name} to {actual_count}.'
107*6dbdd20aSAndroid Build Coastguard Worker      )
108*6dbdd20aSAndroid Build Coastguard Worker      return 1
109*6dbdd20aSAndroid Build Coastguard Worker
110*6dbdd20aSAndroid Build Coastguard Worker  return 0
111*6dbdd20aSAndroid Build Coastguard Worker
112*6dbdd20aSAndroid Build Coastguard Worker
113*6dbdd20aSAndroid Build Coastguard Workerdef main():
114*6dbdd20aSAndroid Build Coastguard Worker  parser = argparse.ArgumentParser(description=__doc__)
115*6dbdd20aSAndroid Build Coastguard Worker  parser.set_defaults(func=do_check)
116*6dbdd20aSAndroid Build Coastguard Worker  subparsers = parser.add_subparsers()
117*6dbdd20aSAndroid Build Coastguard Worker
118*6dbdd20aSAndroid Build Coastguard Worker  check_command = subparsers.add_parser(
119*6dbdd20aSAndroid Build Coastguard Worker      'check', help='Check the rules (default)')
120*6dbdd20aSAndroid Build Coastguard Worker  check_command.set_defaults(func=do_check)
121*6dbdd20aSAndroid Build Coastguard Worker
122*6dbdd20aSAndroid Build Coastguard Worker  options = parser.parse_args()
123*6dbdd20aSAndroid Build Coastguard Worker  return options.func(options)
124*6dbdd20aSAndroid Build Coastguard Worker
125*6dbdd20aSAndroid Build Coastguard Worker
126*6dbdd20aSAndroid Build Coastguard Workerif __name__ == '__main__':
127*6dbdd20aSAndroid Build Coastguard Worker  sys.exit(main())
128