[et] Better RBE defaults (flutter/engine#54059)
This PR adopts some RBE configuration from the way that chromium uses RBE https://chromium.googlesource.com/chromium/tools/depot_tools.git/+/refs/heads/main/reclient_helper.py These changes should bias both local and CI builds more towards using the worker pool, which we recently expanded, and should help limit the bandwidth used, which is a bottleneck for build times on a slow connection.
This commit is contained in:
parent
897f8dc11d
commit
1bddf3416d
@ -35,6 +35,10 @@ class CannedProcess {
|
|||||||
FakeProcess get fakeProcess {
|
FakeProcess get fakeProcess {
|
||||||
return FakeProcess(exitCode: _exitCode, stdout: _stdout, stderr: _stderr);
|
return FakeProcess(exitCode: _exitCode, stdout: _stdout, stderr: _stderr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
io.ProcessResult get processResult {
|
||||||
|
return io.ProcessResult(0, _exitCode, _stdout, _stderr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ExecutedProcess includes the command and the result.
|
/// ExecutedProcess includes the command and the result.
|
||||||
@ -79,7 +83,19 @@ class TestEnvironment {
|
|||||||
});
|
});
|
||||||
return processResult;
|
return processResult;
|
||||||
}, onRun: (List<String> command) {
|
}, onRun: (List<String> command) {
|
||||||
throw UnimplementedError('onRun');
|
final io.ProcessResult result = _getCannedProcessResult(
|
||||||
|
command, cannedProcesses,
|
||||||
|
);
|
||||||
|
processHistory.add(ExecutedProcess(
|
||||||
|
command,
|
||||||
|
FakeProcess(
|
||||||
|
exitCode: result.exitCode,
|
||||||
|
stdout: result.stdout as String,
|
||||||
|
stderr: result.stderr as String,
|
||||||
|
),
|
||||||
|
result.exitCode,
|
||||||
|
));
|
||||||
|
return result;
|
||||||
})),
|
})),
|
||||||
logger: logger,
|
logger: logger,
|
||||||
verbose: verbose,
|
verbose: verbose,
|
||||||
@ -184,6 +200,17 @@ FakeProcess _getCannedResult(
|
|||||||
return FakeProcess();
|
return FakeProcess();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
io.ProcessResult _getCannedProcessResult(
|
||||||
|
List<String> command, List<CannedProcess> cannedProcesses) {
|
||||||
|
for (final CannedProcess cp in cannedProcesses) {
|
||||||
|
final bool matched = cp.commandMatcher(command);
|
||||||
|
if (matched) {
|
||||||
|
return cp.processResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return io.ProcessResult(0, 0, '', '');
|
||||||
|
}
|
||||||
|
|
||||||
typedef CommandMatcher = bool Function(List<String> command);
|
typedef CommandMatcher = bool Function(List<String> command);
|
||||||
|
|
||||||
/// Returns a [Matcher] that fails the test if no process has a matching command.
|
/// Returns a [Matcher] that fails the test if no process has a matching command.
|
||||||
|
@ -226,6 +226,15 @@ def setup_rbe(args):
|
|||||||
# care of starting and stopping the compiler proxy.
|
# care of starting and stopping the compiler proxy.
|
||||||
running_on_luci = os.environ.get('LUCI_CONTEXT') is not None
|
running_on_luci = os.environ.get('LUCI_CONTEXT') is not None
|
||||||
|
|
||||||
|
# The default is 'racing', which eagerly attempts to use the local machine
|
||||||
|
# to run build actions. This is not a performance improvement in CI where the
|
||||||
|
# 'local machine' is nearly always a VM anyway, and is not any more powerful
|
||||||
|
# than the 'remote' RBE workers. Only attempt a 'local' build if the remote
|
||||||
|
# worker times-out or otherwise fails. See also docs on RbeExecStrategy in the
|
||||||
|
# engine_build_config package under tools/pkg.
|
||||||
|
if running_on_luci:
|
||||||
|
rbe_gn_args['rbe_exec_strategy'] = 'remote_local_fallback'
|
||||||
|
|
||||||
if args.rbe_server_address:
|
if args.rbe_server_address:
|
||||||
rbe_gn_args['rbe_server_address'] = args.rbe_server_address
|
rbe_gn_args['rbe_server_address'] = args.rbe_server_address
|
||||||
if args.rbe_exec_strategy:
|
if args.rbe_exec_strategy:
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:ffi' as ffi;
|
import 'dart:ffi' as ffi;
|
||||||
import 'dart:io' as io show Directory, File, Process;
|
import 'dart:io' as io show Directory, File, Process, ProcessResult;
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
@ -14,9 +14,9 @@ import 'package:process_runner/process_runner.dart';
|
|||||||
|
|
||||||
import 'build_config.dart';
|
import 'build_config.dart';
|
||||||
|
|
||||||
/// The base clase for events generated by a command.
|
/// The base class for events generated by a command.
|
||||||
sealed class RunnerEvent {
|
sealed class RunnerEvent {
|
||||||
RunnerEvent(this.name, this.command, this.timestamp);
|
RunnerEvent(this.name, this.command, this.timestamp, [this.environment]);
|
||||||
|
|
||||||
/// The name of the task or command.
|
/// The name of the task or command.
|
||||||
final String name;
|
final String name;
|
||||||
@ -26,11 +26,14 @@ sealed class RunnerEvent {
|
|||||||
|
|
||||||
/// When the event happened.
|
/// When the event happened.
|
||||||
final DateTime timestamp;
|
final DateTime timestamp;
|
||||||
|
|
||||||
|
/// Additional environment variables set during the command, if any.
|
||||||
|
final Map<String, String>? environment;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A [RunnerEvent] representing the start of a command.
|
/// A [RunnerEvent] representing the start of a command.
|
||||||
final class RunnerStart extends RunnerEvent {
|
final class RunnerStart extends RunnerEvent {
|
||||||
RunnerStart(super.name, super.command, super.timestamp);
|
RunnerStart(super.name, super.command, super.timestamp, [super.environment]);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
@ -120,6 +123,85 @@ final class RunnerError extends RunnerEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The strategy that RBE uses for local vs. remote actions.
|
||||||
|
enum RbeExecStrategy {
|
||||||
|
/// On a cache miss, all actions will be executed locally.
|
||||||
|
local,
|
||||||
|
|
||||||
|
/// On a cache miss, actions will run both remotely and locally (capacity
|
||||||
|
/// allowing), with the action completing when the faster of the two finishes.
|
||||||
|
racing,
|
||||||
|
|
||||||
|
/// On a cache miss, all actions will be executed remotely.
|
||||||
|
remote,
|
||||||
|
|
||||||
|
/// On a cache miss, actions will be executed locally if the latency of the
|
||||||
|
/// remote action completing is too high.
|
||||||
|
remoteLocalFallback;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => switch (this) {
|
||||||
|
RbeExecStrategy.local => 'local',
|
||||||
|
RbeExecStrategy.racing => 'racing',
|
||||||
|
RbeExecStrategy.remote => 'remote',
|
||||||
|
RbeExecStrategy.remoteLocalFallback => 'remote_local_fallback',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configuration options that affect how RBE works.
|
||||||
|
class RbeConfig {
|
||||||
|
const RbeConfig({
|
||||||
|
this.remoteDisabled = false,
|
||||||
|
this.execStrategy = RbeExecStrategy.racing,
|
||||||
|
this.racingBias = 0.95,
|
||||||
|
this.localResourceFraction = 0.2,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Whether all remote queries/actions are disabled.
|
||||||
|
///
|
||||||
|
/// When this is true, not even the remote cache will be queried. All actions
|
||||||
|
/// will be performed locally.
|
||||||
|
final bool remoteDisabled;
|
||||||
|
|
||||||
|
/// The RBE execution strategy.
|
||||||
|
final RbeExecStrategy execStrategy;
|
||||||
|
|
||||||
|
/// When the RBE execution strategy is 'racing', how much to bias towards
|
||||||
|
/// local vs. remote.
|
||||||
|
///
|
||||||
|
/// Values should be in the range of [0, 1]. Closer to 1 implies biasing
|
||||||
|
/// towards remote.
|
||||||
|
final double racingBias;
|
||||||
|
|
||||||
|
/// When the RBE execution strategy is 'racing', how much of the local
|
||||||
|
/// machine to use.
|
||||||
|
final double localResourceFraction;
|
||||||
|
|
||||||
|
/// Environment variables to set when running RBE related subprocesses.
|
||||||
|
/// Defaults mirroring:
|
||||||
|
/// https://chromium.googlesource.com/chromium/tools/depot_tools.git/+/refs/heads/main/reclient_helper.py
|
||||||
|
Map<String, String> get environment => <String, String>{
|
||||||
|
if (remoteDisabled)
|
||||||
|
'RBE_remote_disabled': '1'
|
||||||
|
else ...<String, String>{
|
||||||
|
'RBE_exec_strategy': execStrategy.toString(),
|
||||||
|
if (execStrategy == RbeExecStrategy.racing) ...<String, String>{
|
||||||
|
'RBE_racing_bias': racingBias.toString(),
|
||||||
|
'RBE_local_resource_fraction': localResourceFraction.toString(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Reduce the cas concurrency. Lower value doesn't impact
|
||||||
|
// performance when on high-speed connection, but does show improvements
|
||||||
|
// on easily congested networks.
|
||||||
|
'RBE_cas_concurrency': '100',
|
||||||
|
// Enable the deps cache. Mac needs a larger deps cache as it
|
||||||
|
// seems to have larger dependency sets per action. A larger deps cache
|
||||||
|
// on other platforms doesn't necessarily help but also won't hurt.
|
||||||
|
'RBE_enable_deps_cache': '1',
|
||||||
|
'RBE_deps_cache_max_mb': '1024',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// The type of a callback that handles [RunnerEvent]s while a [Runner]
|
/// The type of a callback that handles [RunnerEvent]s while a [Runner]
|
||||||
/// is executing its `run()` method.
|
/// is executing its `run()` method.
|
||||||
typedef RunnerEventHandler = void Function(RunnerEvent);
|
typedef RunnerEventHandler = void Function(RunnerEvent);
|
||||||
@ -190,6 +272,7 @@ final class BuildRunner extends Runner {
|
|||||||
ffi.Abi? abi,
|
ffi.Abi? abi,
|
||||||
required io.Directory engineSrcDir,
|
required io.Directory engineSrcDir,
|
||||||
required this.build,
|
required this.build,
|
||||||
|
this.rbeConfig = const RbeConfig(),
|
||||||
this.concurrency = 0,
|
this.concurrency = 0,
|
||||||
this.extraGnArgs = const <String>[],
|
this.extraGnArgs = const <String>[],
|
||||||
this.extraNinjaArgs = const <String>[],
|
this.extraNinjaArgs = const <String>[],
|
||||||
@ -210,6 +293,9 @@ final class BuildRunner extends Runner {
|
|||||||
/// The [Build] to run.
|
/// The [Build] to run.
|
||||||
final Build build;
|
final Build build;
|
||||||
|
|
||||||
|
/// Configuration options for RBE.
|
||||||
|
final RbeConfig rbeConfig;
|
||||||
|
|
||||||
/// The maximum number of concurrent jobs.
|
/// The maximum number of concurrent jobs.
|
||||||
///
|
///
|
||||||
/// This currently only applies to the ninja build, passed as the -j
|
/// This currently only applies to the ninja build, passed as the -j
|
||||||
@ -450,14 +536,22 @@ final class BuildRunner extends Runner {
|
|||||||
'${build.name}: RBE ${shutdown ? 'shutdown' : 'startup'}',
|
'${build.name}: RBE ${shutdown ? 'shutdown' : 'startup'}',
|
||||||
bootstrapCommand,
|
bootstrapCommand,
|
||||||
DateTime.now(),
|
DateTime.now(),
|
||||||
|
rbeConfig.environment,
|
||||||
));
|
));
|
||||||
final ProcessRunnerResult bootstrapResult;
|
final ProcessRunnerResult bootstrapResult;
|
||||||
if (dryRun) {
|
if (dryRun) {
|
||||||
bootstrapResult = _dryRunResult;
|
bootstrapResult = _dryRunResult;
|
||||||
} else {
|
} else {
|
||||||
bootstrapResult = await processRunner.runProcess(
|
final io.ProcessResult result = await processRunner.processManager.run(
|
||||||
bootstrapCommand,
|
bootstrapCommand,
|
||||||
failOk: true,
|
environment: rbeConfig.environment,
|
||||||
|
);
|
||||||
|
bootstrapResult = ProcessRunnerResult(
|
||||||
|
result.exitCode,
|
||||||
|
(result.stdout as String).codeUnits, // stdout.
|
||||||
|
(result.stderr as String).codeUnits, // stderr.
|
||||||
|
<int>[], // combined,
|
||||||
|
pid: result.pid, // pid,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
String okMessage = bootstrapResult.stdout.trim();
|
String okMessage = bootstrapResult.stdout.trim();
|
||||||
@ -515,9 +609,12 @@ final class BuildRunner extends Runner {
|
|||||||
...extraNinjaArgs,
|
...extraNinjaArgs,
|
||||||
...build.ninja.targets,
|
...build.ninja.targets,
|
||||||
];
|
];
|
||||||
eventHandler(
|
eventHandler(RunnerStart(
|
||||||
RunnerStart('${build.name}: ninja', command, DateTime.now()),
|
'${build.name}: ninja',
|
||||||
);
|
command,
|
||||||
|
DateTime.now(),
|
||||||
|
rbeConfig.environment,
|
||||||
|
));
|
||||||
final ProcessRunnerResult processResult;
|
final ProcessRunnerResult processResult;
|
||||||
if (dryRun) {
|
if (dryRun) {
|
||||||
processResult = _dryRunResult;
|
processResult = _dryRunResult;
|
||||||
@ -525,6 +622,7 @@ final class BuildRunner extends Runner {
|
|||||||
final io.Process process = await processRunner.processManager.start(
|
final io.Process process = await processRunner.processManager.start(
|
||||||
command,
|
command,
|
||||||
workingDirectory: engineSrcDir.path,
|
workingDirectory: engineSrcDir.path,
|
||||||
|
environment: rbeConfig.environment,
|
||||||
);
|
);
|
||||||
final List<int> stderrOutput = <int>[];
|
final List<int> stderrOutput = <int>[];
|
||||||
final List<int> stdoutOutput = <int>[];
|
final List<int> stdoutOutput = <int>[];
|
||||||
|
@ -283,6 +283,135 @@ void main() {
|
|||||||
expect(events[5].name, equals('$buildName: ninja'));
|
expect(events[5].name, equals('$buildName: ninja'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('GlobalBuildRunner sets default RBE env vars in an RBE build', () async {
|
||||||
|
final Build targetBuild = buildConfig.builds[0];
|
||||||
|
final BuildRunner buildRunner = BuildRunner(
|
||||||
|
platform: FakePlatform(
|
||||||
|
operatingSystem: Platform.linux,
|
||||||
|
numberOfProcessors: 32,
|
||||||
|
),
|
||||||
|
processRunner: ProcessRunner(
|
||||||
|
processManager: _fakeProcessManager(),
|
||||||
|
),
|
||||||
|
abi: ffi.Abi.linuxX64,
|
||||||
|
engineSrcDir: engine.srcDir,
|
||||||
|
build: targetBuild,
|
||||||
|
concurrency: 500,
|
||||||
|
extraGnArgs: <String>['--rbe'],
|
||||||
|
dryRun: true,
|
||||||
|
);
|
||||||
|
final List<RunnerEvent> events = <RunnerEvent>[];
|
||||||
|
void handler(RunnerEvent event) => events.add(event);
|
||||||
|
final bool runResult = await buildRunner.run(handler);
|
||||||
|
|
||||||
|
final String buildName = targetBuild.name;
|
||||||
|
|
||||||
|
expect(runResult, isTrue);
|
||||||
|
|
||||||
|
// Check that the events for the Ninja command are correct.
|
||||||
|
expect(events[4] is RunnerStart, isTrue);
|
||||||
|
expect(events[4].name, equals('$buildName: ninja'));
|
||||||
|
expect(events[4].environment, isNotNull);
|
||||||
|
expect(events[4].environment!.containsKey('RBE_exec_strategy'), isTrue);
|
||||||
|
expect(
|
||||||
|
events[4].environment!['RBE_exec_strategy'],
|
||||||
|
equals(RbeExecStrategy.racing.toString()),
|
||||||
|
);
|
||||||
|
expect(events[4].environment!.containsKey('RBE_racing_bias'), isTrue);
|
||||||
|
expect(events[4].environment!['RBE_racing_bias'], equals('0.95'));
|
||||||
|
expect(
|
||||||
|
events[4].environment!.containsKey('RBE_local_resource_fraction'),
|
||||||
|
isTrue,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
events[4].environment!['RBE_local_resource_fraction'],
|
||||||
|
equals('0.2'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GlobalBuildRunner sets RBE_disable_remote when remote builds are disabled', () async {
|
||||||
|
final Build targetBuild = buildConfig.builds[0];
|
||||||
|
final BuildRunner buildRunner = BuildRunner(
|
||||||
|
platform: FakePlatform(
|
||||||
|
operatingSystem: Platform.linux,
|
||||||
|
numberOfProcessors: 32,
|
||||||
|
),
|
||||||
|
processRunner: ProcessRunner(
|
||||||
|
processManager: _fakeProcessManager(),
|
||||||
|
),
|
||||||
|
abi: ffi.Abi.linuxX64,
|
||||||
|
engineSrcDir: engine.srcDir,
|
||||||
|
build: targetBuild,
|
||||||
|
concurrency: 500,
|
||||||
|
rbeConfig: const RbeConfig(remoteDisabled: true),
|
||||||
|
extraGnArgs: <String>['--rbe'],
|
||||||
|
dryRun: true,
|
||||||
|
);
|
||||||
|
final List<RunnerEvent> events = <RunnerEvent>[];
|
||||||
|
void handler(RunnerEvent event) => events.add(event);
|
||||||
|
final bool runResult = await buildRunner.run(handler);
|
||||||
|
|
||||||
|
final String buildName = targetBuild.name;
|
||||||
|
|
||||||
|
expect(runResult, isTrue);
|
||||||
|
|
||||||
|
// Check that the events for the Ninja command are correct.
|
||||||
|
expect(events[4] is RunnerStart, isTrue);
|
||||||
|
expect(events[4].name, equals('$buildName: ninja'));
|
||||||
|
expect(events[4].environment, isNotNull);
|
||||||
|
expect(events[4].environment!.containsKey('RBE_remote_disabled'), isTrue);
|
||||||
|
expect(events[4].environment!['RBE_remote_disabled'], equals('1'));
|
||||||
|
expect(events[4].environment!.containsKey('RBE_exec_strategy'), isFalse);
|
||||||
|
expect(events[4].environment!.containsKey('RBE_racing_bias'), isFalse);
|
||||||
|
expect(
|
||||||
|
events[4].environment!.containsKey('RBE_local_resource_fraction'),
|
||||||
|
isFalse,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GlobalBuildRunner sets RBE_exec_strategy when a non-default value is passed in the RbeConfig', () async {
|
||||||
|
final Build targetBuild = buildConfig.builds[0];
|
||||||
|
final BuildRunner buildRunner = BuildRunner(
|
||||||
|
platform: FakePlatform(
|
||||||
|
operatingSystem: Platform.linux,
|
||||||
|
numberOfProcessors: 32,
|
||||||
|
),
|
||||||
|
processRunner: ProcessRunner(
|
||||||
|
processManager: _fakeProcessManager(),
|
||||||
|
),
|
||||||
|
abi: ffi.Abi.linuxX64,
|
||||||
|
engineSrcDir: engine.srcDir,
|
||||||
|
build: targetBuild,
|
||||||
|
concurrency: 500,
|
||||||
|
rbeConfig: const RbeConfig(execStrategy: RbeExecStrategy.local),
|
||||||
|
extraGnArgs: <String>['--rbe'],
|
||||||
|
dryRun: true,
|
||||||
|
);
|
||||||
|
final List<RunnerEvent> events = <RunnerEvent>[];
|
||||||
|
void handler(RunnerEvent event) => events.add(event);
|
||||||
|
final bool runResult = await buildRunner.run(handler);
|
||||||
|
|
||||||
|
final String buildName = targetBuild.name;
|
||||||
|
|
||||||
|
expect(runResult, isTrue);
|
||||||
|
|
||||||
|
// Check that the events for the Ninja command are correct.
|
||||||
|
expect(events[4] is RunnerStart, isTrue);
|
||||||
|
expect(events[4].name, equals('$buildName: ninja'));
|
||||||
|
expect(events[4].environment, isNotNull);
|
||||||
|
expect(events[4].environment!.containsKey('RBE_remote_disabled'), isFalse);
|
||||||
|
expect(events[4].environment!.containsKey('RBE_exec_strategy'), isTrue);
|
||||||
|
expect(
|
||||||
|
events[4].environment!['RBE_exec_strategy'],
|
||||||
|
equals(RbeExecStrategy.local.toString()),
|
||||||
|
);
|
||||||
|
expect(events[4].environment!.containsKey('RBE_racing_bias'), isFalse);
|
||||||
|
expect(
|
||||||
|
events[4].environment!.containsKey('RBE_local_resource_fraction'),
|
||||||
|
isFalse,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('GlobalBuildRunner passes the specified -j when explicitly provided in a non-RBE build', () async {
|
test('GlobalBuildRunner passes the specified -j when explicitly provided in a non-RBE build', () async {
|
||||||
final Build targetBuild = buildConfig.builds[0];
|
final Build targetBuild = buildConfig.builds[0];
|
||||||
final BuildRunner buildRunner = BuildRunner(
|
final BuildRunner buildRunner = BuildRunner(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user