Skip to content

Commit d90b64f

Browse files
[flutter_plugin_tools] Add optional timing info (flutter#4500)
Adds a `--log-timing` flag that can be passed to cause package-looping commands to log relative start time and per-package elapsed time, to help with future efforts to improve CI times (e.g., finding unusually slow packages, or designing changes to sharding). Adds this flag to the CI scripts to enable timing there.
1 parent 027e940 commit d90b64f

9 files changed

+116
-28
lines changed

.ci/scripts/build_examples_uwp.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
# found in the LICENSE file.
55

66
dart ./script/tool/bin/flutter_plugin_tools.dart build-examples --winuwp \
7-
--packages-for-branch
7+
--packages-for-branch --log-timing

.ci/scripts/build_examples_win32.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
# found in the LICENSE file.
55

66
dart ./script/tool/bin/flutter_plugin_tools.dart build-examples --windows \
7-
--packages-for-branch
7+
--packages-for-branch --log-timing

.ci/scripts/drive_examples_win32.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
# found in the LICENSE file.
55

66
dart ./script/tool/bin/flutter_plugin_tools.dart drive-examples --windows \
7-
--packages-for-branch
7+
--packages-for-branch --log-timing

.ci/scripts/native_test_win32.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
# found in the LICENSE file.
55

66
dart ./script/tool/bin/flutter_plugin_tools.dart native-test --windows \
7-
--no-integration --packages-for-branch
7+
--no-integration --packages-for-branch --log-timing

script/tool/CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## NEXT
2+
3+
- Added `--log-timing` to add timing information to package headers in looping
4+
commands.
5+
16
## 0.7.2
27

38
- Update Firebase Testlab deprecated test device. (Pixel 4 API 29 -> Pixel 5 API 30).

script/tool/lib/src/common/package_looping_command.dart

+45-13
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ abstract class PackageLoopingCommand extends PluginCommand {
170170
/// messages. DO NOT RELY on someone noticing a warning; instead, use it for
171171
/// things that might be useful to someone debugging an unexpected result.
172172
void logWarning(String warningMessage) {
173-
print(Colorize(warningMessage)..yellow());
173+
_printColorized(warningMessage, Styles.YELLOW);
174174
if (_currentPackageEntry != null) {
175175
_packagesWithWarnings.add(_currentPackageEntry!);
176176
} else {
@@ -219,6 +219,8 @@ abstract class PackageLoopingCommand extends PluginCommand {
219219
_otherWarningCount = 0;
220220
_currentPackageEntry = null;
221221

222+
final DateTime runStart = DateTime.now();
223+
222224
await initializeRun();
223225

224226
final List<PackageEnumerationEntry> targetPackages =
@@ -227,8 +229,9 @@ abstract class PackageLoopingCommand extends PluginCommand {
227229
final Map<PackageEnumerationEntry, PackageResult> results =
228230
<PackageEnumerationEntry, PackageResult>{};
229231
for (final PackageEnumerationEntry entry in targetPackages) {
232+
final DateTime packageStart = DateTime.now();
230233
_currentPackageEntry = entry;
231-
_printPackageHeading(entry);
234+
_printPackageHeading(entry, startTime: runStart);
232235

233236
// Command implementations should never see excluded packages; they are
234237
// included at this level only for logging.
@@ -246,11 +249,20 @@ abstract class PackageLoopingCommand extends PluginCommand {
246249
result = PackageResult.fail(<String>['Unhandled exception']);
247250
}
248251
if (result.state == RunState.skipped) {
249-
final String message =
250-
'${indentation}SKIPPING: ${result.details.first}';
251-
captureOutput ? print(message) : print(Colorize(message)..darkGray());
252+
_printColorized('${indentation}SKIPPING: ${result.details.first}',
253+
Styles.DARK_GRAY);
252254
}
253255
results[entry] = result;
256+
257+
// Only log an elapsed time for long output; for short output, comparing
258+
// the relative timestamps of successive entries should be trivial.
259+
if (shouldLogTiming && hasLongOutput) {
260+
final Duration elapsedTime = DateTime.now().difference(packageStart);
261+
_printColorized(
262+
'\n[${entry.package.displayName} completed in '
263+
'${elapsedTime.inMinutes}m ${elapsedTime.inSeconds % 60}s]',
264+
Styles.DARK_GRAY);
265+
}
254266
}
255267
_currentPackageEntry = null;
256268

@@ -287,11 +299,20 @@ abstract class PackageLoopingCommand extends PluginCommand {
287299
/// Something is always printed to make it easier to distinguish between
288300
/// a command running for a package and producing no output, and a command
289301
/// not having been run for a package.
290-
void _printPackageHeading(PackageEnumerationEntry entry) {
302+
void _printPackageHeading(PackageEnumerationEntry entry,
303+
{required DateTime startTime}) {
291304
final String packageDisplayName = entry.package.displayName;
292305
String heading = entry.excluded
293306
? 'Not running for $packageDisplayName; excluded'
294307
: 'Running for $packageDisplayName';
308+
309+
if (shouldLogTiming) {
310+
final Duration relativeTime = DateTime.now().difference(startTime);
311+
final String timeString = _formatDurationAsRelativeTime(relativeTime);
312+
heading =
313+
hasLongOutput ? '$heading [@$timeString]' : '[$timeString] $heading';
314+
}
315+
295316
if (hasLongOutput) {
296317
heading = '''
297318
@@ -302,13 +323,7 @@ abstract class PackageLoopingCommand extends PluginCommand {
302323
} else if (!entry.excluded) {
303324
heading = '$heading...';
304325
}
305-
if (captureOutput) {
306-
print(heading);
307-
} else {
308-
final Colorize colorizeHeading = Colorize(heading);
309-
print(
310-
entry.excluded ? colorizeHeading.darkGray() : colorizeHeading.cyan());
311-
}
326+
_printColorized(heading, entry.excluded ? Styles.DARK_GRAY : Styles.CYAN);
312327
}
313328

314329
/// Prints a summary of packges run, packages skipped, and warnings.
@@ -401,4 +416,21 @@ abstract class PackageLoopingCommand extends PluginCommand {
401416
}
402417
_printError(failureListFooter);
403418
}
419+
420+
/// Prints [message] in [color] unless [captureOutput] is set, in which case
421+
/// it is printed without color.
422+
void _printColorized(String message, Styles color) {
423+
if (captureOutput) {
424+
print(message);
425+
} else {
426+
print(Colorize(message)..apply(color));
427+
}
428+
}
429+
430+
/// Returns a duration [d] formatted as minutes:seconds. Does not use hours,
431+
/// since time logging is primarily intended for CI, where durations should
432+
/// always be less than an hour.
433+
String _formatDurationAsRelativeTime(Duration d) {
434+
return '${d.inMinutes}:${(d.inSeconds % 60).toString().padLeft(2, '0')}';
435+
}
404436
}

script/tool/lib/src/common/plugin_command.dart

+20-10
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ abstract class PluginCommand extends Command<void> {
7676
'If no packages have changed, or if there have been changes that may\n'
7777
'affect all packages, the command runs on all packages.\n'
7878
'The packages excluded with $_excludeArg is also excluded even if changed.\n'
79-
'See $_kBaseSha if a custom base is needed to determine the diff.\n\n'
79+
'See $_baseShaArg if a custom base is needed to determine the diff.\n\n'
8080
'Cannot be combined with $_packagesArg.\n');
8181
argParser.addFlag(_packagesForBranchArg,
8282
help:
@@ -86,20 +86,25 @@ abstract class PluginCommand extends Command<void> {
8686
'Cannot be combined with $_packagesArg.\n\n'
8787
'This is intended for use in CI.\n',
8888
hide: true);
89-
argParser.addOption(_kBaseSha,
89+
argParser.addOption(_baseShaArg,
9090
help: 'The base sha used to determine git diff. \n'
9191
'This is useful when $_runOnChangedPackagesArg is specified.\n'
9292
'If not specified, merge-base is used as base sha.');
93+
argParser.addFlag(_logTimingArg,
94+
help: 'Logs timing information.\n\n'
95+
'Currently only logs per-package timing for multi-package commands, '
96+
'but more information may be added in the future.');
9397
}
9498

95-
static const String _pluginsArg = 'plugins';
96-
static const String _packagesArg = 'packages';
97-
static const String _shardIndexArg = 'shardIndex';
98-
static const String _shardCountArg = 'shardCount';
99+
static const String _baseShaArg = 'base-sha';
99100
static const String _excludeArg = 'exclude';
100-
static const String _runOnChangedPackagesArg = 'run-on-changed-packages';
101+
static const String _logTimingArg = 'log-timing';
102+
static const String _packagesArg = 'packages';
101103
static const String _packagesForBranchArg = 'packages-for-branch';
102-
static const String _kBaseSha = 'base-sha';
104+
static const String _pluginsArg = 'plugins';
105+
static const String _runOnChangedPackagesArg = 'run-on-changed-packages';
106+
static const String _shardCountArg = 'shardCount';
107+
static const String _shardIndexArg = 'shardIndex';
103108

104109
/// The directory containing the plugin packages.
105110
final Directory packagesDir;
@@ -183,6 +188,11 @@ abstract class PluginCommand extends Command<void> {
183188
return (argResults![key] as List<String>?) ?? <String>[];
184189
}
185190

191+
/// If true, commands should log timing information that might be useful in
192+
/// analyzing their runtime (e.g., the per-package time for multi-package
193+
/// commands).
194+
bool get shouldLogTiming => getBoolArg(_logTimingArg);
195+
186196
void _checkSharding() {
187197
final int? shardIndex = int.tryParse(getStringArg(_shardIndexArg));
188198
final int? shardCount = int.tryParse(getStringArg(_shardCountArg));
@@ -411,11 +421,11 @@ abstract class PluginCommand extends Command<void> {
411421
return entity is Directory && entity.childFile('pubspec.yaml').existsSync();
412422
}
413423

414-
/// Retrieve an instance of [GitVersionFinder] based on `_kBaseSha` and [gitDir].
424+
/// Retrieve an instance of [GitVersionFinder] based on `_baseShaArg` and [gitDir].
415425
///
416426
/// Throws tool exit if [gitDir] nor root directory is a git directory.
417427
Future<GitVersionFinder> retrieveVersionFinder() async {
418-
final String baseSha = getStringArg(_kBaseSha);
428+
final String baseSha = getStringArg(_baseShaArg);
419429

420430
final GitVersionFinder gitVersionFinder =
421431
GitVersionFinder(await gitDir, baseSha);

script/tool/test/common/package_looping_command_test.dart

+41
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import '../util.dart';
2222
import 'plugin_command_test.mocks.dart';
2323

2424
// Constants for colorized output start and end.
25+
const String _startElapsedTimeColor = '\x1B[90m';
2526
const String _startErrorColor = '\x1B[31m';
2627
const String _startHeadingColor = '\x1B[36m';
2728
const String _startSkipColor = '\x1B[90m';
@@ -272,6 +273,46 @@ void main() {
272273
]));
273274
});
274275

276+
test('prints timing info in long-form output when requested', () async {
277+
createFakePlugin('package_a', packagesDir);
278+
createFakePackage('package_b', packagesDir);
279+
280+
final TestPackageLoopingCommand command =
281+
createTestCommand(hasLongOutput: true);
282+
final List<String> output =
283+
await runCommand(command, arguments: <String>['--log-timing']);
284+
285+
const String separator =
286+
'============================================================';
287+
expect(
288+
output,
289+
containsAllInOrder(<String>[
290+
'$_startHeadingColor\n$separator\n|| Running for package_a [@0:00]\n$separator\n$_endColor',
291+
'$_startElapsedTimeColor\n[package_a completed in 0m 0s]$_endColor',
292+
'$_startHeadingColor\n$separator\n|| Running for package_b [@0:00]\n$separator\n$_endColor',
293+
'$_startElapsedTimeColor\n[package_b completed in 0m 0s]$_endColor',
294+
]));
295+
});
296+
297+
test('prints timing info in short-form output when requested', () async {
298+
createFakePlugin('package_a', packagesDir);
299+
createFakePackage('package_b', packagesDir);
300+
301+
final TestPackageLoopingCommand command =
302+
createTestCommand(hasLongOutput: false);
303+
final List<String> output =
304+
await runCommand(command, arguments: <String>['--log-timing']);
305+
306+
expect(
307+
output,
308+
containsAllInOrder(<String>[
309+
'$_startHeadingColor[0:00] Running for package_a...$_endColor',
310+
'$_startHeadingColor[0:00] Running for package_b...$_endColor',
311+
]));
312+
// Short-form output should not include elapsed time.
313+
expect(output, isNot(contains('[package_a completed in 0m 0s]')));
314+
});
315+
275316
test('shows the success message when nothing fails', () async {
276317
createFakePackage('package_a', packagesDir);
277318
createFakePackage('package_b', packagesDir);

script/tool_runner.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@ readonly TOOL_PATH="$REPO_DIR/script/tool/bin/flutter_plugin_tools.dart"
1919
# The tool expects to be run from the repo root.
2020
cd "$REPO_DIR"
2121
# Run from the in-tree source.
22-
dart run "$TOOL_PATH" "$@" --packages-for-branch $PLUGIN_SHARDING
22+
dart run "$TOOL_PATH" "$@" --packages-for-branch --log-timing $PLUGIN_SHARDING

0 commit comments

Comments
 (0)