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 |