Skip to content

Commit 97b5c95

Browse files
authored
[et] Better RBE defaults (flutter#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.
1 parent 95e9885 commit 97b5c95

4 files changed

Lines changed: 273 additions & 10 deletions

File tree

tools/engine_tool/test/utils.dart

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ class CannedProcess {
3535
FakeProcess get fakeProcess {
3636
return FakeProcess(exitCode: _exitCode, stdout: _stdout, stderr: _stderr);
3737
}
38+
39+
io.ProcessResult get processResult {
40+
return io.ProcessResult(0, _exitCode, _stdout, _stderr);
41+
}
3842
}
3943

4044
/// ExecutedProcess includes the command and the result.
@@ -79,7 +83,19 @@ class TestEnvironment {
7983
});
8084
return processResult;
8185
}, onRun: (List<String> command) {
82-
throw UnimplementedError('onRun');
86+
final io.ProcessResult result = _getCannedProcessResult(
87+
command, cannedProcesses,
88+
);
89+
processHistory.add(ExecutedProcess(
90+
command,
91+
FakeProcess(
92+
exitCode: result.exitCode,
93+
stdout: result.stdout as String,
94+
stderr: result.stderr as String,
95+
),
96+
result.exitCode,
97+
));
98+
return result;
8399
})),
84100
logger: logger,
85101
verbose: verbose,
@@ -184,6 +200,17 @@ FakeProcess _getCannedResult(
184200
return FakeProcess();
185201
}
186202

203+
io.ProcessResult _getCannedProcessResult(
204+
List<String> command, List<CannedProcess> cannedProcesses) {
205+
for (final CannedProcess cp in cannedProcesses) {
206+
final bool matched = cp.commandMatcher(command);
207+
if (matched) {
208+
return cp.processResult;
209+
}
210+
}
211+
return io.ProcessResult(0, 0, '', '');
212+
}
213+
187214
typedef CommandMatcher = bool Function(List<String> command);
188215

189216
/// Returns a [Matcher] that fails the test if no process has a matching command.

tools/gn

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,15 @@ def setup_rbe(args):
226226
# care of starting and stopping the compiler proxy.
227227
running_on_luci = os.environ.get('LUCI_CONTEXT') is not None
228228

229+
# The default is 'racing', which eagerly attempts to use the local machine
230+
# to run build actions. This is not a performance improvement in CI where the
231+
# 'local machine' is nearly always a VM anyway, and is not any more powerful
232+
# than the 'remote' RBE workers. Only attempt a 'local' build if the remote
233+
# worker times-out or otherwise fails. See also docs on RbeExecStrategy in the
234+
# engine_build_config package under tools/pkg.
235+
if running_on_luci:
236+
rbe_gn_args['rbe_exec_strategy'] = 'remote_local_fallback'
237+
229238
if args.rbe_server_address:
230239
rbe_gn_args['rbe_server_address'] = args.rbe_server_address
231240
if args.rbe_exec_strategy:

tools/pkg/engine_build_configs/lib/src/build_config_runner.dart

Lines changed: 107 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import 'dart:async';
66
import 'dart:convert';
77
import 'dart:ffi' as ffi;
8-
import 'dart:io' as io show Directory, File, Process;
8+
import 'dart:io' as io show Directory, File, Process, ProcessResult;
99
import 'dart:math';
1010

1111
import 'package:path/path.dart' as p;
@@ -14,9 +14,9 @@ import 'package:process_runner/process_runner.dart';
1414

1515
import 'build_config.dart';
1616

17-
/// The base clase for events generated by a command.
17+
/// The base class for events generated by a command.
1818
sealed class RunnerEvent {
19-
RunnerEvent(this.name, this.command, this.timestamp);
19+
RunnerEvent(this.name, this.command, this.timestamp, [this.environment]);
2020

2121
/// The name of the task or command.
2222
final String name;
@@ -26,11 +26,14 @@ sealed class RunnerEvent {
2626

2727
/// When the event happened.
2828
final DateTime timestamp;
29+
30+
/// Additional environment variables set during the command, if any.
31+
final Map<String, String>? environment;
2932
}
3033

3134
/// A [RunnerEvent] representing the start of a command.
3235
final class RunnerStart extends RunnerEvent {
33-
RunnerStart(super.name, super.command, super.timestamp);
36+
RunnerStart(super.name, super.command, super.timestamp, [super.environment]);
3437

3538
@override
3639
String toString() {
@@ -120,6 +123,85 @@ final class RunnerError extends RunnerEvent {
120123
}
121124
}
122125

126+
/// The strategy that RBE uses for local vs. remote actions.
127+
enum RbeExecStrategy {
128+
/// On a cache miss, all actions will be executed locally.
129+
local,
130+
131+
/// On a cache miss, actions will run both remotely and locally (capacity
132+
/// allowing), with the action completing when the faster of the two finishes.
133+
racing,
134+
135+
/// On a cache miss, all actions will be executed remotely.
136+
remote,
137+
138+
/// On a cache miss, actions will be executed locally if the latency of the
139+
/// remote action completing is too high.
140+
remoteLocalFallback;
141+
142+
@override
143+
String toString() => switch (this) {
144+
RbeExecStrategy.local => 'local',
145+
RbeExecStrategy.racing => 'racing',
146+
RbeExecStrategy.remote => 'remote',
147+
RbeExecStrategy.remoteLocalFallback => 'remote_local_fallback',
148+
};
149+
}
150+
151+
/// Configuration options that affect how RBE works.
152+
class RbeConfig {
153+
const RbeConfig({
154+
this.remoteDisabled = false,
155+
this.execStrategy = RbeExecStrategy.racing,
156+
this.racingBias = 0.95,
157+
this.localResourceFraction = 0.2,
158+
});
159+
160+
/// Whether all remote queries/actions are disabled.
161+
///
162+
/// When this is true, not even the remote cache will be queried. All actions
163+
/// will be performed locally.
164+
final bool remoteDisabled;
165+
166+
/// The RBE execution strategy.
167+
final RbeExecStrategy execStrategy;
168+
169+
/// When the RBE execution strategy is 'racing', how much to bias towards
170+
/// local vs. remote.
171+
///
172+
/// Values should be in the range of [0, 1]. Closer to 1 implies biasing
173+
/// towards remote.
174+
final double racingBias;
175+
176+
/// When the RBE execution strategy is 'racing', how much of the local
177+
/// machine to use.
178+
final double localResourceFraction;
179+
180+
/// Environment variables to set when running RBE related subprocesses.
181+
/// Defaults mirroring:
182+
/// https://chromium.googlesource.com/chromium/tools/depot_tools.git/+/refs/heads/main/reclient_helper.py
183+
Map<String, String> get environment => <String, String>{
184+
if (remoteDisabled)
185+
'RBE_remote_disabled': '1'
186+
else ...<String, String>{
187+
'RBE_exec_strategy': execStrategy.toString(),
188+
if (execStrategy == RbeExecStrategy.racing) ...<String, String>{
189+
'RBE_racing_bias': racingBias.toString(),
190+
'RBE_local_resource_fraction': localResourceFraction.toString(),
191+
},
192+
},
193+
// Reduce the cas concurrency. Lower value doesn't impact
194+
// performance when on high-speed connection, but does show improvements
195+
// on easily congested networks.
196+
'RBE_cas_concurrency': '100',
197+
// Enable the deps cache. Mac needs a larger deps cache as it
198+
// seems to have larger dependency sets per action. A larger deps cache
199+
// on other platforms doesn't necessarily help but also won't hurt.
200+
'RBE_enable_deps_cache': '1',
201+
'RBE_deps_cache_max_mb': '1024',
202+
};
203+
}
204+
123205
/// The type of a callback that handles [RunnerEvent]s while a [Runner]
124206
/// is executing its `run()` method.
125207
typedef RunnerEventHandler = void Function(RunnerEvent);
@@ -190,6 +272,7 @@ final class BuildRunner extends Runner {
190272
ffi.Abi? abi,
191273
required io.Directory engineSrcDir,
192274
required this.build,
275+
this.rbeConfig = const RbeConfig(),
193276
this.concurrency = 0,
194277
this.extraGnArgs = const <String>[],
195278
this.extraNinjaArgs = const <String>[],
@@ -210,6 +293,9 @@ final class BuildRunner extends Runner {
210293
/// The [Build] to run.
211294
final Build build;
212295

296+
/// Configuration options for RBE.
297+
final RbeConfig rbeConfig;
298+
213299
/// The maximum number of concurrent jobs.
214300
///
215301
/// This currently only applies to the ninja build, passed as the -j
@@ -450,14 +536,22 @@ final class BuildRunner extends Runner {
450536
'${build.name}: RBE ${shutdown ? 'shutdown' : 'startup'}',
451537
bootstrapCommand,
452538
DateTime.now(),
539+
rbeConfig.environment,
453540
));
454541
final ProcessRunnerResult bootstrapResult;
455542
if (dryRun) {
456543
bootstrapResult = _dryRunResult;
457544
} else {
458-
bootstrapResult = await processRunner.runProcess(
545+
final io.ProcessResult result = await processRunner.processManager.run(
459546
bootstrapCommand,
460-
failOk: true,
547+
environment: rbeConfig.environment,
548+
);
549+
bootstrapResult = ProcessRunnerResult(
550+
result.exitCode,
551+
(result.stdout as String).codeUnits, // stdout.
552+
(result.stderr as String).codeUnits, // stderr.
553+
<int>[], // combined,
554+
pid: result.pid, // pid,
461555
);
462556
}
463557
String okMessage = bootstrapResult.stdout.trim();
@@ -515,16 +609,20 @@ final class BuildRunner extends Runner {
515609
...extraNinjaArgs,
516610
...build.ninja.targets,
517611
];
518-
eventHandler(
519-
RunnerStart('${build.name}: ninja', command, DateTime.now()),
520-
);
612+
eventHandler(RunnerStart(
613+
'${build.name}: ninja',
614+
command,
615+
DateTime.now(),
616+
rbeConfig.environment,
617+
));
521618
final ProcessRunnerResult processResult;
522619
if (dryRun) {
523620
processResult = _dryRunResult;
524621
} else {
525622
final io.Process process = await processRunner.processManager.start(
526623
command,
527624
workingDirectory: engineSrcDir.path,
625+
environment: rbeConfig.environment,
528626
);
529627
final List<int> stderrOutput = <int>[];
530628
final List<int> stdoutOutput = <int>[];

tools/pkg/engine_build_configs/test/build_config_runner_test.dart

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,135 @@ void main() {
283283
expect(events[5].name, equals('$buildName: ninja'));
284284
});
285285

286+
test('GlobalBuildRunner sets default RBE env vars in an RBE build', () async {
287+
final Build targetBuild = buildConfig.builds[0];
288+
final BuildRunner buildRunner = BuildRunner(
289+
platform: FakePlatform(
290+
operatingSystem: Platform.linux,
291+
numberOfProcessors: 32,
292+
),
293+
processRunner: ProcessRunner(
294+
processManager: _fakeProcessManager(),
295+
),
296+
abi: ffi.Abi.linuxX64,
297+
engineSrcDir: engine.srcDir,
298+
build: targetBuild,
299+
concurrency: 500,
300+
extraGnArgs: <String>['--rbe'],
301+
dryRun: true,
302+
);
303+
final List<RunnerEvent> events = <RunnerEvent>[];
304+
void handler(RunnerEvent event) => events.add(event);
305+
final bool runResult = await buildRunner.run(handler);
306+
307+
final String buildName = targetBuild.name;
308+
309+
expect(runResult, isTrue);
310+
311+
// Check that the events for the Ninja command are correct.
312+
expect(events[4] is RunnerStart, isTrue);
313+
expect(events[4].name, equals('$buildName: ninja'));
314+
expect(events[4].environment, isNotNull);
315+
expect(events[4].environment!.containsKey('RBE_exec_strategy'), isTrue);
316+
expect(
317+
events[4].environment!['RBE_exec_strategy'],
318+
equals(RbeExecStrategy.racing.toString()),
319+
);
320+
expect(events[4].environment!.containsKey('RBE_racing_bias'), isTrue);
321+
expect(events[4].environment!['RBE_racing_bias'], equals('0.95'));
322+
expect(
323+
events[4].environment!.containsKey('RBE_local_resource_fraction'),
324+
isTrue,
325+
);
326+
expect(
327+
events[4].environment!['RBE_local_resource_fraction'],
328+
equals('0.2'),
329+
);
330+
});
331+
332+
test('GlobalBuildRunner sets RBE_disable_remote when remote builds are disabled', () async {
333+
final Build targetBuild = buildConfig.builds[0];
334+
final BuildRunner buildRunner = BuildRunner(
335+
platform: FakePlatform(
336+
operatingSystem: Platform.linux,
337+
numberOfProcessors: 32,
338+
),
339+
processRunner: ProcessRunner(
340+
processManager: _fakeProcessManager(),
341+
),
342+
abi: ffi.Abi.linuxX64,
343+
engineSrcDir: engine.srcDir,
344+
build: targetBuild,
345+
concurrency: 500,
346+
rbeConfig: const RbeConfig(remoteDisabled: true),
347+
extraGnArgs: <String>['--rbe'],
348+
dryRun: true,
349+
);
350+
final List<RunnerEvent> events = <RunnerEvent>[];
351+
void handler(RunnerEvent event) => events.add(event);
352+
final bool runResult = await buildRunner.run(handler);
353+
354+
final String buildName = targetBuild.name;
355+
356+
expect(runResult, isTrue);
357+
358+
// Check that the events for the Ninja command are correct.
359+
expect(events[4] is RunnerStart, isTrue);
360+
expect(events[4].name, equals('$buildName: ninja'));
361+
expect(events[4].environment, isNotNull);
362+
expect(events[4].environment!.containsKey('RBE_remote_disabled'), isTrue);
363+
expect(events[4].environment!['RBE_remote_disabled'], equals('1'));
364+
expect(events[4].environment!.containsKey('RBE_exec_strategy'), isFalse);
365+
expect(events[4].environment!.containsKey('RBE_racing_bias'), isFalse);
366+
expect(
367+
events[4].environment!.containsKey('RBE_local_resource_fraction'),
368+
isFalse,
369+
);
370+
});
371+
372+
test('GlobalBuildRunner sets RBE_exec_strategy when a non-default value is passed in the RbeConfig', () async {
373+
final Build targetBuild = buildConfig.builds[0];
374+
final BuildRunner buildRunner = BuildRunner(
375+
platform: FakePlatform(
376+
operatingSystem: Platform.linux,
377+
numberOfProcessors: 32,
378+
),
379+
processRunner: ProcessRunner(
380+
processManager: _fakeProcessManager(),
381+
),
382+
abi: ffi.Abi.linuxX64,
383+
engineSrcDir: engine.srcDir,
384+
build: targetBuild,
385+
concurrency: 500,
386+
rbeConfig: const RbeConfig(execStrategy: RbeExecStrategy.local),
387+
extraGnArgs: <String>['--rbe'],
388+
dryRun: true,
389+
);
390+
final List<RunnerEvent> events = <RunnerEvent>[];
391+
void handler(RunnerEvent event) => events.add(event);
392+
final bool runResult = await buildRunner.run(handler);
393+
394+
final String buildName = targetBuild.name;
395+
396+
expect(runResult, isTrue);
397+
398+
// Check that the events for the Ninja command are correct.
399+
expect(events[4] is RunnerStart, isTrue);
400+
expect(events[4].name, equals('$buildName: ninja'));
401+
expect(events[4].environment, isNotNull);
402+
expect(events[4].environment!.containsKey('RBE_remote_disabled'), isFalse);
403+
expect(events[4].environment!.containsKey('RBE_exec_strategy'), isTrue);
404+
expect(
405+
events[4].environment!['RBE_exec_strategy'],
406+
equals(RbeExecStrategy.local.toString()),
407+
);
408+
expect(events[4].environment!.containsKey('RBE_racing_bias'), isFalse);
409+
expect(
410+
events[4].environment!.containsKey('RBE_local_resource_fraction'),
411+
isFalse,
412+
);
413+
});
414+
286415
test('GlobalBuildRunner passes the specified -j when explicitly provided in a non-RBE build', () async {
287416
final Build targetBuild = buildConfig.builds[0];
288417
final BuildRunner buildRunner = BuildRunner(

0 commit comments

Comments
 (0)