Chromium Code Reviews| Index: tools/mb/mb.py |
| diff --git a/tools/mb/mb.py b/tools/mb/mb.py |
| index 09c8ebcd833177a8ec97f612a55787ad7966bcb9..8e7efa19709d64412ccffa66a55740be1414d27b 100755 |
| --- a/tools/mb/mb.py |
| +++ b/tools/mb/mb.py |
| @@ -23,6 +23,9 @@ import shutil |
| import sys |
| import subprocess |
| import tempfile |
| +import urllib2 |
| + |
| +from collections import OrderedDict |
| def main(args): |
| mbw = MetaBuildWrapper() |
| @@ -142,6 +145,24 @@ class MetaBuildWrapper(object): |
| '(default is //tools/mb/mb_config.pyl)') |
| subp.set_defaults(func=self.CmdValidate) |
| + subp = subps.add_parser('audit', |
| + help='Audit the config file to track progress') |
| + subp.add_argument('-f', '--config-file', metavar='PATH', |
| + default=self.default_config, |
| + help='path to config file ' |
| + '(default is //tools/mb/mb_config.pyl)') |
| + subp.add_argument('-i', '--internal', action='store_true', |
| + help='check internal masters also') |
| + subp.add_argument('-m', '--master', action='append', |
| + help='master to audit (default is all non-internal ' |
| + 'masters in file)') |
| + subp.add_argument('-u', '--url-template', action='store', |
| + default='https://build.chromium.org/p/' |
| + '{master}/json/builders', |
| + help='URL scheme for JSON APIs to buildbot ' |
| + '(default: %(default)s) ') |
| + subp.set_defaults(func=self.CmdAudit) |
| + |
| subp = subps.add_parser('help', |
| help='Get help on a subcommand.') |
| subp.add_argument(nargs='?', action='store', dest='subcommand', |
| @@ -334,6 +355,109 @@ class MetaBuildWrapper(object): |
| self.Print('mb config file %s looks ok.' % self.args.config_file) |
| return 0 |
| + def CmdAudit(self): |
| + self.ReadConfigFile() |
| + |
| + STAT_MISSING_ON_MASTER = 'in config but not on master' |
| + STAT_MISSING_IN_CONFIG = 'on master but not in config' |
| + STAT_TBD = 'still TBD' |
| + STAT_GYP = 'still on GYP' |
| + STAT_DONE = 'done (on GN)' |
| + stats = OrderedDict() |
| + stats[STAT_MISSING_ON_MASTER] = 0 |
| + stats[STAT_MISSING_IN_CONFIG] = 0 |
| + stats[STAT_TBD] = 0 |
| + stats[STAT_GYP] = 0 |
| + stats[STAT_DONE] = 0 |
| + |
| + masters = self.args.master or self.masters |
| + for master in sorted(masters): |
| + config_builders = sorted(self.masters[master]) |
| + url = self.args.url_template.replace('{master}', master) |
| + |
| + self.Print('Auditing %s' % master) |
| + |
| + MASTERS_TO_SKIP = ( |
| + 'client.skia', |
| + 'client.v8.fyi', |
| + 'tryserver.v8', |
| + ) |
| + if master in MASTERS_TO_SKIP: |
| + # We skip these bots because we don't care if they're converted. |
| + self.Print(' skipped (do not care if incomplete)') |
| + self.Print('') |
| + continue |
| + |
| + INTERNAL_MASTERS = ( |
| + 'chrome', |
| + 'chrome.continuous', |
| + 'official.desktop', |
| + 'official.desktop.continuous' |
| + ) |
| + if master in INTERNAL_MASTERS and not self.args.internal: |
| + self.Print(' skipped (internal)') |
| + self.Print('') |
| + continue |
| + |
| + try: |
| + json_contents = self.Fetch(url) |
| + d = json.loads(json_contents) |
| + except Exception as e: |
| + self.Print(str(e)) |
| + return 1 |
| + |
| + missing_on_master = [] |
| + missing_in_config = [] |
| + tbd = [] |
| + gyp = [] |
| + done = [] |
| + buildbot_builders = sorted(d.keys()) |
| + for builder in config_builders: |
| + if builder in buildbot_builders: |
| + config = self.masters[master][builder] |
| + if config == 'tbd': |
| + tbd.append(builder) |
| + else: |
| + # TODO(dpranke): Check if MB is actually running? |
| + vals = self.FlattenConfig(config) |
| + if vals['type'] == 'gyp': |
| + gyp.append(builder) |
| + else: |
| + done.append(builder) |
| + else: |
| + missing_on_master.append(builder) |
| + |
| + for builder in buildbot_builders: |
|
estaab
2016/03/12 04:07:27
nit:
missing_in_config = sorted(set(buildbot_build
|
| + if builder not in config_builders: |
| + missing_in_config.append(builder) |
| + |
| + def PrintBuilders(heading, builders): |
| + stats.setdefault(heading, 0) |
| + stats[heading] += len(builders) |
| + if builders: |
| + self.Print(' %s:' % heading) |
| + for builder in builders: |
| + self.Print(' %s' % builder) |
| + |
| + if missing_in_config or missing_on_master or tbd or gyp: |
| + PrintBuilders(STAT_MISSING_IN_CONFIG, missing_in_config) |
| + PrintBuilders(STAT_MISSING_ON_MASTER, missing_on_master) |
| + PrintBuilders(STAT_TBD, tbd) |
| + PrintBuilders(STAT_GYP, gyp) |
| + else: |
| + self.Print(' ... ok') |
| + |
| + stats[STAT_DONE] += len(done) |
| + |
| + self.Print('') |
| + |
| + fmt = '{:<27} {:>4}' |
| + self.Print(fmt.format('Totals', str(sum(int(v) for v in stats.values())))) |
| + self.Print(fmt.format('-' * 27, '----')) |
| + for stat, count in stats.items(): |
| + self.Print(fmt.format(stat, str(count))) |
| + return 0 |
| + |
| def GetConfig(self): |
| build_dir = self.args.path[0] |
| @@ -1096,6 +1220,13 @@ class MetaBuildWrapper(object): |
| # This function largely exists so it can be overridden for testing. |
| return os.path.exists(path) |
| + def Fetch(self, url): |
| + |
| + f = urllib2.urlopen(url) |
| + contents = f.read() |
| + f.close() |
| + return contents |
| + |
| def GNTargetName(self, target): |
| return target[:-len('_apk')] if target.endswith('_apk') else target |