blob: 2eb887554b2a521dda9045dc790c316bc3c3b39b [file] [log] [blame]
// Copyright (c) 2017, 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.
import 'dart:io';
import 'dart:async';
import 'dart:collection';
import 'dart:convert';
import 'util.dart';
final String cit = Platform.isWindows ? 'cit.bat' : 'cit';
enum LogdogExitKind {
/// Normal program termination with a negative exit code.
normal,
/// Program killed due to error output.
error,
/// Program killed due to timeout.
timeout,
}
class LogdogException implements Exception {
final List<String> command;
final int errorCode;
final String stdout;
final String stderr;
final LogdogExitKind exitKind;
LogdogException(
this.command, this.errorCode, this.stdout, this.stderr, this.exitKind);
LogdogException.fromProcessResult(ProcessResult result)
: this(null, result.exitCode, result.stdout, result.stderr,
LogdogExitKind.normal);
toString() => "Error during logdog execution"
"${command != null ? ' `${command.join(' ')}`': ''}"
":\n$stderr";
}
bool logdogCheckDone = false;
void checkLogdog({bool tryToInstall: true}) {
if (logdogCheckDone) return;
var result = Process.runSync(cit, []);
if (result.exitCode != 0) {
print("cit (from depot_tools) must be in the path.");
throw new StateError("cit not accessible");
}
String stdout = result.stdout;
if (stdout.contains("logdog")) {
logdogCheckDone = true;
return;
}
if (tryToInstall) {
print("logdog isn't yet installed. Installation might take some time");
result = Process.runSync(cit, ["logdog"]);
checkLogdog(tryToInstall: false);
} else {
print("Couldn't install logdog");
throw new StateError("logdog not accessible");
}
}
Future<String> logdog(List<String> args,
{bool exitOnError: true,
Duration timeout: const Duration(seconds: 10)}) async {
checkLogdog();
args = args.toList()..insert(0, "logdog");
LogdogExitKind exitKind = LogdogExitKind.normal;
Process process = await Process.start(cit, args);
StringBuffer stdout = new StringBuffer();
StringBuffer stderr = new StringBuffer();
StreamSubscription stdoutSubscription =
process.stdout.transform(UTF8.decoder).listen(stdout.write);
StreamSubscription stderrSubscription =
process.stderr.transform(UTF8.decoder).listen((String text) {
stderr.write(text);
if (exitOnError) {
exitKind = LogdogExitKind.error;
log('Error on `${args.join(' ')}`: $text');
process.kill();
}
});
Timer timer;
if (timeout != null) {
timer = new Timer(timeout, () {
exitKind = LogdogExitKind.timeout;
log('Timeout on `${args.join(' ')}`');
process.kill();
});
}
int exitCode = await process.exitCode;
// Cancel the timer; it might still be running.
timer?.cancel();
// Cancel the stdout/stderr subscriptions; if the process is killed the
// streams might not have closed.
stdoutSubscription.cancel();
stderrSubscription.cancel();
if (exitCode == 0) return stdout.toString();
throw new LogdogException(
args, exitCode, stdout.toString(), stderr.toString(), exitKind);
}
Future<String> cat(String log) async {
return logdog(["cat", "-raw", log]);
}
/// Returns the content for [path], for instance the available build numbers
/// for 'dart2js-linux-chromeff-1-4-be' using the path
/// `chromium/bb/client.dart/dart2js-linux-chromeff-1-4-be`.
Future<String> ls(String path) async {
return logdog(["ls", path]);
}
class LogResult<T> {
final String log;
final T result;
LogResult(this.log, this.result);
}
const int maxConcurrentLogdogs = 20;
/// Fetches the given [logs] concurrently using [logdog].
///
/// At most [maxConcurrentLogdogs] connections are opened at the same time.
///
/// The resulting [LogResult] has a [LogResult.result] equal to `null` if
/// the log didn't exist.
Stream<LogResult<String>> catN(Iterable<String> logs) async* {
var queue = new Queue<Future<LogResult<ProcessResult>>>();
var it = logs.iterator;
// Launches a new logdog to fetch the next log.
// Returns false when nothing was left to enqueue.
bool enqueueNext() {
if (!it.moveNext()) return false;
var log = it.current;
queue.add(new Future.sync(() async {
var logPath = log.substring(0, log.lastIndexOf("/"));
var lsResult = await Process.run(cit, ["logdog", "ls", logPath]);
if (lsResult.exitCode != 0) return new LogResult(log, lsResult);
if (lsResult.stdout == "") return new LogResult(log, null);
return new LogResult(
log, await Process.run(cit, ["logdog", "cat", "-raw", log]));
}));
return true;
}
for (int i = 0; i < maxConcurrentLogdogs; i++) {
enqueueNext();
}
while (queue.isNotEmpty) {
var logResult =
await queue.removeFirst().timeout(const Duration(seconds: 15));
enqueueNext();
if (logResult.result == null) {
yield new LogResult(logResult.log, null);
} else if (logResult.result.exitCode != 0) {
throw new LogdogException.fromProcessResult(logResult.result);
} else {
yield new LogResult(logResult.log, logResult.result.stdout);
}
}
}
/*
main() async {
// print(cat(
// "chromium/bb/client.dart/dart2js-win7-ie11ff-4-4-be/4215/+/recipes/steps/dart2js_ie11_tests/0/stdout"));
catN(new Iterable.generate(10, (i) {
return "chromium/bb/client.dart/dart2js-win7-ie11ff-4-4-be/"
"${4200 + i}"
"/+/recipes/steps/dart2js_ie11_tests/0/stdout";
})).listen((logResult) {
print("--------------------------");
if (logResult.result == null) {
print("${logResult.log} - empty");
} else {
print(logResult.result.substring(0, 200));
}
});
}
*/