Unverified Commit ef146f63 authored by Zachary Anderson's avatar Zachary Anderson Committed by GitHub

[flutter_tool] Clean up usage events and custom dimensions (#36785)

parent 24f483cf
...@@ -15,8 +15,7 @@ import 'package:flutter_tools/src/context_runner.dart'; ...@@ -15,8 +15,7 @@ import 'package:flutter_tools/src/context_runner.dart';
import 'package:flutter_tools/src/devfs.dart'; import 'package:flutter_tools/src/devfs.dart';
import 'package:flutter_tools/src/bundle.dart'; import 'package:flutter_tools/src/bundle.dart';
import 'package:flutter_tools/src/globals.dart'; import 'package:flutter_tools/src/globals.dart';
import 'package:flutter_tools/src/reporting/disabled_usage.dart'; import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:flutter_tools/src/reporting/usage.dart';
const String _kOptionPackages = 'packages'; const String _kOptionPackages = 'packages';
const String _kOptionAsset = 'asset-dir'; const String _kOptionAsset = 'asset-dir';
......
...@@ -18,8 +18,7 @@ import 'package:flutter_tools/src/dart/package_map.dart'; ...@@ -18,8 +18,7 @@ import 'package:flutter_tools/src/dart/package_map.dart';
import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/globals.dart'; import 'package:flutter_tools/src/globals.dart';
import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/reporting/disabled_usage.dart'; import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:flutter_tools/src/reporting/usage.dart';
import 'package:flutter_tools/src/test/coverage_collector.dart'; import 'package:flutter_tools/src/test/coverage_collector.dart';
import 'package:flutter_tools/src/test/runner.dart'; import 'package:flutter_tools/src/test/runner.dart';
......
...@@ -19,8 +19,7 @@ import 'src/base/utils.dart'; ...@@ -19,8 +19,7 @@ import 'src/base/utils.dart';
import 'src/context_runner.dart'; import 'src/context_runner.dart';
import 'src/doctor.dart'; import 'src/doctor.dart';
import 'src/globals.dart'; import 'src/globals.dart';
import 'src/reporting/crash_reporting.dart'; import 'src/reporting/reporting.dart';
import 'src/reporting/usage.dart';
import 'src/runner/flutter_command.dart'; import 'src/runner/flutter_command.dart';
import 'src/runner/flutter_command_runner.dart'; import 'src/runner/flutter_command_runner.dart';
import 'src/version.dart'; import 'src/version.dart';
......
...@@ -24,8 +24,7 @@ import '../features.dart'; ...@@ -24,8 +24,7 @@ import '../features.dart';
import '../flutter_manifest.dart'; import '../flutter_manifest.dart';
import '../globals.dart'; import '../globals.dart';
import '../project.dart'; import '../project.dart';
import '../reporting/usage.dart'; import '../reporting/reporting.dart';
import '../runner/flutter_command.dart';
import 'android_sdk.dart'; import 'android_sdk.dart';
import 'android_studio.dart'; import 'android_studio.dart';
...@@ -747,11 +746,7 @@ Future<void> _buildGradleProjectV2( ...@@ -747,11 +746,7 @@ Future<void> _buildGradleProjectV2(
printError('The Gradle failure may have been because of AndroidX incompatibilities in this Flutter app.'); printError('The Gradle failure may have been because of AndroidX incompatibilities in this Flutter app.');
printError('See https://goo.gl/CP92wY for more information on the problem and how to fix it.'); printError('See https://goo.gl/CP92wY for more information on the problem and how to fix it.');
printError('*******************************************************************************************'); printError('*******************************************************************************************');
String commandName = ''; BuildEvent('android-x-failure').send();
if (FlutterCommand.current != null) {
commandName = '-${FlutterCommand.current.name}';
}
flutterUsage.sendEvent('build$commandName', 'android-x-failure');
} }
throwToolExit('Gradle task $assembleTask failed with exit code $exitCode', exitCode: exitCode); throwToolExit('Gradle task $assembleTask failed with exit code $exitCode', exitCode: exitCode);
} }
......
...@@ -14,7 +14,7 @@ import '../dart/package_map.dart'; ...@@ -14,7 +14,7 @@ import '../dart/package_map.dart';
import '../globals.dart'; import '../globals.dart';
import '../macos/xcode.dart'; import '../macos/xcode.dart';
import '../project.dart'; import '../project.dart';
import '../reporting/usage.dart'; import '../reporting/reporting.dart';
import 'context.dart'; import 'context.dart';
import 'file_system.dart'; import 'file_system.dart';
......
...@@ -9,7 +9,7 @@ import '../base/context.dart'; ...@@ -9,7 +9,7 @@ import '../base/context.dart';
import '../base/os.dart'; import '../base/os.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../project.dart'; import '../project.dart';
import '../reporting/usage.dart'; import '../reporting/reporting.dart';
import '../runner/flutter_command.dart' show DevelopmentArtifact, FlutterCommandResult; import '../runner/flutter_command.dart' show DevelopmentArtifact, FlutterCommandResult;
import 'build.dart'; import 'build.dart';
...@@ -38,20 +38,20 @@ class BuildAarCommand extends BuildSubCommand { ...@@ -38,20 +38,20 @@ class BuildAarCommand extends BuildSubCommand {
final String name = 'aar'; final String name = 'aar';
@override @override
Future<Map<String, String>> get usageValues async { Future<Map<CustomDimensions, String>> get usageValues async {
final Map<String, String> usage = <String, String>{}; final Map<CustomDimensions, String> usage = <CustomDimensions, String>{};
final FlutterProject futterProject = _getProject(); final FlutterProject futterProject = _getProject();
if (futterProject == null) { if (futterProject == null) {
return usage; return usage;
} }
if (futterProject.manifest.isModule) { if (futterProject.manifest.isModule) {
usage[kCommandBuildAarProjectType] = 'module'; usage[CustomDimensions.commandBuildAarProjectType] = 'module';
} else if (futterProject.manifest.isPlugin) { } else if (futterProject.manifest.isPlugin) {
usage[kCommandBuildAarProjectType] = 'plugin'; usage[CustomDimensions.commandBuildAarProjectType] = 'plugin';
} else { } else {
usage[kCommandBuildAarProjectType] = 'app'; usage[CustomDimensions.commandBuildAarProjectType] = 'app';
} }
usage[kCommandBuildAarTargetPlatform] = usage[CustomDimensions.commandBuildAarTargetPlatform] =
(argResults['target-platform'] as List<String>).join(','); (argResults['target-platform'] as List<String>).join(',');
return usage; return usage;
} }
......
...@@ -10,7 +10,7 @@ import '../build_info.dart'; ...@@ -10,7 +10,7 @@ import '../build_info.dart';
import '../bundle.dart'; import '../bundle.dart';
import '../features.dart'; import '../features.dart';
import '../project.dart'; import '../project.dart';
import '../reporting/usage.dart'; import '../reporting/reporting.dart';
import '../runner/flutter_command.dart' show FlutterOptions, FlutterCommandResult; import '../runner/flutter_command.dart' show FlutterOptions, FlutterCommandResult;
import 'build.dart'; import 'build.dart';
...@@ -74,17 +74,15 @@ class BuildBundleCommand extends BuildSubCommand { ...@@ -74,17 +74,15 @@ class BuildBundleCommand extends BuildSubCommand {
' iOS runtimes.'; ' iOS runtimes.';
@override @override
Future<Map<String, String>> get usageValues async { Future<Map<CustomDimensions, String>> get usageValues async {
final String projectDir = fs.file(targetFile).parent.parent.path; final String projectDir = fs.file(targetFile).parent.parent.path;
final FlutterProject futterProject = FlutterProject.fromPath(projectDir); final FlutterProject futterProject = FlutterProject.fromPath(projectDir);
if (futterProject == null) { if (futterProject == null) {
return const <String, String>{}; return const <CustomDimensions, String>{};
} }
return <CustomDimensions, String>{
return <String, String>{ CustomDimensions.commandBuildBundleTargetPlatform: argResults['target-platform'],
kCommandBuildBundleTargetPlatform: argResults['target-platform'], CustomDimensions.commandBuildBundleIsModule: '${futterProject.isModule}'
kCommandBuildBundleIsModule: '${futterProject.isModule}'
}; };
} }
......
...@@ -11,7 +11,7 @@ import '../base/file_system.dart'; ...@@ -11,7 +11,7 @@ import '../base/file_system.dart';
import '../convert.dart'; import '../convert.dart';
import '../features.dart'; import '../features.dart';
import '../globals.dart'; import '../globals.dart';
import '../reporting/usage.dart'; import '../reporting/reporting.dart';
import '../runner/flutter_command.dart'; import '../runner/flutter_command.dart';
import '../version.dart'; import '../version.dart';
......
...@@ -23,7 +23,7 @@ import '../doctor.dart'; ...@@ -23,7 +23,7 @@ import '../doctor.dart';
import '../features.dart'; import '../features.dart';
import '../globals.dart'; import '../globals.dart';
import '../project.dart'; import '../project.dart';
import '../reporting/usage.dart'; import '../reporting/reporting.dart';
import '../runner/flutter_command.dart'; import '../runner/flutter_command.dart';
import '../template.dart'; import '../template.dart';
import '../version.dart'; import '../version.dart';
...@@ -165,11 +165,11 @@ class CreateCommand extends FlutterCommand { ...@@ -165,11 +165,11 @@ class CreateCommand extends FlutterCommand {
String get invocation => '${runner.executableName} $name <output directory>'; String get invocation => '${runner.executableName} $name <output directory>';
@override @override
Future<Map<String, String>> get usageValues async { Future<Map<CustomDimensions, String>> get usageValues async {
return <String, String>{ return <CustomDimensions, String>{
kCommandCreateProjectType: argResults['template'], CustomDimensions.commandCreateProjectType: argResults['template'],
kCommandCreateAndroidLanguage: argResults['android-language'], CustomDimensions.commandCreateAndroidLanguage: argResults['android-language'],
kCommandCreateIosLanguage: argResults['ios-language'], CustomDimensions.commandCreateIosLanguage: argResults['ios-language'],
}; };
} }
......
...@@ -9,7 +9,7 @@ import '../base/os.dart'; ...@@ -9,7 +9,7 @@ import '../base/os.dart';
import '../cache.dart'; import '../cache.dart';
import '../dart/pub.dart'; import '../dart/pub.dart';
import '../project.dart'; import '../project.dart';
import '../reporting/usage.dart'; import '../reporting/reporting.dart';
import '../runner/flutter_command.dart'; import '../runner/flutter_command.dart';
class PackagesCommand extends FlutterCommand { class PackagesCommand extends FlutterCommand {
...@@ -71,8 +71,8 @@ class PackagesGetCommand extends FlutterCommand { ...@@ -71,8 +71,8 @@ class PackagesGetCommand extends FlutterCommand {
} }
@override @override
Future<Map<String, String>> get usageValues async { Future<Map<CustomDimensions, String>> get usageValues async {
final Map<String, String> usageValues = <String, String>{}; final Map<CustomDimensions, String> usageValues = <CustomDimensions, String>{};
final String workingDirectory = argResults.rest.length == 1 ? argResults.rest[0] : null; final String workingDirectory = argResults.rest.length == 1 ? argResults.rest[0] : null;
final String target = findProjectRoot(workingDirectory); final String target = findProjectRoot(workingDirectory);
if (target == null) { if (target == null) {
...@@ -82,11 +82,11 @@ class PackagesGetCommand extends FlutterCommand { ...@@ -82,11 +82,11 @@ class PackagesGetCommand extends FlutterCommand {
final bool hasPlugins = await rootProject.flutterPluginsFile.exists(); final bool hasPlugins = await rootProject.flutterPluginsFile.exists();
if (hasPlugins) { if (hasPlugins) {
final int numberOfPlugins = (await rootProject.flutterPluginsFile.readAsLines()).length; final int numberOfPlugins = (await rootProject.flutterPluginsFile.readAsLines()).length;
usageValues[kCommandPackagesNumberPlugins] = '$numberOfPlugins'; usageValues[CustomDimensions.commandPackagesNumberPlugins] = '$numberOfPlugins';
} else { } else {
usageValues[kCommandPackagesNumberPlugins] = '0'; usageValues[CustomDimensions.commandPackagesNumberPlugins] = '0';
} }
usageValues[kCommandPackagesProjectModule] = '${rootProject.isModule}'; usageValues[CustomDimensions.commandPackagesProjectModule] = '${rootProject.isModule}';
return usageValues; return usageValues;
} }
...@@ -100,11 +100,11 @@ class PackagesGetCommand extends FlutterCommand { ...@@ -100,11 +100,11 @@ class PackagesGetCommand extends FlutterCommand {
checkLastModified: false, checkLastModified: false,
); );
pubGetTimer.stop(); pubGetTimer.stop();
flutterUsage.sendEvent('packages-pub-get', 'success'); PubGetEvent(success: true).send();
flutterUsage.sendTiming('packages-pub-get', 'success', pubGetTimer.elapsed); flutterUsage.sendTiming('packages-pub-get', 'success', pubGetTimer.elapsed);
} catch (_) { } catch (_) {
pubGetTimer.stop(); pubGetTimer.stop();
flutterUsage.sendEvent('packages-pub-get', 'failure'); PubGetEvent(success: false).send();
flutterUsage.sendTiming('packages-pub-get', 'failure', pubGetTimer.elapsed); flutterUsage.sendTiming('packages-pub-get', 'failure', pubGetTimer.elapsed);
rethrow; rethrow;
} }
......
...@@ -17,7 +17,7 @@ import '../features.dart'; ...@@ -17,7 +17,7 @@ import '../features.dart';
import '../globals.dart'; import '../globals.dart';
import '../macos/xcode.dart'; import '../macos/xcode.dart';
import '../project.dart'; import '../project.dart';
import '../reporting/usage.dart'; import '../reporting/reporting.dart';
import '../resident_runner.dart'; import '../resident_runner.dart';
import '../resident_web_runner.dart'; import '../resident_web_runner.dart';
import '../run_cold.dart'; import '../run_cold.dart';
...@@ -198,7 +198,7 @@ class RunCommand extends RunCommandBase { ...@@ -198,7 +198,7 @@ class RunCommand extends RunCommandBase {
} }
@override @override
Future<Map<String, String>> get usageValues async { Future<Map<CustomDimensions, String>> get usageValues async {
String deviceType, deviceOsVersion; String deviceType, deviceOsVersion;
bool isEmulator; bool isEmulator;
...@@ -227,13 +227,13 @@ class RunCommand extends RunCommandBase { ...@@ -227,13 +227,13 @@ class RunCommand extends RunCommandBase {
hostLanguage.add(iosProject.isSwift ? 'swift' : 'objc'); hostLanguage.add(iosProject.isSwift ? 'swift' : 'objc');
} }
return <String, String>{ return <CustomDimensions, String>{
kCommandRunIsEmulator: '$isEmulator', CustomDimensions.commandRunIsEmulator: '$isEmulator',
kCommandRunTargetName: deviceType, CustomDimensions.commandRunTargetName: deviceType,
kCommandRunTargetOsVersion: deviceOsVersion, CustomDimensions.commandRunTargetOsVersion: deviceOsVersion,
kCommandRunModeName: modeName, CustomDimensions.commandRunModeName: modeName,
kCommandRunProjectModule: '${FlutterProject.current().isModule}', CustomDimensions.commandRunProjectModule: '${FlutterProject.current().isModule}',
kCommandRunProjectHostLanguage: hostLanguage.join(','), CustomDimensions.commandRunProjectHostLanguage: hostLanguage.join(','),
}; };
} }
......
...@@ -42,7 +42,7 @@ import 'macos/cocoapods_validator.dart'; ...@@ -42,7 +42,7 @@ import 'macos/cocoapods_validator.dart';
import 'macos/macos_workflow.dart'; import 'macos/macos_workflow.dart';
import 'macos/xcode.dart'; import 'macos/xcode.dart';
import 'macos/xcode_validator.dart'; import 'macos/xcode_validator.dart';
import 'reporting/usage.dart'; import 'reporting/reporting.dart';
import 'run_hot.dart'; import 'run_hot.dart';
import 'version.dart'; import 'version.dart';
import 'web/chrome.dart'; import 'web/chrome.dart';
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; import 'dart:async';
import 'package:json_rpc_2/json_rpc_2.dart' as rpc; import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
......
...@@ -31,7 +31,7 @@ import 'macos/cocoapods_validator.dart'; ...@@ -31,7 +31,7 @@ import 'macos/cocoapods_validator.dart';
import 'macos/macos_workflow.dart'; import 'macos/macos_workflow.dart';
import 'macos/xcode_validator.dart'; import 'macos/xcode_validator.dart';
import 'proxy_validator.dart'; import 'proxy_validator.dart';
import 'reporting/usage.dart'; import 'reporting/reporting.dart';
import 'tester/flutter_tester.dart'; import 'tester/flutter_tester.dart';
import 'version.dart'; import 'version.dart';
import 'vscode/vscode_validator.dart'; import 'vscode/vscode_validator.dart';
...@@ -236,7 +236,7 @@ class Doctor { ...@@ -236,7 +236,7 @@ class Doctor {
break; break;
} }
flutterUsage.sendEvent('doctorResult.${validator.runtimeType.toString()}', result.typeStr); DoctorResultEvent(validator: validator, result: result).send();
if (result.statusInfo != null) { if (result.statusInfo != null) {
printStatus('${result.coloredLeadingBox} ${validator.title} (${result.statusInfo})', printStatus('${result.coloredLeadingBox} ${validator.title} (${result.statusInfo})',
......
...@@ -16,7 +16,7 @@ import '../convert.dart'; ...@@ -16,7 +16,7 @@ import '../convert.dart';
import '../devfs.dart'; import '../devfs.dart';
import '../globals.dart'; import '../globals.dart';
import '../project.dart'; import '../project.dart';
import '../reporting/usage.dart'; import '../reporting/reporting.dart';
import 'fuchsia_pm.dart'; import 'fuchsia_pm.dart';
import 'fuchsia_sdk.dart'; import 'fuchsia_sdk.dart';
......
...@@ -24,7 +24,7 @@ import '../globals.dart'; ...@@ -24,7 +24,7 @@ import '../globals.dart';
import '../macos/cocoapod_utils.dart'; import '../macos/cocoapod_utils.dart';
import '../macos/xcode.dart'; import '../macos/xcode.dart';
import '../project.dart'; import '../project.dart';
import '../reporting/usage.dart'; import '../reporting/reporting.dart';
import '../services.dart'; import '../services.dart';
import 'code_signing.dart'; import 'code_signing.dart';
import 'xcodeproj.dart'; import 'xcodeproj.dart';
...@@ -472,13 +472,10 @@ Future<void> diagnoseXcodeBuildFailure(XcodeBuildResult result) async { ...@@ -472,13 +472,10 @@ Future<void> diagnoseXcodeBuildFailure(XcodeBuildResult result) async {
if (result.xcodeBuildExecution != null && if (result.xcodeBuildExecution != null &&
result.xcodeBuildExecution.buildForPhysicalDevice && result.xcodeBuildExecution.buildForPhysicalDevice &&
result.stdout?.toUpperCase()?.contains('BITCODE') == true) { result.stdout?.toUpperCase()?.contains('BITCODE') == true) {
flutterUsage.sendEvent( BuildEvent('xcode-bitcode-failure',
'Xcode', command: result.xcodeBuildExecution.buildCommands.toString(),
'bitcode-failure', settings: result.xcodeBuildExecution.buildSettings.toString(),
parameters: <String, String>{ ).send();
'build-commands': result.xcodeBuildExecution.buildCommands.toString(),
'build-settings': result.xcodeBuildExecution.buildSettings.toString(),
});
} }
if (result.xcodeBuildExecution != null && if (result.xcodeBuildExecution != null &&
......
...@@ -13,7 +13,7 @@ import '../cache.dart'; ...@@ -13,7 +13,7 @@ import '../cache.dart';
import '../convert.dart'; import '../convert.dart';
import '../globals.dart'; import '../globals.dart';
import '../project.dart'; import '../project.dart';
import '../reporting/usage.dart'; import '../reporting/reporting.dart';
/// Builds the Linux project through the Makefile. /// Builds the Linux project through the Makefile.
Future<void> buildLinux(LinuxProject linuxProject, BuildInfo buildInfo, {String target = 'lib/main.dart'}) async { Future<void> buildLinux(LinuxProject linuxProject, BuildInfo buildInfo, {String target = 'lib/main.dart'}) async {
......
...@@ -12,7 +12,7 @@ import '../convert.dart'; ...@@ -12,7 +12,7 @@ import '../convert.dart';
import '../globals.dart'; import '../globals.dart';
import '../ios/xcodeproj.dart'; import '../ios/xcodeproj.dart';
import '../project.dart'; import '../project.dart';
import '../reporting/usage.dart'; import '../reporting/reporting.dart';
import 'cocoapod_utils.dart'; import 'cocoapod_utils.dart';
......
...@@ -2,17 +2,7 @@ ...@@ -2,17 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; part of reporting;
import 'package:http/http.dart' as http;
import 'package:meta/meta.dart';
import '../base/io.dart';
import '../base/os.dart';
import '../base/platform.dart';
import '../globals.dart';
import 'usage.dart';
/// Tells crash backend that the error is from the Flutter CLI. /// Tells crash backend that the error is from the Flutter CLI.
const String _kProductId = 'Flutter_Tools'; const String _kProductId = 'Flutter_Tools';
......
...@@ -2,9 +2,7 @@ ...@@ -2,9 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; part of reporting;
import 'usage.dart';
class DisabledUsage implements Usage { class DisabledUsage implements Usage {
@override @override
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
part of reporting;
/// A generic usage even that does not involve custom dimensions.
///
/// If sending values for custom dimensions is required, extend this class as
/// below.
class UsageEvent {
UsageEvent(this.category, this.parameter);
final String category;
final String parameter;
void send() {
flutterUsage.sendEvent(category, parameter);
}
}
/// A usage event related to hot reload/restart.
///
/// On a successful hot reload, we collect stats that help understand scale of
/// the update. For example, [syncedLibraryCount]/[finalLibraryCount] indicates
/// how many libraries were affected by the hot reload request. Relation of
/// [invalidatedSourcesCount] to [syncedLibraryCount] should help understand
/// sync/transfer "overhead" of updating this number of source files.
class HotEvent extends UsageEvent {
HotEvent(String parameter, {
@required this.targetPlatform,
@required this.sdkName,
@required this.emulator,
@required this.fullRestart,
this.reason,
this.finalLibraryCount,
this.syncedLibraryCount,
this.syncedClassesCount,
this.syncedProceduresCount,
this.syncedBytes,
this.invalidatedSourcesCount,
this.transferTimeInMs,
this.overallTimeInMs,
}) : super('hot', parameter);
final String reason;
final String targetPlatform;
final String sdkName;
final bool emulator;
final bool fullRestart;
final int finalLibraryCount;
final int syncedLibraryCount;
final int syncedClassesCount;
final int syncedProceduresCount;
final int syncedBytes;
final int invalidatedSourcesCount;
final int transferTimeInMs;
final int overallTimeInMs;
@override
void send() {
final Map<String, String> parameters = _useCdKeys(<CustomDimensions, String>{
CustomDimensions.hotEventTargetPlatform: targetPlatform,
CustomDimensions.hotEventSdkName: sdkName,
CustomDimensions.hotEventEmulator: emulator.toString(),
CustomDimensions.hotEventFullRestart: fullRestart.toString(),
if (reason != null)
CustomDimensions.hotEventReason: reason,
if (finalLibraryCount != null)
CustomDimensions.hotEventFinalLibraryCount: finalLibraryCount.toString(),
if (syncedLibraryCount != null)
CustomDimensions.hotEventSyncedLibraryCount: syncedLibraryCount.toString(),
if (syncedClassesCount != null)
CustomDimensions.hotEventSyncedClassesCount: syncedClassesCount.toString(),
if (syncedProceduresCount != null)
CustomDimensions.hotEventSyncedProceduresCount: syncedProceduresCount.toString(),
if (syncedBytes != null)
CustomDimensions.hotEventSyncedBytes: syncedBytes.toString(),
if (invalidatedSourcesCount != null)
CustomDimensions.hotEventInvalidatedSourcesCount: invalidatedSourcesCount.toString(),
if (transferTimeInMs != null)
CustomDimensions.hotEventTransferTimeInMs: transferTimeInMs.toString(),
if (overallTimeInMs != null)
CustomDimensions.hotEventOverallTimeInMs: overallTimeInMs.toString(),
});
flutterUsage.sendEvent(category, parameter, parameters: parameters);
}
}
/// An event that reports the result of a [DoctorValidator]
class DoctorResultEvent extends UsageEvent {
DoctorResultEvent({
@required DoctorValidator validator,
@required ValidationResult result
}) : super('doctorResult.${validator.runtimeType.toString()}',
result.typeStr);
// TODO(zra): Override send() to detect a GroupedValidator and send separate
// events for each sub-validator.
}
/// An event that reports success or failure of a pub get.
class PubGetEvent extends UsageEvent {
PubGetEvent({
@required bool success,
}) : super('packages-pub-get', success ? 'success' : 'failure');
}
/// An event that reports something about a build.
class BuildEvent extends UsageEvent {
BuildEvent(String parameter, {
this.command,
this.settings,
}) : super(
'build' +
(FlutterCommand.current == null ? '' : '-${FlutterCommand.current.name}'),
parameter);
final String command;
final String settings;
@override
void send() {
final Map<String, String> parameters = _useCdKeys(<CustomDimensions, String>{
if (command != null)
CustomDimensions.buildEventCommand: command,
if (settings != null)
CustomDimensions.buildEventSettings: settings,
});
flutterUsage.sendEvent(category, parameter, parameters: parameters);
}
}
/// An event that reports the result of a top-level command.
class CommandResultEvent extends UsageEvent {
CommandResultEvent(String commandPath, FlutterCommandResult result)
: super(commandPath, result?.toString() ?? 'unspecified');
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
library reporting;
import 'dart:async';
import 'package:http/http.dart' as http;
import 'package:meta/meta.dart';
import 'package:usage/usage_io.dart';
import '../base/config.dart';
import '../base/context.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../base/os.dart';
import '../base/platform.dart';
import '../base/time.dart';
import '../base/utils.dart';
import '../doctor.dart';
import '../features.dart';
import '../globals.dart';
import '../runner/flutter_command.dart';
import '../version.dart';
part 'crash_reporting.dart';
part 'disabled_usage.dart';
part 'events.dart';
part 'usage.dart';
...@@ -2,78 +2,146 @@ ...@@ -2,78 +2,146 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; part of reporting;
import 'package:meta/meta.dart';
import 'package:usage/usage_io.dart';
import '../base/config.dart';
import '../base/context.dart';
import '../base/file_system.dart';
import '../base/os.dart';
import '../base/platform.dart';
import '../base/time.dart';
import '../base/utils.dart';
import '../features.dart';
import '../globals.dart';
import '../version.dart';
const String _kFlutterUA = 'UA-67589403-6'; const String _kFlutterUA = 'UA-67589403-6';
// Attached to all `Usage.sendCommand` and `Usage.sendEvent`. /// The collection of custom dimensions understood by the analytics backend.
const String _kLocalTimeParameter = 'cd33'; /// When adding to this list, first ensure that the custom dimension is
/// defined in the backend, or will be defined shortly after the relevent PR
/// lands.
enum CustomDimensions {
sessionHostOsDetails, // cd1
sessionChannelName, // cd2
commandRunIsEmulator, // cd3
commandRunTargetName, // cd4
hotEventReason, // cd5
hotEventFinalLibraryCount, // cd6
hotEventSyncedLibraryCount, // cd7
hotEventSyncedClassesCount, // cd8
hotEventSyncedProceduresCount, // cd9
hotEventSyncedBytes, // cd10
hotEventInvalidatedSourcesCount, // cd11
hotEventTransferTimeInMs, // cd12
hotEventOverallTimeInMs, // cd13
commandRunProjectType, // cd14
commandRunProjectHostLanguage, // cd15
commandCreateAndroidLanguage, // cd16
commandCreateIosLanguage, // cd17
commandRunProjectModule, // cd18
commandCreateProjectType, // cd19
commandPackagesNumberPlugins, // cd20
commandPackagesProjectModule, // cd21
commandRunTargetOsVersion, // cd22
commandRunModeName, // cd23
commandBuildBundleTargetPlatform, // cd24
commandBuildBundleIsModule, // cd25
commandResult, // cd26
hotEventTargetPlatform, // cd27
hotEventSdkName, // cd28
hotEventEmulator, // cd29
hotEventFullRestart, // cd30
commandHasTerminal, // cd31
enabledFlutterFeatures, // cd32
localTime, // cd33
commandBuildAarTargetPlatform, // cd34
commandBuildAarProjectType, // cd35
buildEventCommand, // cd36
buildEventSettings, // cd37
}
const String kSessionHostOsDetails = 'cd1'; String cdKey(CustomDimensions cd) => 'cd${cd.index + 1}';
const String kSessionChannelName = 'cd2';
const String kEventReloadReasonParameterName = 'cd5'; Map<String, String> _useCdKeys(Map<CustomDimensions, String> parameters) {
const String kEventReloadFinalLibraryCount = 'cd6'; return parameters.map((CustomDimensions k, String v) =>
const String kEventReloadSyncedLibraryCount = 'cd7'; MapEntry<String, String>(cdKey(k), v));
const String kEventReloadSyncedClassesCount = 'cd8'; }
const String kEventReloadSyncedProceduresCount = 'cd9';
const String kEventReloadSyncedBytes = 'cd10';
const String kEventReloadInvalidatedSourcesCount = 'cd11';
const String kEventReloadTransferTimeInMs = 'cd12';
const String kEventReloadOverallTimeInMs = 'cd13';
const String kCommandRunIsEmulator = 'cd3'; Usage get flutterUsage => Usage.instance;
const String kCommandRunTargetName = 'cd4';
const String kCommandRunProjectType = 'cd14';
const String kCommandRunProjectHostLanguage = 'cd15';
const String kCommandRunProjectModule = 'cd18';
const String kCommandRunTargetOsVersion = 'cd22';
const String kCommandRunModeName = 'cd23';
const String kCommandCreateAndroidLanguage = 'cd16'; abstract class Usage {
const String kCommandCreateIosLanguage = 'cd17'; factory Usage({
const String kCommandCreateProjectType = 'cd19'; String settingsName = 'flutter',
String versionOverride,
String configDirOverride
}) => _UsageImpl(settingsName: settingsName,
versionOverride: versionOverride,
configDirOverride: configDirOverride);
const String kCommandPackagesNumberPlugins = 'cd20'; /// Returns [Usage] active in the current app context.
const String kCommandPackagesProjectModule = 'cd21'; static Usage get instance => context.get<Usage>();
const String kCommandBuildBundleTargetPlatform = 'cd24'; /// Uses the global [Usage] instance to send a 'command' to analytics.
const String kCommandBuildBundleIsModule = 'cd25'; static void command(String command, {
Map<CustomDimensions, String> parameters,
}) => flutterUsage.sendCommand(command, parameters: _useCdKeys(parameters));
const String kCommandResult = 'cd26'; /// Whether this is the first run of the tool.
const String kCommandHasTerminal = 'cd31'; bool get isFirstRun;
const String kCommandBuildAarTargetPlatform = 'cd34'; /// Whether analytics reporting should be supressed.
const String kCommandBuildAarProjectType = 'cd35'; bool get suppressAnalytics;
const String reloadExceptionTargetPlatform = 'cd27'; /// Suppress analytics for this session.
const String reloadExceptionSdkName = 'cd28'; set suppressAnalytics(bool value);
const String reloadExceptionEmulator = 'cd29';
const String reloadExceptionFullRestart = 'cd30';
const String enabledFlutterFeatures = 'cd32'; /// Whether analytics reporting is enabled.
// Next ID: cd36 bool get enabled;
Usage get flutterUsage => Usage.instance; /// Enable or disable reporting analytics.
set enabled(bool value);
/// A stable randomly generated UUID used to deduplicate multiple identical
/// reports coming from the same computer.
String get clientId;
/// Sends a 'command' to the underlying analytics implementation.
///
/// Note that using [command] above is preferred to ensure that the parameter
/// keys are well-defined in [CustomDimensions] above.
void sendCommand(String command, {
Map<String, String> parameters
});
/// Sends an 'event' to the underlying analytics implementation.
///
/// Note that this method should not be used directly, instead see the
/// event types defined in this directory in events.dart.
@visibleForOverriding
@visibleForTesting
void sendEvent(String category, String parameter, {
Map<String, String> parameters
});
/// Sends timing information to the underlying analytics implementation.
void sendTiming(String category, String variableName, Duration duration, {
String label
});
/// Sends an exception to the underlying analytics implementation.
void sendException(dynamic exception);
class Usage { /// Fires whenever analytics data is sent over the network.
@visibleForTesting
Stream<Map<String, dynamic>> get onSend;
/// Returns when the last analytics event has been sent, or after a fixed
/// (short) delay, whichever is less.
Future<void> ensureAnalyticsSent();
/// Prints a welcome message that informs the tool user about the collection
/// of anonymous usage information.
void printWelcome();
}
class _UsageImpl implements Usage {
/// Create a new Usage instance; [versionOverride] and [configDirOverride] are /// Create a new Usage instance; [versionOverride] and [configDirOverride] are
/// used for testing. /// used for testing.
Usage({ String settingsName = 'flutter', String versionOverride, String configDirOverride}) { _UsageImpl({
String settingsName = 'flutter',
String versionOverride,
String configDirOverride
}) {
final FlutterVersion flutterVersion = FlutterVersion.instance; final FlutterVersion flutterVersion = FlutterVersion.instance;
final String version = versionOverride ?? flutterVersion.getVersionString(redactUnknownBranches: true); final String version = versionOverride ?? flutterVersion.getVersionString(redactUnknownBranches: true);
...@@ -90,9 +158,10 @@ class Usage { ...@@ -90,9 +158,10 @@ class Usage {
LogToFileAnalytics(logFilePath); LogToFileAnalytics(logFilePath);
// Report a more detailed OS version string than package:usage does by default. // Report a more detailed OS version string than package:usage does by default.
_analytics.setSessionValue(kSessionHostOsDetails, os.name); _analytics.setSessionValue(cdKey(CustomDimensions.sessionHostOsDetails), os.name);
// Send the branch name as the "channel". // Send the branch name as the "channel".
_analytics.setSessionValue(kSessionChannelName, flutterVersion.getBranchName(redactUnknownBranches: true)); _analytics.setSessionValue(cdKey(CustomDimensions.sessionChannelName),
flutterVersion.getBranchName(redactUnknownBranches: true));
// For each flutter experimental feature, record a session value in a comma // For each flutter experimental feature, record a session value in a comma
// separated list. // separated list.
final String enabledFeatures = allFeatures final String enabledFeatures = allFeatures
...@@ -102,7 +171,7 @@ class Usage { ...@@ -102,7 +171,7 @@ class Usage {
}) })
.map((Feature feature) => feature.configSetting) .map((Feature feature) => feature.configSetting)
.join(','); .join(',');
_analytics.setSessionValue(enabledFlutterFeatures, enabledFeatures); _analytics.setSessionValue(cdKey(CustomDimensions.enabledFlutterFeatures), enabledFeatures);
// Record the host as the application installer ID - the context that flutter_tools is running in. // Record the host as the application installer ID - the context that flutter_tools is running in.
if (platform.environment.containsKey('FLUTTER_HOST')) { if (platform.environment.containsKey('FLUTTER_HOST')) {
...@@ -119,34 +188,34 @@ class Usage { ...@@ -119,34 +188,34 @@ class Usage {
} }
} }
/// Returns [Usage] active in the current app context.
static Usage get instance => context.get<Usage>();
Analytics _analytics; Analytics _analytics;
bool _printedWelcome = false; bool _printedWelcome = false;
bool _suppressAnalytics = false; bool _suppressAnalytics = false;
@override
bool get isFirstRun => _analytics.firstRun; bool get isFirstRun => _analytics.firstRun;
bool get enabled => _analytics.enabled; @override
bool get suppressAnalytics => _suppressAnalytics || _analytics.firstRun; bool get suppressAnalytics => _suppressAnalytics || _analytics.firstRun;
/// Suppress analytics for this session. @override
set suppressAnalytics(bool value) { set suppressAnalytics(bool value) {
_suppressAnalytics = value; _suppressAnalytics = value;
} }
/// Enable or disable reporting analytics. @override
bool get enabled => _analytics.enabled;
@override
set enabled(bool value) { set enabled(bool value) {
_analytics.enabled = value; _analytics.enabled = value;
} }
/// A stable randomly generated UUID used to deduplicate multiple identical @override
/// reports coming from the same computer.
String get clientId => _analytics.clientId; String get clientId => _analytics.clientId;
@override
void sendCommand(String command, { Map<String, String> parameters }) { void sendCommand(String command, { Map<String, String> parameters }) {
if (suppressAnalytics) { if (suppressAnalytics) {
return; return;
...@@ -154,11 +223,12 @@ class Usage { ...@@ -154,11 +223,12 @@ class Usage {
final Map<String, String> paramsWithLocalTime = <String, String>{ final Map<String, String> paramsWithLocalTime = <String, String>{
...?parameters, ...?parameters,
_kLocalTimeParameter: systemClock.now().toString(), cdKey(CustomDimensions.localTime): systemClock.now().toString(),
}; };
_analytics.sendScreenView(command, parameters: paramsWithLocalTime); _analytics.sendScreenView(command, parameters: paramsWithLocalTime);
} }
@override
void sendEvent( void sendEvent(
String category, String category,
String parameter, { String parameter, {
...@@ -170,12 +240,13 @@ class Usage { ...@@ -170,12 +240,13 @@ class Usage {
final Map<String, String> paramsWithLocalTime = <String, String>{ final Map<String, String> paramsWithLocalTime = <String, String>{
...?parameters, ...?parameters,
_kLocalTimeParameter: systemClock.now().toString(), cdKey(CustomDimensions.localTime): systemClock.now().toString(),
}; };
_analytics.sendEvent(category, parameter, parameters: paramsWithLocalTime); _analytics.sendEvent(category, parameter, parameters: paramsWithLocalTime);
} }
@override
void sendTiming( void sendTiming(
String category, String category,
String variableName, String variableName,
...@@ -193,6 +264,7 @@ class Usage { ...@@ -193,6 +264,7 @@ class Usage {
); );
} }
@override
void sendException(dynamic exception) { void sendException(dynamic exception) {
if (suppressAnalytics) { if (suppressAnalytics) {
return; return;
...@@ -200,12 +272,10 @@ class Usage { ...@@ -200,12 +272,10 @@ class Usage {
_analytics.sendException(exception.runtimeType.toString()); _analytics.sendException(exception.runtimeType.toString());
} }
/// Fires whenever analytics data is sent over the network. @override
@visibleForTesting
Stream<Map<String, dynamic>> get onSend => _analytics.onSend; Stream<Map<String, dynamic>> get onSend => _analytics.onSend;
/// Returns when the last analytics event has been sent, or after a fixed @override
/// (short) delay, whichever is less.
Future<void> ensureAnalyticsSent() async { Future<void> ensureAnalyticsSent() async {
// TODO(devoncarew): This may delay tool exit and could cause some analytics // TODO(devoncarew): This may delay tool exit and could cause some analytics
// events to not be reported. Perhaps we could send the analytics pings // events to not be reported. Perhaps we could send the analytics pings
...@@ -213,6 +283,7 @@ class Usage { ...@@ -213,6 +283,7 @@ class Usage {
await _analytics.waitForLastPing(timeout: const Duration(milliseconds: 250)); await _analytics.waitForLastPing(timeout: const Duration(milliseconds: 250));
} }
@override
void printWelcome() { void printWelcome() {
// This gets called if it's the first run by the selected command, if any, // This gets called if it's the first run by the selected command, if any,
// and on exit, in case there was no command. // and on exit, in case there was no command.
...@@ -255,7 +326,9 @@ class LogToFileAnalytics extends AnalyticsMock { ...@@ -255,7 +326,9 @@ class LogToFileAnalytics extends AnalyticsMock {
final Map<String, String> _sessionValues = <String, String>{}; final Map<String, String> _sessionValues = <String, String>{};
@override @override
Future<void> sendScreenView(String viewName, {Map<String, String> parameters}) { Future<void> sendScreenView(String viewName, {
Map<String, String> parameters,
}) {
parameters ??= <String, String>{}; parameters ??= <String, String>{};
parameters['viewName'] = viewName; parameters['viewName'] = viewName;
parameters.addAll(_sessionValues); parameters.addAll(_sessionValues);
......
...@@ -21,7 +21,7 @@ import 'convert.dart'; ...@@ -21,7 +21,7 @@ import 'convert.dart';
import 'devfs.dart'; import 'devfs.dart';
import 'device.dart'; import 'device.dart';
import 'globals.dart'; import 'globals.dart';
import 'reporting/usage.dart'; import 'reporting/reporting.dart';
import 'resident_runner.dart'; import 'resident_runner.dart';
import 'vmservice.dart'; import 'vmservice.dart';
...@@ -367,15 +367,12 @@ class HotRunner extends ResidentRunner { ...@@ -367,15 +367,12 @@ class HotRunner extends ResidentRunner {
futures.add(view.flushUIThreadTasks()); futures.add(view.flushUIThreadTasks());
await Future.wait(futures); await Future.wait(futures);
} }
} }
Future<OperationResult> _restartFromSources({ String reason, bool benchmarkMode = false }) async { Future<OperationResult> _restartFromSources({
final Map<String, String> analyticsParameters = String reason,
reason == null bool benchmarkMode = false
? null }) async {
: <String, String>{kEventReloadReasonParameterName: reason};
if (!_isPaused()) { if (!_isPaused()) {
printTrace('Refreshing active FlutterViews before restarting.'); printTrace('Refreshing active FlutterViews before restarting.');
await refreshViews(); await refreshViews();
...@@ -387,8 +384,9 @@ class HotRunner extends ResidentRunner { ...@@ -387,8 +384,9 @@ class HotRunner extends ResidentRunner {
final UpdateFSReport updatedDevFS = await _updateDevFS(fullRestart: true); final UpdateFSReport updatedDevFS = await _updateDevFS(fullRestart: true);
if (!updatedDevFS.success) { if (!updatedDevFS.success) {
for (FlutterDevice device in flutterDevices) { for (FlutterDevice device in flutterDevices) {
if (device.generator != null) if (device.generator != null) {
await device.generator.reject(); await device.generator.reject();
}
} }
return OperationResult(1, 'DevFS synchronization failed'); return OperationResult(1, 'DevFS synchronization failed');
} }
...@@ -396,30 +394,32 @@ class HotRunner extends ResidentRunner { ...@@ -396,30 +394,32 @@ class HotRunner extends ResidentRunner {
for (FlutterDevice device in flutterDevices) { for (FlutterDevice device in flutterDevices) {
// VM must have accepted the kernel binary, there will be no reload // VM must have accepted the kernel binary, there will be no reload
// report, so we let incremental compiler know that source code was accepted. // report, so we let incremental compiler know that source code was accepted.
if (device.generator != null) if (device.generator != null) {
device.generator.accept(); device.generator.accept();
}
} }
// Check if the isolate is paused and resume it. // Check if the isolate is paused and resume it.
final List<Future<void>> futures = <Future<void>>[]; final List<Future<void>> futures = <Future<void>>[];
for (FlutterDevice device in flutterDevices) { for (FlutterDevice device in flutterDevices) {
for (FlutterView view in device.views) { for (FlutterView view in device.views) {
if (view.uiIsolate != null) { if (view.uiIsolate == null) {
// Reload the isolate. continue;
final Completer<void> completer = Completer<void>();
futures.add(completer.future);
unawaited(view.uiIsolate.reload().then(
(ServiceObject _) {
final ServiceEvent pauseEvent = view.uiIsolate.pauseEvent;
if ((pauseEvent != null) && pauseEvent.isPauseEvent) {
// Resume the isolate so that it can be killed by the embedder.
return view.uiIsolate.resume();
}
return null;
},
).whenComplete(
() { completer.complete(null); },
));
} }
// Reload the isolate.
final Completer<void> completer = Completer<void>();
futures.add(completer.future);
unawaited(view.uiIsolate.reload().then(
(ServiceObject _) {
final ServiceEvent pauseEvent = view.uiIsolate.pauseEvent;
if ((pauseEvent != null) && pauseEvent.isPauseEvent) {
// Resume the isolate so that it can be killed by the embedder.
return view.uiIsolate.resume();
}
return null;
},
).whenComplete(
() { completer.complete(null); },
));
} }
} }
await Future.wait(futures); await Future.wait(futures);
...@@ -432,7 +432,8 @@ class HotRunner extends ResidentRunner { ...@@ -432,7 +432,8 @@ class HotRunner extends ResidentRunner {
_runningFromSnapshot = false; _runningFromSnapshot = false;
_addBenchmarkData('hotRestartMillisecondsToFrame', _addBenchmarkData('hotRestartMillisecondsToFrame',
restartTimer.elapsed.inMilliseconds); restartTimer.elapsed.inMilliseconds);
flutterUsage.sendEvent('hot', 'restart', parameters: analyticsParameters);
// Send timing analytics.
flutterUsage.sendTiming('hot', 'restart', restartTimer.elapsed); flutterUsage.sendTiming('hot', 'restart', restartTimer.elapsed);
// In benchmark mode, make sure all stream notifications have finished. // In benchmark mode, make sure all stream notifications have finished.
...@@ -499,81 +500,156 @@ class HotRunner extends ResidentRunner { ...@@ -499,81 +500,156 @@ class HotRunner extends ResidentRunner {
bool get supportsRestart => true; bool get supportsRestart => true;
@override @override
Future<OperationResult> restart({ bool fullRestart = false, bool pauseAfterRestart = false, String reason, bool benchmarkMode = false }) async { Future<OperationResult> restart({
bool fullRestart = false,
bool pauseAfterRestart = false,
String reason,
bool benchmarkMode = false
}) async {
String targetPlatform;
String sdkName;
bool emulator;
if (flutterDevices.length == 1) {
final Device device = flutterDevices.first.device;
targetPlatform = getNameForTargetPlatform(await device.targetPlatform);
sdkName = await device.sdkNameAndVersion;
emulator = await device.isLocalEmulator;
} else if (flutterDevices.length > 1) {
targetPlatform = 'multiple';
sdkName = 'multiple';
emulator = false;
} else {
targetPlatform = 'unknown';
sdkName = 'unknown';
emulator = false;
}
final Stopwatch timer = Stopwatch()..start(); final Stopwatch timer = Stopwatch()..start();
if (fullRestart) { if (fullRestart) {
if (!canHotRestart) { final OperationResult result = await _fullRestartHelper(
return OperationResult(1, 'hotRestart not supported'); targetPlatform: targetPlatform,
} sdkName: sdkName,
final Status status = logger.startProgress( emulator: emulator,
'Performing hot restart...', reason: reason,
timeout: timeoutConfiguration.fastOperation, benchmarkMode: benchmarkMode,
progressId: 'hot.restart',
); );
try {
if (!(await hotRunnerConfig.setupHotRestart()))
return OperationResult(1, 'setupHotRestart failed');
final OperationResult result = await _restartFromSources(reason: reason, benchmarkMode: benchmarkMode,);
if (!result.isOk)
return result;
} on rpc.RpcException {
await _measureJsonRpcException(flutterDevices, fullRestart);
return OperationResult(1, 'hot restart failed to complete', fatal: true);
} finally {
status.cancel();
}
printStatus('Restarted application in ${getElapsedAsMilliseconds(timer.elapsed)}.'); printStatus('Restarted application in ${getElapsedAsMilliseconds(timer.elapsed)}.');
return OperationResult.ok; return result;
} else { }
final bool reloadOnTopOfSnapshot = _runningFromSnapshot; final OperationResult result = await _hotReloadHelper(
final String progressPrefix = reloadOnTopOfSnapshot ? 'Initializing' : 'Performing'; targetPlatform: targetPlatform,
Status status = logger.startProgress( sdkName: sdkName,
'$progressPrefix hot reload...', emulator: emulator,
timeout: timeoutConfiguration.fastOperation, reason: reason,
progressId: 'hot.reload', pauseAfterRestart: pauseAfterRestart,
); );
OperationResult result; if (result.isOk) {
bool showTime = true; final String elapsed = getElapsedAsMilliseconds(timer.elapsed);
try { printStatus('${result.message} in $elapsed.');
result = await _reloadSources( }
pause: pauseAfterRestart, return result;
reason: reason, }
onSlow: (String message) {
status?.cancel(); Future<OperationResult> _fullRestartHelper({
status = logger.startProgress( String targetPlatform,
message, String sdkName,
timeout: timeoutConfiguration.slowOperation, bool emulator,
progressId: 'hot.reload', String reason,
); bool benchmarkMode,
showTime = false; }) async {
}, if (!canHotRestart) {
); return OperationResult(1, 'hotRestart not supported');
} on rpc.RpcException { }
await _measureJsonRpcException(flutterDevices, fullRestart); final Status status = logger.startProgress(
return OperationResult(1, 'hot reload failed to complete', fatal: true); 'Performing hot restart...',
} finally { timeout: timeoutConfiguration.fastOperation,
status.cancel(); progressId: 'hot.restart',
);
OperationResult result;
String restartEvent = 'restart';
try {
if (!(await hotRunnerConfig.setupHotRestart())) {
return OperationResult(1, 'setupHotRestart failed');
} }
if (result.isOk) { result = await _restartFromSources(
if (showTime) { reason: reason,
printStatus('${result.message} in ${getElapsedAsMilliseconds(timer.elapsed)}.'); benchmarkMode: benchmarkMode,
} else { );
printStatus('${result.message}.'); if (!result.isOk) {
} restartEvent = 'restart-failed';
} }
return result; } on rpc.RpcException {
restartEvent = 'exception';
return OperationResult(1, 'hot restart failed to complete', fatal: true);
} finally {
HotEvent(restartEvent,
targetPlatform: targetPlatform,
sdkName: sdkName,
emulator: emulator,
fullRestart: true,
reason: reason).send();
status.cancel();
} }
return result;
} }
Future<OperationResult> _reloadSources({ bool pause = false, String reason, void Function(String message) onSlow }) async { Future<OperationResult> _hotReloadHelper({
final Map<String, String> analyticsParameters = <String, String>{}; String targetPlatform,
if (reason != null) { String sdkName,
analyticsParameters[kEventReloadReasonParameterName] = reason; bool emulator,
String reason,
bool pauseAfterRestart = false,
}) async {
final bool reloadOnTopOfSnapshot = _runningFromSnapshot;
final String progressPrefix = reloadOnTopOfSnapshot ? 'Initializing' : 'Performing';
Status status = logger.startProgress(
'$progressPrefix hot reload...',
timeout: timeoutConfiguration.fastOperation,
progressId: 'hot.reload',
);
OperationResult result;
try {
result = await _reloadSources(
targetPlatform: targetPlatform,
sdkName: sdkName,
emulator: emulator,
pause: pauseAfterRestart,
reason: reason,
onSlow: (String message) {
status?.cancel();
status = logger.startProgress(
message,
timeout: timeoutConfiguration.slowOperation,
progressId: 'hot.reload',
);
},
);
} on rpc.RpcException {
HotEvent('exception',
targetPlatform: targetPlatform,
sdkName: sdkName,
emulator: emulator,
fullRestart: false,
reason: reason).send();
return OperationResult(1, 'hot reload failed to complete', fatal: true);
} finally {
status.cancel();
} }
return result;
}
Future<OperationResult> _reloadSources({
String targetPlatform,
String sdkName,
bool emulator,
bool pause = false,
String reason,
void Function(String message) onSlow
}) async {
for (FlutterDevice device in flutterDevices) { for (FlutterDevice device in flutterDevices) {
for (FlutterView view in device.views) { for (FlutterView view in device.views) {
if (view.uiIsolate == null) if (view.uiIsolate == null) {
throw 'Application isolate not found'; throw 'Application isolate not found';
}
} }
} }
...@@ -595,10 +671,12 @@ class HotRunner extends ResidentRunner { ...@@ -595,10 +671,12 @@ class HotRunner extends ResidentRunner {
final UpdateFSReport updatedDevFS = await _updateDevFS(); final UpdateFSReport updatedDevFS = await _updateDevFS();
// Record time it took to synchronize to DevFS. // Record time it took to synchronize to DevFS.
_addBenchmarkData('hotReloadDevFSSyncMilliseconds', devFSTimer.elapsed.inMilliseconds); _addBenchmarkData('hotReloadDevFSSyncMilliseconds', devFSTimer.elapsed.inMilliseconds);
if (!updatedDevFS.success) if (!updatedDevFS.success) {
return OperationResult(1, 'DevFS synchronization failed'); return OperationResult(1, 'DevFS synchronization failed');
}
String reloadMessage; String reloadMessage;
final Stopwatch vmReloadTimer = Stopwatch()..start(); final Stopwatch vmReloadTimer = Stopwatch()..start();
Map<String, dynamic> firstReloadDetails;
try { try {
final String entryPath = fs.path.relative( final String entryPath = fs.path.relative(
getReloadPath(fullRestart: false), getReloadPath(fullRestart: false),
...@@ -635,27 +713,23 @@ class HotRunner extends ResidentRunner { ...@@ -635,27 +713,23 @@ class HotRunner extends ResidentRunner {
final Map<String, dynamic> reloadReport = report.reports[0]; final Map<String, dynamic> reloadReport = report.reports[0];
if (!validateReloadReport(reloadReport)) { if (!validateReloadReport(reloadReport)) {
// Reload failed. // Reload failed.
flutterUsage.sendEvent('hot', 'reload-reject'); HotEvent('reload-reject',
targetPlatform: targetPlatform,
sdkName: sdkName,
emulator: emulator,
fullRestart: false,
reason: reason,
).send();
return OperationResult(1, 'Reload rejected'); return OperationResult(1, 'Reload rejected');
} else {
// Collect stats that help understand scale of update for this hot reload request.
// For example, [syncedLibraryCount]/[finalLibraryCount] indicates how
// many libraries were affected by the hot reload request.
// Relation of [invalidatedSourcesCount] to [syncedLibraryCount] should help
// understand sync/transfer "overhead" of updating this number of source files.
final Map<String, dynamic> details = reloadReport['details'];
analyticsParameters[kEventReloadFinalLibraryCount] = "${details['finalLibraryCount']}";
analyticsParameters[kEventReloadSyncedLibraryCount] = "${details['receivedLibraryCount']}";
analyticsParameters[kEventReloadSyncedClassesCount] = "${details['receivedClassesCount']}";
analyticsParameters[kEventReloadSyncedProceduresCount] = "${details['receivedProceduresCount']}";
analyticsParameters[kEventReloadSyncedBytes] = '${updatedDevFS.syncedBytes}';
analyticsParameters[kEventReloadInvalidatedSourcesCount] = '${updatedDevFS.invalidatedSourcesCount}';
analyticsParameters[kEventReloadTransferTimeInMs] = '${devFSTimer.elapsed.inMilliseconds}';
final int loadedLibraryCount = reloadReport['details']['loadedLibraryCount'];
final int finalLibraryCount = reloadReport['details']['finalLibraryCount'];
printTrace('reloaded $loadedLibraryCount of $finalLibraryCount libraries');
reloadMessage = 'Reloaded $loadedLibraryCount of $finalLibraryCount libraries';
} }
// Collect stats only from the first device. If/when run -d all is
// refactored, we'll probably need to send one hot reload/restart event
// per device to analytics.
firstReloadDetails ??= reloadReport['details'];
final int loadedLibraryCount = reloadReport['details']['loadedLibraryCount'];
final int finalLibraryCount = reloadReport['details']['finalLibraryCount'];
printTrace('reloaded $loadedLibraryCount of $finalLibraryCount libraries');
reloadMessage = 'Reloaded $loadedLibraryCount of $finalLibraryCount libraries';
} }
} on Map<String, dynamic> catch (error, stackTrace) { } on Map<String, dynamic> catch (error, stackTrace) {
printTrace('Hot reload failed: $error\n$stackTrace'); printTrace('Hot reload failed: $error\n$stackTrace');
...@@ -666,7 +740,13 @@ class HotRunner extends ResidentRunner { ...@@ -666,7 +740,13 @@ class HotRunner extends ResidentRunner {
'the source code. Please address the error and then use "R" to ' 'the source code. Please address the error and then use "R" to '
'restart the app.\n' 'restart the app.\n'
'$errorMessage (error code: $errorCode)'; '$errorMessage (error code: $errorCode)';
flutterUsage.sendEvent('hot', 'reload-barred'); HotEvent('reload-barred',
targetPlatform: targetPlatform,
sdkName: sdkName,
emulator: emulator,
fullRestart: false,
reason: reason,
).send();
return OperationResult(errorCode, errorMessage); return OperationResult(errorCode, errorMessage);
} }
return OperationResult(errorCode, '$errorMessage (error code: $errorCode)'); return OperationResult(errorCode, '$errorMessage (error code: $errorCode)');
...@@ -783,8 +863,26 @@ class HotRunner extends ResidentRunner { ...@@ -783,8 +863,26 @@ class HotRunner extends ResidentRunner {
final Duration reloadDuration = reloadTimer.elapsed; final Duration reloadDuration = reloadTimer.elapsed;
final int reloadInMs = reloadDuration.inMilliseconds; final int reloadInMs = reloadDuration.inMilliseconds;
analyticsParameters[kEventReloadOverallTimeInMs] = '$reloadInMs'; // Collect stats that help understand scale of update for this hot reload request.
flutterUsage.sendEvent('hot', 'reload', parameters: analyticsParameters); // For example, [syncedLibraryCount]/[finalLibraryCount] indicates how
// many libraries were affected by the hot reload request.
// Relation of [invalidatedSourcesCount] to [syncedLibraryCount] should help
// understand sync/transfer "overhead" of updating this number of source files.
HotEvent('reload',
targetPlatform: targetPlatform,
sdkName: sdkName,
emulator: emulator,
fullRestart: false,
reason: reason,
overallTimeInMs: reloadInMs,
finalLibraryCount: firstReloadDetails['finalLibraryCount'],
syncedLibraryCount: firstReloadDetails['receivedLibraryCount'],
syncedClassesCount: firstReloadDetails['receivedClassesCount'],
syncedProceduresCount: firstReloadDetails['receivedProceduresCount'],
syncedBytes: updatedDevFS.syncedBytes,
invalidatedSourcesCount: updatedDevFS.invalidatedSourcesCount,
transferTimeInMs: devFSTimer.elapsed.inMilliseconds,
).send();
if (shouldReportReloadTime) { if (shouldReportReloadTime) {
printTrace('Hot reload performed in ${getElapsedAsMilliseconds(reloadDuration)}.'); printTrace('Hot reload performed in ${getElapsedAsMilliseconds(reloadDuration)}.');
...@@ -952,32 +1050,3 @@ class ProjectFileInvalidator { ...@@ -952,32 +1050,3 @@ class ProjectFileInvalidator {
return invalidatedFiles; return invalidatedFiles;
} }
} }
// This is an error case we would like to know more about.
Future<void> _measureJsonRpcException(List<FlutterDevice> flutterDevices, bool fullRestart) async {
String targetPlatform;
String deviceSdk;
bool emulator;
if (flutterDevices.length == 1) {
final Device device = flutterDevices.first.device;
targetPlatform = getNameForTargetPlatform(await device.targetPlatform);
deviceSdk = await device.sdkNameAndVersion;
emulator = await device.isLocalEmulator;
} else if (flutterDevices.length > 1) {
targetPlatform = 'multiple';
deviceSdk = 'multiple';
emulator = false;
} else {
targetPlatform = 'unknown';
deviceSdk = 'unknown';
emulator = false;
}
flutterUsage.sendEvent('hot', 'exception',
parameters: <String, String>{
reloadExceptionTargetPlatform: targetPlatform,
reloadExceptionSdkName: deviceSdk,
reloadExceptionEmulator: emulator.toString(),
reloadExceptionFullRestart: fullRestart.toString(),
},
);
}
...@@ -28,7 +28,7 @@ import '../doctor.dart'; ...@@ -28,7 +28,7 @@ import '../doctor.dart';
import '../features.dart'; import '../features.dart';
import '../globals.dart'; import '../globals.dart';
import '../project.dart'; import '../project.dart';
import '../reporting/usage.dart'; import '../reporting/reporting.dart';
import 'flutter_command_runner.dart'; import 'flutter_command_runner.dart';
export '../cache.dart' show DevelopmentArtifact; export '../cache.dart' show DevelopmentArtifact;
...@@ -63,6 +63,21 @@ class FlutterCommandResult { ...@@ -63,6 +63,21 @@ class FlutterCommandResult {
/// [FlutterCommand] will automatically measure and report the command's /// [FlutterCommand] will automatically measure and report the command's
/// complete time if not overridden. /// complete time if not overridden.
final DateTime endTimeOverride; final DateTime endTimeOverride;
@override
String toString() {
switch (exitStatus) {
case ExitStatus.success:
return 'success';
case ExitStatus.warning:
return 'warning';
case ExitStatus.fail:
return 'fail';
default:
assert(false);
return null;
}
}
} }
/// Common flutter command line options. /// Common flutter command line options.
...@@ -366,7 +381,8 @@ abstract class FlutterCommand extends Command<void> { ...@@ -366,7 +381,8 @@ abstract class FlutterCommand extends Command<void> {
} }
/// Additional usage values to be sent with the usage ping. /// Additional usage values to be sent with the usage ping.
Future<Map<String, String>> get usageValues async => const <String, String>{}; Future<Map<CustomDimensions, String>> get usageValues async =>
const <CustomDimensions, String>{};
/// Runs this command. /// Runs this command.
/// ///
...@@ -382,8 +398,9 @@ abstract class FlutterCommand extends Command<void> { ...@@ -382,8 +398,9 @@ abstract class FlutterCommand extends Command<void> {
name: 'command', name: 'command',
overrides: <Type, Generator>{FlutterCommand: () => this}, overrides: <Type, Generator>{FlutterCommand: () => this},
body: () async { body: () async {
if (flutterUsage.isFirstRun) if (flutterUsage.isFirstRun) {
flutterUsage.printWelcome(); flutterUsage.printWelcome();
}
final String commandPath = await usagePath; final String commandPath = await usagePath;
FlutterCommandResult commandResult; FlutterCommandResult commandResult;
try { try {
...@@ -411,21 +428,7 @@ abstract class FlutterCommand extends Command<void> { ...@@ -411,21 +428,7 @@ abstract class FlutterCommand extends Command<void> {
} }
// Send command result. // Send command result.
String result = 'unspecified'; CommandResultEvent(commandPath, commandResult).send();
if (commandResult != null) {
switch (commandResult.exitStatus) {
case ExitStatus.success:
result = 'success';
break;
case ExitStatus.warning:
result = 'warning';
break;
case ExitStatus.fail:
result = 'fail';
break;
}
}
flutterUsage.sendEvent(commandPath, result);
// Send timing. // Send timing.
final List<String> labels = <String>[ final List<String> labels = <String>[
...@@ -476,12 +479,12 @@ abstract class FlutterCommand extends Command<void> { ...@@ -476,12 +479,12 @@ abstract class FlutterCommand extends Command<void> {
setupApplicationPackages(); setupApplicationPackages();
if (commandPath != null) { if (commandPath != null) {
final Map<String, String> additionalUsageValues = <String,String>{ final Map<CustomDimensions, String> additionalUsageValues =
...?await usageValues, <CustomDimensions, String>{
}; ...?await usageValues,
additionalUsageValues[kCommandHasTerminal] = CustomDimensions.commandHasTerminal: io.stdout.hasTerminal ? 'true' : 'false',
io.stdout.hasTerminal ? 'true' : 'false'; };
flutterUsage.sendCommand(commandPath, parameters: additionalUsageValues); Usage.command(commandPath, parameters: additionalUsageValues);
} }
return await runCommand(); return await runCommand();
......
...@@ -30,7 +30,7 @@ import '../convert.dart'; ...@@ -30,7 +30,7 @@ import '../convert.dart';
import '../dart/package_map.dart'; import '../dart/package_map.dart';
import '../device.dart'; import '../device.dart';
import '../globals.dart'; import '../globals.dart';
import '../reporting/usage.dart'; import '../reporting/reporting.dart';
import '../tester/flutter_tester.dart'; import '../tester/flutter_tester.dart';
import '../version.dart'; import '../version.dart';
import '../vmservice.dart'; import '../vmservice.dart';
......
...@@ -13,7 +13,7 @@ import '../build_info.dart'; ...@@ -13,7 +13,7 @@ import '../build_info.dart';
import '../bundle.dart'; import '../bundle.dart';
import '../globals.dart'; import '../globals.dart';
import '../project.dart'; import '../project.dart';
import '../reporting/usage.dart'; import '../reporting/reporting.dart';
/// The [WebCompilationProxy] instance. /// The [WebCompilationProxy] instance.
WebCompilationProxy get webCompilationProxy => context.get<WebCompilationProxy>(); WebCompilationProxy get webCompilationProxy => context.get<WebCompilationProxy>();
......
...@@ -13,7 +13,7 @@ import '../cache.dart'; ...@@ -13,7 +13,7 @@ import '../cache.dart';
import '../convert.dart'; import '../convert.dart';
import '../globals.dart'; import '../globals.dart';
import '../project.dart'; import '../project.dart';
import '../reporting/usage.dart'; import '../reporting/reporting.dart';
import 'msbuild_utils.dart'; import 'msbuild_utils.dart';
import 'visual_studio.dart'; import 'visual_studio.dart';
......
...@@ -4,22 +4,21 @@ ...@@ -4,22 +4,21 @@
import 'package:args/command_runner.dart'; import 'package:args/command_runner.dart';
import 'package:file/memory.dart'; import 'package:file/memory.dart';
import 'package:mockito/mockito.dart';
import 'package:flutter_tools/src/base/config.dart'; import 'package:flutter_tools/src/base/config.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/time.dart';
import 'package:flutter_tools/src/features.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/time.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/build.dart'; import 'package:flutter_tools/src/commands/build.dart';
import 'package:flutter_tools/src/commands/config.dart'; import 'package:flutter_tools/src/commands/config.dart';
import 'package:flutter_tools/src/commands/doctor.dart'; import 'package:flutter_tools/src/commands/doctor.dart';
import 'package:flutter_tools/src/doctor.dart'; import 'package:flutter_tools/src/doctor.dart';
import 'package:flutter_tools/src/reporting/usage.dart'; import 'package:flutter_tools/src/features.dart';
import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:flutter_tools/src/runner/flutter_command.dart'; import 'package:flutter_tools/src/runner/flutter_command.dart';
import 'package:flutter_tools/src/version.dart'; import 'package:flutter_tools/src/version.dart';
import 'package:mockito/mockito.dart';
import 'package:platform/platform.dart'; import 'package:platform/platform.dart';
import '../src/common.dart'; import '../src/common.dart';
...@@ -94,7 +93,8 @@ void main() { ...@@ -94,7 +93,8 @@ void main() {
final Usage usage = Usage(); final Usage usage = Usage();
usage.sendCommand('test'); usage.sendCommand('test');
expect(fs.file('test').readAsStringSync(), contains('$enabledFlutterFeatures: enable-web')); final String featuresKey = cdKey(CustomDimensions.enabledFlutterFeatures);
expect(fs.file('test').readAsStringSync(), contains('$featuresKey: enable-web'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FlutterVersion: () => FlutterVersion(const SystemClock()), FlutterVersion: () => FlutterVersion(const SystemClock()),
Config: () => mockFlutterConfig, Config: () => mockFlutterConfig,
...@@ -114,7 +114,8 @@ void main() { ...@@ -114,7 +114,8 @@ void main() {
final Usage usage = Usage(); final Usage usage = Usage();
usage.sendCommand('test'); usage.sendCommand('test');
expect(fs.file('test').readAsStringSync(), contains('$enabledFlutterFeatures: enable-web,enable-linux-desktop,enable-macos-desktop')); final String featuresKey = cdKey(CustomDimensions.enabledFlutterFeatures);
expect(fs.file('test').readAsStringSync(), contains('$featuresKey: enable-web,enable-linux-desktop,enable-macos-desktop'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FlutterVersion: () => FlutterVersion(const SystemClock()), FlutterVersion: () => FlutterVersion(const SystemClock()),
Config: () => mockFlutterConfig, Config: () => mockFlutterConfig,
......
...@@ -3,11 +3,11 @@ ...@@ -3,11 +3,11 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'package:file/memory.dart'; import 'package:file/memory.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/artifacts.dart';
import '../src/common.dart'; import '../src/common.dart';
import '../src/context.dart'; import '../src/context.dart';
......
...@@ -7,7 +7,7 @@ import 'package:flutter_tools/src/android/aar.dart'; ...@@ -7,7 +7,7 @@ import 'package:flutter_tools/src/android/aar.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/build_aar.dart'; import 'package:flutter_tools/src/commands/build_aar.dart';
import 'package:flutter_tools/src/reporting/usage.dart'; import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
import '../../src/common.dart'; import '../../src/common.dart';
...@@ -51,7 +51,7 @@ void main() { ...@@ -51,7 +51,7 @@ void main() {
final BuildAarCommand command = await runCommandIn(projectPath); final BuildAarCommand command = await runCommandIn(projectPath);
expect(await command.usageValues, expect(await command.usageValues,
containsPair(kCommandBuildAarProjectType, 'module')); containsPair(CustomDimensions.commandBuildAarProjectType, 'module'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
AarBuilder: () => mockAarBuilder, AarBuilder: () => mockAarBuilder,
...@@ -63,7 +63,7 @@ void main() { ...@@ -63,7 +63,7 @@ void main() {
final BuildAarCommand command = await runCommandIn(projectPath); final BuildAarCommand command = await runCommandIn(projectPath);
expect(await command.usageValues, expect(await command.usageValues,
containsPair(kCommandBuildAarProjectType, 'plugin')); containsPair(CustomDimensions.commandBuildAarProjectType, 'plugin'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
AarBuilder: () => mockAarBuilder, AarBuilder: () => mockAarBuilder,
...@@ -76,7 +76,7 @@ void main() { ...@@ -76,7 +76,7 @@ void main() {
final BuildAarCommand command = await runCommandIn(projectPath, final BuildAarCommand command = await runCommandIn(projectPath,
arguments: <String>['--target-platform=android-arm']); arguments: <String>['--target-platform=android-arm']);
expect(await command.usageValues, expect(await command.usageValues,
containsPair(kCommandBuildAarTargetPlatform, 'android-arm')); containsPair(CustomDimensions.commandBuildAarTargetPlatform, 'android-arm'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
AarBuilder: () => mockAarBuilder, AarBuilder: () => mockAarBuilder,
......
...@@ -6,11 +6,11 @@ import 'package:args/command_runner.dart'; ...@@ -6,11 +6,11 @@ import 'package:args/command_runner.dart';
import 'package:file/memory.dart'; import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/common.dart'; import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/bundle.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/build_bundle.dart'; import 'package:flutter_tools/src/commands/build_bundle.dart';
import 'package:flutter_tools/src/bundle.dart';
import 'package:flutter_tools/src/features.dart'; import 'package:flutter_tools/src/features.dart';
import 'package:flutter_tools/src/reporting/usage.dart'; import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
import '../../src/common.dart'; import '../../src/common.dart';
...@@ -70,7 +70,7 @@ void main() { ...@@ -70,7 +70,7 @@ void main() {
final BuildBundleCommand command = await runCommandIn(projectPath); final BuildBundleCommand command = await runCommandIn(projectPath);
expect(await command.usageValues, expect(await command.usageValues,
containsPair(kCommandBuildBundleIsModule, 'true')); containsPair(CustomDimensions.commandBuildBundleIsModule, 'true'));
}, timeout: allowForCreateFlutterProject); }, timeout: allowForCreateFlutterProject);
testUsingContext('bundle getUsage indicate that project is not a module', () async { testUsingContext('bundle getUsage indicate that project is not a module', () async {
...@@ -80,7 +80,7 @@ void main() { ...@@ -80,7 +80,7 @@ void main() {
final BuildBundleCommand command = await runCommandIn(projectPath); final BuildBundleCommand command = await runCommandIn(projectPath);
expect(await command.usageValues, expect(await command.usageValues,
containsPair(kCommandBuildBundleIsModule, 'false')); containsPair(CustomDimensions.commandBuildBundleIsModule, 'false'));
}, timeout: allowForCreateFlutterProject); }, timeout: allowForCreateFlutterProject);
testUsingContext('bundle getUsage indicate the target platform', () async { testUsingContext('bundle getUsage indicate the target platform', () async {
...@@ -90,7 +90,7 @@ void main() { ...@@ -90,7 +90,7 @@ void main() {
final BuildBundleCommand command = await runCommandIn(projectPath); final BuildBundleCommand command = await runCommandIn(projectPath);
expect(await command.usageValues, expect(await command.usageValues,
containsPair(kCommandBuildBundleTargetPlatform, 'android-arm')); containsPair(CustomDimensions.commandBuildBundleTargetPlatform, 'android-arm'));
}, timeout: allowForCreateFlutterProject); }, timeout: allowForCreateFlutterProject);
testUsingContext('bundle fails to build for Windows if feature is disabled', () async { testUsingContext('bundle fails to build for Windows if feature is disabled', () async {
......
...@@ -7,7 +7,7 @@ import 'package:flutter_tools/src/base/file_system.dart'; ...@@ -7,7 +7,7 @@ import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/create.dart'; import 'package:flutter_tools/src/commands/create.dart';
import 'package:flutter_tools/src/doctor.dart'; import 'package:flutter_tools/src/doctor.dart';
import 'package:flutter_tools/src/reporting/usage.dart'; import 'package:flutter_tools/src/reporting/reporting.dart';
import '../../src/common.dart'; import '../../src/common.dart';
import '../../src/context.dart'; import '../../src/context.dart';
...@@ -43,25 +43,35 @@ void main() { ...@@ -43,25 +43,35 @@ void main() {
final CreateCommand command = CreateCommand(); final CreateCommand command = CreateCommand();
final CommandRunner<void> runner = createTestCommandRunner(command); final CommandRunner<void> runner = createTestCommandRunner(command);
await runner.run(<String>['create', '--flutter-root=flutter', '--no-pub', '--template=module', 'testy']); await runner.run(<String>[
expect(await command.usageValues, containsPair(kCommandCreateProjectType, 'module')); 'create', '--flutter-root=flutter', '--no-pub', '--template=module', 'testy']);
expect(await command.usageValues,
containsPair(CustomDimensions.commandCreateProjectType, 'module'));
await runner.run(<String>['create', '--flutter-root=flutter', '--no-pub', '--template=app', 'testy']); await runner.run(<String>[
expect(await command.usageValues, containsPair(kCommandCreateProjectType, 'app')); 'create', '--flutter-root=flutter', '--no-pub', '--template=app', 'testy']);
expect(await command.usageValues,
containsPair(CustomDimensions.commandCreateProjectType, 'app'));
await runner.run(<String>['create', '--flutter-root=flutter', '--no-pub', '--template=package', 'testy']); await runner.run(<String>[
expect(await command.usageValues, containsPair(kCommandCreateProjectType, 'package')); 'create', '--flutter-root=flutter', '--no-pub', '--template=package', 'testy']);
expect(await command.usageValues,
containsPair(CustomDimensions.commandCreateProjectType, 'package'));
await runner.run(<String>['create', '--flutter-root=flutter', '--no-pub', '--template=plugin', 'testy']); await runner.run(<String>[
expect(await command.usageValues, containsPair(kCommandCreateProjectType, 'plugin')); 'create', '--flutter-root=flutter', '--no-pub', '--template=plugin', 'testy']);
expect(await command.usageValues,
containsPair(CustomDimensions.commandCreateProjectType, 'plugin'));
})); }));
test('set iOS host language type as usage value', () => testbed.run(() async { test('set iOS host language type as usage value', () => testbed.run(() async {
final CreateCommand command = CreateCommand(); final CreateCommand command = CreateCommand();
final CommandRunner<void> runner = createTestCommandRunner(command); final CommandRunner<void> runner = createTestCommandRunner(command);
await runner.run(<String>['create', '--flutter-root=flutter', '--no-pub', '--template=app', 'testy']); await runner.run(<String>[
expect(await command.usageValues, containsPair(kCommandCreateIosLanguage, 'objc')); 'create', '--flutter-root=flutter', '--no-pub', '--template=app', 'testy']);
expect(await command.usageValues,
containsPair(CustomDimensions.commandCreateIosLanguage, 'objc'));
await runner.run(<String>[ await runner.run(<String>[
'create', 'create',
...@@ -71,7 +81,8 @@ void main() { ...@@ -71,7 +81,8 @@ void main() {
'--ios-language=swift', '--ios-language=swift',
'testy', 'testy',
]); ]);
expect(await command.usageValues, containsPair(kCommandCreateIosLanguage, 'swift')); expect(await command.usageValues,
containsPair(CustomDimensions.commandCreateIosLanguage, 'swift'));
})); }));
...@@ -80,7 +91,8 @@ void main() { ...@@ -80,7 +91,8 @@ void main() {
final CommandRunner<void> runner = createTestCommandRunner(command); final CommandRunner<void> runner = createTestCommandRunner(command);
await runner.run(<String>['create', '--flutter-root=flutter', '--no-pub', '--template=app', 'testy']); await runner.run(<String>['create', '--flutter-root=flutter', '--no-pub', '--template=app', 'testy']);
expect(await command.usageValues, containsPair(kCommandCreateAndroidLanguage, 'java')); expect(await command.usageValues,
containsPair(CustomDimensions.commandCreateAndroidLanguage, 'java'));
await runner.run(<String>[ await runner.run(<String>[
'create', 'create',
...@@ -90,7 +102,8 @@ void main() { ...@@ -90,7 +102,8 @@ void main() {
'--android-language=kotlin', '--android-language=kotlin',
'testy', 'testy',
]); ]);
expect(await command.usageValues, containsPair(kCommandCreateAndroidLanguage, 'kotlin')); expect(await command.usageValues,
containsPair(CustomDimensions.commandCreateAndroidLanguage, 'kotlin'));
})); }));
}); });
} }
......
...@@ -16,9 +16,9 @@ import 'package:flutter_tools/src/base/user_messages.dart'; ...@@ -16,9 +16,9 @@ import 'package:flutter_tools/src/base/user_messages.dart';
import 'package:flutter_tools/src/doctor.dart'; import 'package:flutter_tools/src/doctor.dart';
import 'package:flutter_tools/src/globals.dart'; import 'package:flutter_tools/src/globals.dart';
import 'package:flutter_tools/src/proxy_validator.dart'; import 'package:flutter_tools/src/proxy_validator.dart';
import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:flutter_tools/src/vscode/vscode.dart'; import 'package:flutter_tools/src/vscode/vscode.dart';
import 'package:flutter_tools/src/vscode/vscode_validator.dart'; import 'package:flutter_tools/src/vscode/vscode_validator.dart';
import 'package:flutter_tools/src/reporting/usage.dart';
import '../../src/common.dart'; import '../../src/common.dart';
import '../../src/context.dart'; import '../../src/context.dart';
......
...@@ -10,7 +10,7 @@ import 'package:flutter_tools/src/base/io.dart'; ...@@ -10,7 +10,7 @@ import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/utils.dart'; import 'package:flutter_tools/src/base/utils.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/packages.dart'; import 'package:flutter_tools/src/commands/packages.dart';
import 'package:flutter_tools/src/reporting/usage.dart'; import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:process/process.dart'; import 'package:process/process.dart';
import '../../src/common.dart'; import '../../src/common.dart';
...@@ -226,7 +226,8 @@ void main() { ...@@ -226,7 +226,8 @@ void main() {
final PackagesCommand command = await runCommandIn(projectPath, 'get'); final PackagesCommand command = await runCommandIn(projectPath, 'get');
final PackagesGetCommand getCommand = command.subcommands['get'] as PackagesGetCommand; final PackagesGetCommand getCommand = command.subcommands['get'] as PackagesGetCommand;
expect(await getCommand.usageValues, containsPair(kCommandPackagesNumberPlugins, '0')); expect(await getCommand.usageValues,
containsPair(CustomDimensions.commandPackagesNumberPlugins, '0'));
}, timeout: allowForCreateFlutterProject); }, timeout: allowForCreateFlutterProject);
testUsingContext('indicate that the project is not a module in usage value', () async { testUsingContext('indicate that the project is not a module in usage value', () async {
...@@ -237,7 +238,8 @@ void main() { ...@@ -237,7 +238,8 @@ void main() {
final PackagesCommand command = await runCommandIn(projectPath, 'get'); final PackagesCommand command = await runCommandIn(projectPath, 'get');
final PackagesGetCommand getCommand = command.subcommands['get'] as PackagesGetCommand; final PackagesGetCommand getCommand = command.subcommands['get'] as PackagesGetCommand;
expect(await getCommand.usageValues, containsPair(kCommandPackagesProjectModule, 'false')); expect(await getCommand.usageValues,
containsPair(CustomDimensions.commandPackagesProjectModule, 'false'));
}, timeout: allowForCreateFlutterProject); }, timeout: allowForCreateFlutterProject);
testUsingContext('indicate that the project is a module in usage value', () async { testUsingContext('indicate that the project is a module in usage value', () async {
...@@ -248,7 +250,8 @@ void main() { ...@@ -248,7 +250,8 @@ void main() {
final PackagesCommand command = await runCommandIn(projectPath, 'get'); final PackagesCommand command = await runCommandIn(projectPath, 'get');
final PackagesGetCommand getCommand = command.subcommands['get'] as PackagesGetCommand; final PackagesGetCommand getCommand = command.subcommands['get'] as PackagesGetCommand;
expect(await getCommand.usageValues, containsPair(kCommandPackagesProjectModule, 'true')); expect(await getCommand.usageValues,
containsPair(CustomDimensions.commandPackagesProjectModule, 'true'));
}, timeout: allowForCreateFlutterProject); }, timeout: allowForCreateFlutterProject);
testUsingContext('upgrade fetches packages', () async { testUsingContext('upgrade fetches packages', () async {
......
...@@ -6,9 +6,9 @@ import 'dart:async'; ...@@ -6,9 +6,9 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:flutter_tools/src/base/common.dart'; import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/context.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/context.dart';
import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/terminal.dart'; import 'package:flutter_tools/src/base/terminal.dart';
......
...@@ -8,17 +8,16 @@ import 'dart:convert'; ...@@ -8,17 +8,16 @@ import 'dart:convert';
import 'package:file/file.dart'; import 'package:file/file.dart';
import 'package:file/local.dart'; import 'package:file/local.dart';
import 'package:file/memory.dart'; import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:http/http.dart';
import 'package:http/testing.dart';
import 'package:flutter_tools/runner.dart' as tools; import 'package:flutter_tools/runner.dart' as tools;
import 'package:flutter_tools/src/base/context.dart'; import 'package:flutter_tools/src/base/context.dart';
import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/reporting/crash_reporting.dart'; import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:flutter_tools/src/runner/flutter_command.dart'; import 'package:flutter_tools/src/runner/flutter_command.dart';
import 'package:http/http.dart';
import 'package:http/testing.dart';
import 'package:pedantic/pedantic.dart'; import 'package:pedantic/pedantic.dart';
import '../src/common.dart'; import '../src/common.dart';
......
...@@ -129,6 +129,7 @@ void main() { ...@@ -129,6 +129,7 @@ void main() {
final MockDevice mockDevice = MockDevice(); final MockDevice mockDevice = MockDevice();
when(mockDevice.supportsHotReload).thenReturn(true); when(mockDevice.supportsHotReload).thenReturn(true);
when(mockDevice.supportsHotRestart).thenReturn(false); when(mockDevice.supportsHotRestart).thenReturn(false);
when(mockDevice.targetPlatform).thenAnswer((Invocation _) async => TargetPlatform.tester);
// Trigger hot restart. // Trigger hot restart.
final List<FlutterDevice> devices = <FlutterDevice>[ final List<FlutterDevice> devices = <FlutterDevice>[
FlutterDevice(mockDevice, generator: residentCompiler, trackWidgetCreation: false, buildMode: BuildMode.debug)..devFS = mockDevFs, FlutterDevice(mockDevice, generator: residentCompiler, trackWidgetCreation: false, buildMode: BuildMode.debug)..devFS = mockDevFs,
...@@ -190,6 +191,7 @@ void main() { ...@@ -190,6 +191,7 @@ void main() {
final MockDevice mockDevice = MockDevice(); final MockDevice mockDevice = MockDevice();
when(mockDevice.supportsHotReload).thenReturn(true); when(mockDevice.supportsHotReload).thenReturn(true);
when(mockDevice.supportsHotRestart).thenReturn(true); when(mockDevice.supportsHotRestart).thenReturn(true);
when(mockDevice.targetPlatform).thenAnswer((Invocation _) async => TargetPlatform.tester);
final List<FlutterDevice> devices = <FlutterDevice>[ final List<FlutterDevice> devices = <FlutterDevice>[
FlutterDevice(mockDevice, generator: residentCompiler, trackWidgetCreation: false, buildMode: BuildMode.debug), FlutterDevice(mockDevice, generator: residentCompiler, trackWidgetCreation: false, buildMode: BuildMode.debug),
]; ];
...@@ -206,6 +208,7 @@ void main() { ...@@ -206,6 +208,7 @@ void main() {
final MockDevice mockDevice = MockDevice(); final MockDevice mockDevice = MockDevice();
when(mockDevice.supportsHotReload).thenReturn(true); when(mockDevice.supportsHotReload).thenReturn(true);
when(mockDevice.supportsHotRestart).thenReturn(true); when(mockDevice.supportsHotRestart).thenReturn(true);
when(mockDevice.targetPlatform).thenAnswer((Invocation _) async => TargetPlatform.tester);
// Trigger hot restart. // Trigger hot restart.
final List<FlutterDevice> devices = <FlutterDevice>[ final List<FlutterDevice> devices = <FlutterDevice>[
FlutterDevice(mockDevice, generator: residentCompiler, trackWidgetCreation: false, buildMode: BuildMode.debug)..devFS = mockDevFs, FlutterDevice(mockDevice, generator: residentCompiler, trackWidgetCreation: false, buildMode: BuildMode.debug)..devFS = mockDevFs,
......
...@@ -5,14 +5,14 @@ ...@@ -5,14 +5,14 @@
import 'dart:async'; import 'dart:async';
import 'package:file/file.dart'; import 'package:file/file.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart' show ProcessException, ProcessResult; import 'package:flutter_tools/src/base/io.dart' show ProcessException, ProcessResult;
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/ios/mac.dart'; import 'package:flutter_tools/src/ios/mac.dart';
import 'package:flutter_tools/src/ios/xcodeproj.dart'; import 'package:flutter_tools/src/ios/xcodeproj.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/reporting/usage.dart'; import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
import 'package:platform/platform.dart'; import 'package:platform/platform.dart';
import 'package:process/process.dart'; import 'package:process/process.dart';
...@@ -183,9 +183,10 @@ void main() { ...@@ -183,9 +183,10 @@ void main() {
); );
await diagnoseXcodeBuildFailure(buildResult); await diagnoseXcodeBuildFailure(buildResult);
verify(mockUsage.sendEvent('Xcode', 'bitcode-failure', parameters: <String, String>{ verify(mockUsage.sendEvent('build', 'xcode-bitcode-failure',
'build-commands': buildCommands.toString(), parameters: <String, String>{
'build-settings': buildSettings.toString(), cdKey(CustomDimensions.buildEventCommand): buildCommands.toString(),
cdKey(CustomDimensions.buildEventSettings): buildSettings.toString(),
})).called(1); })).called(1);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Usage: () => mockUsage, Usage: () => mockUsage,
......
...@@ -4,17 +4,17 @@ ...@@ -4,17 +4,17 @@
import 'dart:async'; import 'dart:async';
import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/common.dart'; import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/context.dart'; import 'package:flutter_tools/src/base/context.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/flutter_manifest.dart'; import 'package:flutter_tools/src/flutter_manifest.dart';
import 'package:flutter_tools/src/ios/ios_workflow.dart'; import 'package:flutter_tools/src/ios/ios_workflow.dart';
import 'package:flutter_tools/src/ios/xcodeproj.dart'; import 'package:flutter_tools/src/ios/xcodeproj.dart';
import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
......
...@@ -11,7 +11,7 @@ import 'package:flutter_tools/src/base/logger.dart'; ...@@ -11,7 +11,7 @@ import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/devfs.dart'; import 'package:flutter_tools/src/devfs.dart';
import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/reporting/usage.dart'; import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:flutter_tools/src/resident_runner.dart'; import 'package:flutter_tools/src/resident_runner.dart';
import 'package:flutter_tools/src/run_hot.dart'; import 'package:flutter_tools/src/run_hot.dart';
import 'package:flutter_tools/src/vmservice.dart'; import 'package:flutter_tools/src/vmservice.dart';
...@@ -53,6 +53,7 @@ void main() { ...@@ -53,6 +53,7 @@ void main() {
when(mockDevFS.lastCompiled).thenReturn(DateTime(2000)); when(mockDevFS.lastCompiled).thenReturn(DateTime(2000));
when(mockDevFS.sources).thenReturn(<Uri>[]); when(mockDevFS.sources).thenReturn(<Uri>[]);
when(mockDevFS.destroy()).thenAnswer((Invocation invocation) async { }); when(mockDevFS.destroy()).thenAnswer((Invocation invocation) async { });
when(mockDevFS.assetPathsToEvict).thenReturn(<String>{});
// FlutterDevice Mocks. // FlutterDevice Mocks.
when(mockFlutterDevice.updateDevFS( when(mockFlutterDevice.updateDevFS(
// Intentionally provide empty list to match above mock. // Intentionally provide empty list to match above mock.
...@@ -96,6 +97,19 @@ void main() { ...@@ -96,6 +97,19 @@ void main() {
mockVMService, mockVMService,
]); ]);
when(mockFlutterDevice.refreshViews()).thenAnswer((Invocation invocation) async { }); when(mockFlutterDevice.refreshViews()).thenAnswer((Invocation invocation) async { });
when(mockFlutterDevice.reloadSources(any, pause: anyNamed('pause'))).thenReturn(<Future<Map<String, dynamic>>>[
Future<Map<String, dynamic>>.value(<String, dynamic>{
'type': 'ReloadReport',
'success': true,
'details': <String, dynamic>{
'loadedLibraryCount': 1,
'finalLibraryCount': 1,
'receivedLibraryCount': 1,
'receivedClassesCount': 1,
'receivedProceduresCount': 1,
},
}),
]);
// VMService mocks. // VMService mocks.
when(mockVMService.wsAddress).thenReturn(testUri); when(mockVMService.wsAddress).thenReturn(testUri);
when(mockVMService.done).thenAnswer((Invocation invocation) { when(mockVMService.done).thenAnswer((Invocation invocation) {
...@@ -108,6 +122,9 @@ void main() { ...@@ -108,6 +122,9 @@ void main() {
when(mockIsolate.flutterExit()).thenAnswer((Invocation invocation) { when(mockIsolate.flutterExit()).thenAnswer((Invocation invocation) {
return Future<Map<String, Object>>.value(null); return Future<Map<String, Object>>.value(null);
}); });
when(mockIsolate.reload()).thenAnswer((Invocation invocation) {
return Future<ServiceObject>.value(null);
});
}); });
test('ResidentRunner can attach to device successfully', () => testbed.run(() async { test('ResidentRunner can attach to device successfully', () => testbed.run(() async {
...@@ -162,15 +179,48 @@ void main() { ...@@ -162,15 +179,48 @@ void main() {
expect(result.fatal, true); expect(result.fatal, true);
expect(result.code, 1); expect(result.code, 1);
verify(flutterUsage.sendEvent('hot', 'exception', parameters: <String, String>{ verify(flutterUsage.sendEvent('hot', 'exception', parameters: <String, String>{
reloadExceptionTargetPlatform: getNameForTargetPlatform(TargetPlatform.android_arm), cdKey(CustomDimensions.hotEventTargetPlatform):
reloadExceptionSdkName: 'Example', getNameForTargetPlatform(TargetPlatform.android_arm),
reloadExceptionEmulator: 'false', cdKey(CustomDimensions.hotEventSdkName): 'Example',
reloadExceptionFullRestart: 'false', cdKey(CustomDimensions.hotEventEmulator): 'false',
cdKey(CustomDimensions.hotEventFullRestart): 'false',
})).called(1); })).called(1);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Usage: () => MockUsage(), Usage: () => MockUsage(),
})); }));
// Need one for hot restart as well.
test('ResidentRunner can send target platform to analytics from hot reload', () => testbed.run(() async {
when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async {
return 'Example';
});
when(mockDevice.targetPlatform).thenAnswer((Invocation invocation) async {
return TargetPlatform.android_arm;
});
when(mockDevice.isLocalEmulator).thenAnswer((Invocation invocation) async {
return false;
});
final Completer<DebugConnectionInfo> onConnectionInfo = Completer<DebugConnectionInfo>.sync();
final Completer<void> onAppStart = Completer<void>.sync();
unawaited(residentRunner.attach(
appStartedCompleter: onAppStart,
connectionInfoCompleter: onConnectionInfo,
));
final OperationResult result = await residentRunner.restart(fullRestart: false);
expect(result.fatal, false);
expect(result.code, 0);
expect(verify(flutterUsage.sendEvent('hot', 'reload',
parameters: captureAnyNamed('parameters'))).captured[0],
containsPair(cdKey(CustomDimensions.hotEventTargetPlatform),
getNameForTargetPlatform(TargetPlatform.android_arm))
);
}, overrides: <Type, Generator>{
Usage: () => MockUsage(),
}));
test('ResidentRunner Can handle an RPC exception from hot restart', () => testbed.run(() async { test('ResidentRunner Can handle an RPC exception from hot restart', () => testbed.run(() async {
when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async { when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async {
return 'Example'; return 'Example';
...@@ -206,10 +256,11 @@ void main() { ...@@ -206,10 +256,11 @@ void main() {
expect(result.fatal, true); expect(result.fatal, true);
expect(result.code, 1); expect(result.code, 1);
verify(flutterUsage.sendEvent('hot', 'exception', parameters: <String, String>{ verify(flutterUsage.sendEvent('hot', 'exception', parameters: <String, String>{
reloadExceptionTargetPlatform: getNameForTargetPlatform(TargetPlatform.android_arm), cdKey(CustomDimensions.hotEventTargetPlatform):
reloadExceptionSdkName: 'Example', getNameForTargetPlatform(TargetPlatform.android_arm),
reloadExceptionEmulator: 'false', cdKey(CustomDimensions.hotEventSdkName): 'Example',
reloadExceptionFullRestart: 'true', cdKey(CustomDimensions.hotEventEmulator): 'false',
cdKey(CustomDimensions.hotEventFullRestart): 'true',
})).called(1); })).called(1);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Usage: () => MockUsage(), Usage: () => MockUsage(),
......
...@@ -2,10 +2,10 @@ ...@@ -2,10 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/base/time.dart';
import 'package:flutter_tools/src/reporting/usage.dart';
import 'package:flutter_tools/src/base/common.dart'; import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/time.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:flutter_tools/src/runner/flutter_command.dart'; import 'package:flutter_tools/src/runner/flutter_command.dart';
import 'package:flutter_tools/src/version.dart'; import 'package:flutter_tools/src/version.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/reporting/usage.dart'; import 'package:flutter_tools/src/reporting/reporting.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';
......
...@@ -5,9 +5,6 @@ ...@@ -5,9 +5,6 @@
import 'dart:async'; import 'dart:async';
import 'package:args/command_runner.dart'; import 'package:args/command_runner.dart';
import 'package:test_api/test_api.dart' hide TypeMatcher, isInstanceOf;
import 'package:test_api/test_api.dart' as test_package show TypeMatcher;
import 'package:flutter_tools/src/base/common.dart'; import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/platform.dart';
...@@ -15,6 +12,8 @@ import 'package:flutter_tools/src/base/process.dart'; ...@@ -15,6 +12,8 @@ import 'package:flutter_tools/src/base/process.dart';
import 'package:flutter_tools/src/commands/create.dart'; import 'package:flutter_tools/src/commands/create.dart';
import 'package:flutter_tools/src/runner/flutter_command.dart'; import 'package:flutter_tools/src/runner/flutter_command.dart';
import 'package:flutter_tools/src/runner/flutter_command_runner.dart'; import 'package:flutter_tools/src/runner/flutter_command_runner.dart';
import 'package:test_api/test_api.dart' as test_package show TypeMatcher;
import 'package:test_api/test_api.dart' hide TypeMatcher, isInstanceOf;
export 'package:test_core/test_core.dart' hide TypeMatcher, isInstanceOf; // Defines a 'package:test' shim. export 'package:test_core/test_core.dart' hide TypeMatcher, isInstanceOf; // Defines a 'package:test' shim.
......
...@@ -13,15 +13,15 @@ import 'package:flutter_tools/src/base/io.dart'; ...@@ -13,15 +13,15 @@ import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/os.dart'; import 'package:flutter_tools/src/base/os.dart';
import 'package:flutter_tools/src/base/terminal.dart'; import 'package:flutter_tools/src/base/terminal.dart';
import 'package:flutter_tools/src/base/time.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/context_runner.dart'; import 'package:flutter_tools/src/context_runner.dart';
import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/doctor.dart'; import 'package:flutter_tools/src/doctor.dart';
import 'package:flutter_tools/src/ios/simulators.dart'; import 'package:flutter_tools/src/ios/simulators.dart';
import 'package:flutter_tools/src/ios/xcodeproj.dart'; import 'package:flutter_tools/src/ios/xcodeproj.dart';
import 'package:flutter_tools/src/base/time.dart';
import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/reporting/usage.dart'; import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:flutter_tools/src/version.dart'; import 'package:flutter_tools/src/version.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
......
...@@ -8,17 +8,17 @@ import 'dart:io'; ...@@ -8,17 +8,17 @@ import 'dart:io';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:file/memory.dart'; import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/os.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/context.dart'; import 'package:flutter_tools/src/base/context.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/os.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/terminal.dart'; import 'package:flutter_tools/src/base/terminal.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/context_runner.dart'; import 'package:flutter_tools/src/context_runner.dart';
import 'package:flutter_tools/src/features.dart'; import 'package:flutter_tools/src/features.dart';
import 'package:flutter_tools/src/reporting/usage.dart'; import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:flutter_tools/src/version.dart'; import 'package:flutter_tools/src/version.dart';
import 'context.dart'; import 'context.dart';
......
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