blob: 81063854855a8b9855e6a43690d1bf357e37dd50 [file] [log] [blame]
#!/usr/bin/env dart
// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// Find the newest commit that has a full set of results on the bots.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:args/args.dart';
import 'package:glob/glob.dart';
void main(List<String> args) async {
final parser = new ArgParser();
abbr: "b",
help: "Select the bots matching the glob pattern [option is repeatable]",
splitCommas: false);
abbr: "B",
help: "Select the bots building this branch",
defaultsTo: "master");
abbr: "c", help: "List this many commits", defaultsTo: "1");
parser.addFlag("help", help: "Show the program usage.", negatable: false);
final options = parser.parse(args);
if (options["help"]) {
Usage: find_base_commit.dart [OPTION]...
Find the newest commit that has a full set of results on the bots.
The options are as follows:
int count = int.parse(options["count"]);
final globs = new List<Glob>.from(
options["bot"].map((String pattern) => new Glob(pattern)));
// Download the most recent builds from buildbucket.
int maxBuilds = 1000;
final url = Uri.parse(
const maxRetries = 3;
const timeout = const Duration(seconds: 30);
Map<String, dynamic> object;
for (int i = 1; i <= maxRetries; i++) {
try {
final client = new HttpClient();
final request = await client.getUrl(url).timeout(timeout);
final response = await request.close().timeout(timeout);
object = await response
.transform(new Utf8Decoder())
.transform(new JsonDecoder())
} on TimeoutException catch (e) {
final inSeconds = e.duration.inSeconds;
"Attempt $i of $maxRetries timed out after $inSeconds seconds");
if (i == maxRetries) {
stderr.writeln("error: Failed to download $url");
// Locate the builds we're interested in and map them to each commit. The
// builds returned by the API are sorted with the newest first. Since bots
// don't build back in time and always build the latest commit whenever they
// can, the first time we see a commit, we know it's newer than all commits
// we haven't seen yet. The insertion order into the botsForCommits map will
// then sorted with the newest commit first.
final builds = object["builds"];
final botsForCommits = <String, Set<String>>{};
for (final build in builds) {
final parameters = jsonDecode(build["parameters_json"]);
final bot = parameters["builder_name"];
if (bot.endsWith("-dev") || bot.endsWith("-stable")) {
// Ignore the -dev and -stable builders. The -try builders aren't in the
// bucket we're reading.
if (globs.isNotEmpty && !globs.any((glob) => glob.matches(bot))) {
// Filter way bots we're not interested in.
final properties = parameters["properties"];
final branch = properties["branch"];
if (branch != null && branch != "refs/heads/${options['branch']}") {
// Ignore bots that are building the wrong branch.
final commit = properties["revision"];
if (commit == null) {
// Ignore bots that aren't commit based, e.g. fuzz-linux.
final botsForCommit =
botsForCommits.putIfAbsent(commit, () => new Set<String>());
if (botsForCommits.isEmpty) {
print("Failed to locate any commits having run on the bots");
exitCode = 1;
int maxBots = 0;
for (final commit in botsForCommits.keys) {
maxBots = max(maxBots, botsForCommits[commit].length);
// List commits run on the most bots.
for (final commit in botsForCommits.keys
.where((commit) => botsForCommits[commit].length == maxBots)
.take(count)) {