Commit c74225e0 authored by xster's avatar xster Committed by GitHub

Report timing on failed executions too (#9661)

* handle errors

* review notes
parent 3012cce1
...@@ -31,16 +31,14 @@ enum ExitStatus { ...@@ -31,16 +31,14 @@ enum ExitStatus {
} }
/// [FlutterCommand]s' subclasses' [FlutterCommand.runCommand] can optionally /// [FlutterCommand]s' subclasses' [FlutterCommand.runCommand] can optionally
/// provide a [FlutterCommandResult] to furnish additional information for /// provide a [FlutterCommandResult] to furnish additional information for
/// analytics. /// analytics.
class FlutterCommandResult { class FlutterCommandResult {
FlutterCommandResult( const FlutterCommandResult(
this.exitStatus, { this.exitStatus, {
this.analyticsParameters, this.analyticsParameters,
this.endTimeOverride, this.endTimeOverride,
}) { });
assert(exitStatus != null);
}
final ExitStatus exitStatus; final ExitStatus exitStatus;
...@@ -49,10 +47,10 @@ class FlutterCommandResult { ...@@ -49,10 +47,10 @@ class FlutterCommandResult {
/// Do not add PII. /// Do not add PII.
final List<String> analyticsParameters; final List<String> analyticsParameters;
/// Optional epoch time when the command's non-interactive wait time is /// Optional epoch time when the command's non-interactive wait time is
/// complete during the command's execution. Use to measure user perceivable /// complete during the command's execution. Use to measure user perceivable
/// latency without measuring user interaction time. /// latency without measuring user interaction time.
/// ///
/// [FlutterCommand] will automatically measure and report the command's /// [FlutterCommand] will automatically measure and report the command's
/// complete time if not overriden. /// complete time if not overriden.
final DateTime endTimeOverride; final DateTime endTimeOverride;
...@@ -153,31 +151,38 @@ abstract class FlutterCommand extends Command<Null> { ...@@ -153,31 +151,38 @@ abstract class FlutterCommand extends Command<Null> {
if (flutterUsage.isFirstRun) if (flutterUsage.isFirstRun)
flutterUsage.printWelcome(); flutterUsage.printWelcome();
final FlutterCommandResult commandResult = await verifyThenRunCommand(); FlutterCommandResult commandResult;
try {
final DateTime endTime = clock.now(); commandResult = await verifyThenRunCommand();
printTrace("'flutter $name' took ${getElapsedAsMilliseconds(endTime.difference(startTime))}."); } on ToolExit {
if (usagePath != null) { commandResult = const FlutterCommandResult(ExitStatus.fail);
final List<String> labels = <String>[]; rethrow;
if (commandResult?.exitStatus != null) } finally {
labels.add(getEnumName(commandResult.exitStatus)); final DateTime endTime = clock.now();
if (commandResult?.analyticsParameters?.isNotEmpty ?? false) printTrace('"flutter $name" took ${getElapsedAsMilliseconds(endTime.difference(startTime))}.');
labels.addAll(commandResult.analyticsParameters); if (usagePath != null) {
final List<String> labels = <String>[];
final String label = labels if (commandResult?.exitStatus != null)
.where((String label) => !isBlank(label)) labels.add(getEnumName(commandResult.exitStatus));
.join('-'); if (commandResult?.analyticsParameters?.isNotEmpty ?? false)
flutterUsage.sendTiming( labels.addAll(commandResult.analyticsParameters);
'flutter',
name, final String label = labels
// If the command provides its own end time, use it. Otherwise report .where((String label) => !isBlank(label))
// the duration of the entire execution. .join('-');
(commandResult?.endTimeOverride ?? endTime).difference(startTime), flutterUsage.sendTiming(
// Report in the form of `success-[parameter1-parameter2]`, all of which 'flutter',
// can be null if the command doesn't provide a FlutterCommandResult. name,
label: label == '' ? null : label, // If the command provides its own end time, use it. Otherwise report
); // the duration of the entire execution.
(commandResult?.endTimeOverride ?? endTime).difference(startTime),
// Report in the form of `success-[parameter1-parameter2]`, all of which
// can be null if the command doesn't provide a FlutterCommandResult.
label: label == '' ? null : label,
);
}
} }
} }
/// Perform validation then call [runCommand] to execute the command. /// Perform validation then call [runCommand] to execute the command.
...@@ -202,11 +207,11 @@ abstract class FlutterCommand extends Command<Null> { ...@@ -202,11 +207,11 @@ abstract class FlutterCommand extends Command<Null> {
final String commandPath = await usagePath; final String commandPath = await usagePath;
if (commandPath != null) if (commandPath != null)
flutterUsage.sendCommand(commandPath); flutterUsage.sendCommand(commandPath);
return runCommand(); return await runCommand();
} }
/// Subclasses must implement this to execute the command. /// Subclasses must implement this to execute the command.
/// Optionally provide a [FlutterCommandResult] to send more details about the /// Optionally provide a [FlutterCommandResult] to send more details about the
/// execution for analytics. /// execution for analytics.
Future<FlutterCommandResult> runCommand(); Future<FlutterCommandResult> runCommand();
......
...@@ -6,6 +6,7 @@ import 'dart:async'; ...@@ -6,6 +6,7 @@ import 'dart:async';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/usage.dart'; import 'package:flutter_tools/src/usage.dart';
import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/runner/flutter_command.dart'; import 'package:flutter_tools/src/runner/flutter_command.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
import 'package:quiver/time.dart'; import 'package:quiver/time.dart';
...@@ -59,7 +60,7 @@ void main() { ...@@ -59,7 +60,7 @@ void main() {
verify(clock.now()).called(2); verify(clock.now()).called(2);
expect( expect(
verify(usage.sendTiming(captureAny, captureAny, captureAny, label: captureAny)).captured, verify(usage.sendTiming(captureAny, captureAny, captureAny, label: captureAny)).captured,
<dynamic>['flutter', 'dummy', const Duration(milliseconds: 1000), null] <dynamic>['flutter', 'dummy', const Duration(milliseconds: 1000), null]
); );
}, },
...@@ -72,7 +73,7 @@ void main() { ...@@ -72,7 +73,7 @@ void main() {
// Crash if called a third time which is unexpected. // Crash if called a third time which is unexpected.
mockTimes = <int>[1000, 2000]; mockTimes = <int>[1000, 2000];
final DummyFlutterCommand flutterCommand = final DummyFlutterCommand flutterCommand =
new DummyFlutterCommand(noUsagePath: true); new DummyFlutterCommand(noUsagePath: true);
await flutterCommand.run(); await flutterCommand.run();
verify(clock.now()).called(2); verify(clock.now()).called(2);
...@@ -82,31 +83,57 @@ void main() { ...@@ -82,31 +83,57 @@ void main() {
Clock: () => clock, Clock: () => clock,
Usage: () => usage, Usage: () => usage,
}); });
testUsingContext('report additional FlutterCommandResult data', () async { testUsingContext('report additional FlutterCommandResult data', () async {
// Crash if called a third time which is unexpected. // Crash if called a third time which is unexpected.
mockTimes = <int>[1000, 2000]; mockTimes = <int>[1000, 2000];
final FlutterCommandResult commandResult = new FlutterCommandResult( final FlutterCommandResult commandResult = new FlutterCommandResult(
ExitStatus.fail, ExitStatus.success,
// nulls should be cleaned up. // nulls should be cleaned up.
analyticsParameters: <String> ['blah1', 'blah2', null, 'blah3'], analyticsParameters: <String> ['blah1', 'blah2', null, 'blah3'],
endTimeOverride: new DateTime.fromMillisecondsSinceEpoch(1500) endTimeOverride: new DateTime.fromMillisecondsSinceEpoch(1500)
); );
final DummyFlutterCommand flutterCommand = final DummyFlutterCommand flutterCommand = new DummyFlutterCommand(
new DummyFlutterCommand(flutterCommandResult: commandResult); commandFunction: () async => commandResult
);
await flutterCommand.run(); await flutterCommand.run();
verify(clock.now()).called(2); verify(clock.now()).called(2);
expect( expect(
verify(usage.sendTiming(captureAny, captureAny, captureAny, label: captureAny)).captured, verify(usage.sendTiming(captureAny, captureAny, captureAny, label: captureAny)).captured,
<dynamic>[ <dynamic>[
'flutter', 'flutter',
'dummy', 'dummy',
const Duration(milliseconds: 500), // FlutterCommandResult's end time used instead. const Duration(milliseconds: 500), // FlutterCommandResult's end time used instead.
'fail-blah1-blah2-blah3', 'success-blah1-blah2-blah3',
], ],
); );
},
overrides: <Type, Generator>{
Clock: () => clock,
Usage: () => usage,
});
testUsingContext('report failed execution timing too', () async {
// Crash if called a third time which is unexpected.
mockTimes = <int>[1000, 2000];
final DummyFlutterCommand flutterCommand =
new DummyFlutterCommand(commandFunction: () async { throwToolExit('fail'); });
try {
await flutterCommand.run();
fail('Mock should make this fail');
} on ToolExit {
// Should have still checked time twice.
verify(clock.now()).called(2);
expect(
verify(usage.sendTiming(captureAny, captureAny, captureAny, label: captureAny)).captured,
<dynamic>['flutter', 'dummy', const Duration(milliseconds: 1000), 'fail']
);
}
}, },
overrides: <Type, Generator>{ overrides: <Type, Generator>{
Clock: () => clock, Clock: () => clock,
...@@ -117,16 +144,18 @@ void main() { ...@@ -117,16 +144,18 @@ void main() {
} }
typedef Future<FlutterCommandResult> CommandFunction();
class DummyFlutterCommand extends FlutterCommand { class DummyFlutterCommand extends FlutterCommand {
DummyFlutterCommand({ DummyFlutterCommand({
this.shouldUpdateCache : false, this.shouldUpdateCache : false,
this.noUsagePath : false, this.noUsagePath : false,
this.flutterCommandResult this.commandFunction,
}); });
final bool noUsagePath; final bool noUsagePath;
final FlutterCommandResult flutterCommandResult; final CommandFunction commandFunction;
@override @override
final bool shouldUpdateCache; final bool shouldUpdateCache;
...@@ -142,10 +171,10 @@ class DummyFlutterCommand extends FlutterCommand { ...@@ -142,10 +171,10 @@ class DummyFlutterCommand extends FlutterCommand {
@override @override
Future<FlutterCommandResult> runCommand() async { Future<FlutterCommandResult> runCommand() async {
return flutterCommandResult; return commandFunction == null ? null : commandFunction();
} }
} }
class MockCache extends Mock implements Cache {} class MockCache extends Mock implements Cache {}
class MockUsage extends Mock implements Usage {} class MockUsage extends Mock implements Usage {}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment