diff --git a/packages/flutter_tools/bin/fuchsia_asset_builder.dart b/packages/flutter_tools/bin/fuchsia_asset_builder.dart index 063056b1ce..f6974c77f2 100644 --- a/packages/flutter_tools/bin/fuchsia_asset_builder.dart +++ b/packages/flutter_tools/bin/fuchsia_asset_builder.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/bundle.dart'; import 'package:flutter_tools/src/globals.dart'; -import 'package:flutter_tools/src/reporting/disabled_usage.dart'; -import 'package:flutter_tools/src/reporting/usage.dart'; +import 'package:flutter_tools/src/reporting/reporting.dart'; const String _kOptionPackages = 'packages'; const String _kOptionAsset = 'asset-dir'; diff --git a/packages/flutter_tools/bin/fuchsia_tester.dart b/packages/flutter_tools/bin/fuchsia_tester.dart index b78eea375d..7fb9364611 100644 --- a/packages/flutter_tools/bin/fuchsia_tester.dart +++ b/packages/flutter_tools/bin/fuchsia_tester.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/globals.dart'; import 'package:flutter_tools/src/project.dart'; -import 'package:flutter_tools/src/reporting/disabled_usage.dart'; -import 'package:flutter_tools/src/reporting/usage.dart'; +import 'package:flutter_tools/src/reporting/reporting.dart'; import 'package:flutter_tools/src/test/coverage_collector.dart'; import 'package:flutter_tools/src/test/runner.dart'; diff --git a/packages/flutter_tools/lib/runner.dart b/packages/flutter_tools/lib/runner.dart index 398f5a6034..f63553c5ca 100644 --- a/packages/flutter_tools/lib/runner.dart +++ b/packages/flutter_tools/lib/runner.dart @@ -19,8 +19,7 @@ import 'src/base/utils.dart'; import 'src/context_runner.dart'; import 'src/doctor.dart'; import 'src/globals.dart'; -import 'src/reporting/crash_reporting.dart'; -import 'src/reporting/usage.dart'; +import 'src/reporting/reporting.dart'; import 'src/runner/flutter_command.dart'; import 'src/runner/flutter_command_runner.dart'; import 'src/version.dart'; diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart index edcf064b49..ce041479e1 100644 --- a/packages/flutter_tools/lib/src/android/gradle.dart +++ b/packages/flutter_tools/lib/src/android/gradle.dart @@ -24,8 +24,7 @@ import '../features.dart'; import '../flutter_manifest.dart'; import '../globals.dart'; import '../project.dart'; -import '../reporting/usage.dart'; -import '../runner/flutter_command.dart'; +import '../reporting/reporting.dart'; import 'android_sdk.dart'; import 'android_studio.dart'; @@ -747,11 +746,7 @@ Future _buildGradleProjectV2( 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('*******************************************************************************************'); - String commandName = ''; - if (FlutterCommand.current != null) { - commandName = '-${FlutterCommand.current.name}'; - } - flutterUsage.sendEvent('build$commandName', 'android-x-failure'); + BuildEvent('android-x-failure').send(); } throwToolExit('Gradle task $assembleTask failed with exit code $exitCode', exitCode: exitCode); } diff --git a/packages/flutter_tools/lib/src/base/build.dart b/packages/flutter_tools/lib/src/base/build.dart index ca09c3e6f1..a29762b9ae 100644 --- a/packages/flutter_tools/lib/src/base/build.dart +++ b/packages/flutter_tools/lib/src/base/build.dart @@ -14,7 +14,7 @@ import '../dart/package_map.dart'; import '../globals.dart'; import '../macos/xcode.dart'; import '../project.dart'; -import '../reporting/usage.dart'; +import '../reporting/reporting.dart'; import 'context.dart'; import 'file_system.dart'; diff --git a/packages/flutter_tools/lib/src/commands/build_aar.dart b/packages/flutter_tools/lib/src/commands/build_aar.dart index e40f522c7e..73c415aef5 100644 --- a/packages/flutter_tools/lib/src/commands/build_aar.dart +++ b/packages/flutter_tools/lib/src/commands/build_aar.dart @@ -9,7 +9,7 @@ import '../base/context.dart'; import '../base/os.dart'; import '../build_info.dart'; import '../project.dart'; -import '../reporting/usage.dart'; +import '../reporting/reporting.dart'; import '../runner/flutter_command.dart' show DevelopmentArtifact, FlutterCommandResult; import 'build.dart'; @@ -38,20 +38,20 @@ class BuildAarCommand extends BuildSubCommand { final String name = 'aar'; @override - Future> get usageValues async { - final Map usage = {}; + Future> get usageValues async { + final Map usage = {}; final FlutterProject futterProject = _getProject(); if (futterProject == null) { return usage; } if (futterProject.manifest.isModule) { - usage[kCommandBuildAarProjectType] = 'module'; + usage[CustomDimensions.commandBuildAarProjectType] = 'module'; } else if (futterProject.manifest.isPlugin) { - usage[kCommandBuildAarProjectType] = 'plugin'; + usage[CustomDimensions.commandBuildAarProjectType] = 'plugin'; } else { - usage[kCommandBuildAarProjectType] = 'app'; + usage[CustomDimensions.commandBuildAarProjectType] = 'app'; } - usage[kCommandBuildAarTargetPlatform] = + usage[CustomDimensions.commandBuildAarTargetPlatform] = (argResults['target-platform'] as List).join(','); return usage; } diff --git a/packages/flutter_tools/lib/src/commands/build_bundle.dart b/packages/flutter_tools/lib/src/commands/build_bundle.dart index 7e3e21f45b..92b4474d96 100644 --- a/packages/flutter_tools/lib/src/commands/build_bundle.dart +++ b/packages/flutter_tools/lib/src/commands/build_bundle.dart @@ -10,7 +10,7 @@ import '../build_info.dart'; import '../bundle.dart'; import '../features.dart'; import '../project.dart'; -import '../reporting/usage.dart'; +import '../reporting/reporting.dart'; import '../runner/flutter_command.dart' show FlutterOptions, FlutterCommandResult; import 'build.dart'; @@ -74,17 +74,15 @@ class BuildBundleCommand extends BuildSubCommand { ' iOS runtimes.'; @override - Future> get usageValues async { + Future> get usageValues async { final String projectDir = fs.file(targetFile).parent.parent.path; final FlutterProject futterProject = FlutterProject.fromPath(projectDir); - if (futterProject == null) { - return const {}; + return const {}; } - - return { - kCommandBuildBundleTargetPlatform: argResults['target-platform'], - kCommandBuildBundleIsModule: '${futterProject.isModule}' + return { + CustomDimensions.commandBuildBundleTargetPlatform: argResults['target-platform'], + CustomDimensions.commandBuildBundleIsModule: '${futterProject.isModule}' }; } diff --git a/packages/flutter_tools/lib/src/commands/config.dart b/packages/flutter_tools/lib/src/commands/config.dart index c98a9714dd..9e4c6b7e73 100644 --- a/packages/flutter_tools/lib/src/commands/config.dart +++ b/packages/flutter_tools/lib/src/commands/config.dart @@ -11,7 +11,7 @@ import '../base/file_system.dart'; import '../convert.dart'; import '../features.dart'; import '../globals.dart'; -import '../reporting/usage.dart'; +import '../reporting/reporting.dart'; import '../runner/flutter_command.dart'; import '../version.dart'; diff --git a/packages/flutter_tools/lib/src/commands/create.dart b/packages/flutter_tools/lib/src/commands/create.dart index 875beaeabf..98f9227346 100644 --- a/packages/flutter_tools/lib/src/commands/create.dart +++ b/packages/flutter_tools/lib/src/commands/create.dart @@ -23,7 +23,7 @@ import '../doctor.dart'; import '../features.dart'; import '../globals.dart'; import '../project.dart'; -import '../reporting/usage.dart'; +import '../reporting/reporting.dart'; import '../runner/flutter_command.dart'; import '../template.dart'; import '../version.dart'; @@ -165,11 +165,11 @@ class CreateCommand extends FlutterCommand { String get invocation => '${runner.executableName} $name '; @override - Future> get usageValues async { - return { - kCommandCreateProjectType: argResults['template'], - kCommandCreateAndroidLanguage: argResults['android-language'], - kCommandCreateIosLanguage: argResults['ios-language'], + Future> get usageValues async { + return { + CustomDimensions.commandCreateProjectType: argResults['template'], + CustomDimensions.commandCreateAndroidLanguage: argResults['android-language'], + CustomDimensions.commandCreateIosLanguage: argResults['ios-language'], }; } diff --git a/packages/flutter_tools/lib/src/commands/packages.dart b/packages/flutter_tools/lib/src/commands/packages.dart index 767a6066cf..8b60b93272 100644 --- a/packages/flutter_tools/lib/src/commands/packages.dart +++ b/packages/flutter_tools/lib/src/commands/packages.dart @@ -9,7 +9,7 @@ import '../base/os.dart'; import '../cache.dart'; import '../dart/pub.dart'; import '../project.dart'; -import '../reporting/usage.dart'; +import '../reporting/reporting.dart'; import '../runner/flutter_command.dart'; class PackagesCommand extends FlutterCommand { @@ -71,8 +71,8 @@ class PackagesGetCommand extends FlutterCommand { } @override - Future> get usageValues async { - final Map usageValues = {}; + Future> get usageValues async { + final Map usageValues = {}; final String workingDirectory = argResults.rest.length == 1 ? argResults.rest[0] : null; final String target = findProjectRoot(workingDirectory); if (target == null) { @@ -82,11 +82,11 @@ class PackagesGetCommand extends FlutterCommand { final bool hasPlugins = await rootProject.flutterPluginsFile.exists(); if (hasPlugins) { final int numberOfPlugins = (await rootProject.flutterPluginsFile.readAsLines()).length; - usageValues[kCommandPackagesNumberPlugins] = '$numberOfPlugins'; + usageValues[CustomDimensions.commandPackagesNumberPlugins] = '$numberOfPlugins'; } else { - usageValues[kCommandPackagesNumberPlugins] = '0'; + usageValues[CustomDimensions.commandPackagesNumberPlugins] = '0'; } - usageValues[kCommandPackagesProjectModule] = '${rootProject.isModule}'; + usageValues[CustomDimensions.commandPackagesProjectModule] = '${rootProject.isModule}'; return usageValues; } @@ -100,11 +100,11 @@ class PackagesGetCommand extends FlutterCommand { checkLastModified: false, ); pubGetTimer.stop(); - flutterUsage.sendEvent('packages-pub-get', 'success'); + PubGetEvent(success: true).send(); flutterUsage.sendTiming('packages-pub-get', 'success', pubGetTimer.elapsed); } catch (_) { pubGetTimer.stop(); - flutterUsage.sendEvent('packages-pub-get', 'failure'); + PubGetEvent(success: false).send(); flutterUsage.sendTiming('packages-pub-get', 'failure', pubGetTimer.elapsed); rethrow; } diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart index 091b0433fc..298386cd97 100644 --- a/packages/flutter_tools/lib/src/commands/run.dart +++ b/packages/flutter_tools/lib/src/commands/run.dart @@ -17,7 +17,7 @@ import '../features.dart'; import '../globals.dart'; import '../macos/xcode.dart'; import '../project.dart'; -import '../reporting/usage.dart'; +import '../reporting/reporting.dart'; import '../resident_runner.dart'; import '../resident_web_runner.dart'; import '../run_cold.dart'; @@ -198,7 +198,7 @@ class RunCommand extends RunCommandBase { } @override - Future> get usageValues async { + Future> get usageValues async { String deviceType, deviceOsVersion; bool isEmulator; @@ -227,13 +227,13 @@ class RunCommand extends RunCommandBase { hostLanguage.add(iosProject.isSwift ? 'swift' : 'objc'); } - return { - kCommandRunIsEmulator: '$isEmulator', - kCommandRunTargetName: deviceType, - kCommandRunTargetOsVersion: deviceOsVersion, - kCommandRunModeName: modeName, - kCommandRunProjectModule: '${FlutterProject.current().isModule}', - kCommandRunProjectHostLanguage: hostLanguage.join(','), + return { + CustomDimensions.commandRunIsEmulator: '$isEmulator', + CustomDimensions.commandRunTargetName: deviceType, + CustomDimensions.commandRunTargetOsVersion: deviceOsVersion, + CustomDimensions.commandRunModeName: modeName, + CustomDimensions.commandRunProjectModule: '${FlutterProject.current().isModule}', + CustomDimensions.commandRunProjectHostLanguage: hostLanguage.join(','), }; } diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart index f086f2850c..83985e5fce 100644 --- a/packages/flutter_tools/lib/src/context_runner.dart +++ b/packages/flutter_tools/lib/src/context_runner.dart @@ -42,7 +42,7 @@ import 'macos/cocoapods_validator.dart'; import 'macos/macos_workflow.dart'; import 'macos/xcode.dart'; import 'macos/xcode_validator.dart'; -import 'reporting/usage.dart'; +import 'reporting/reporting.dart'; import 'run_hot.dart'; import 'version.dart'; import 'web/chrome.dart'; diff --git a/packages/flutter_tools/lib/src/devfs.dart b/packages/flutter_tools/lib/src/devfs.dart index 7a28775de0..f2a5cf2e7c 100644 --- a/packages/flutter_tools/lib/src/devfs.dart +++ b/packages/flutter_tools/lib/src/devfs.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; + import 'package:json_rpc_2/json_rpc_2.dart' as rpc; import 'package:meta/meta.dart'; diff --git a/packages/flutter_tools/lib/src/doctor.dart b/packages/flutter_tools/lib/src/doctor.dart index cf697c7fe1..b2a0311032 100644 --- a/packages/flutter_tools/lib/src/doctor.dart +++ b/packages/flutter_tools/lib/src/doctor.dart @@ -31,7 +31,7 @@ import 'macos/cocoapods_validator.dart'; import 'macos/macos_workflow.dart'; import 'macos/xcode_validator.dart'; import 'proxy_validator.dart'; -import 'reporting/usage.dart'; +import 'reporting/reporting.dart'; import 'tester/flutter_tester.dart'; import 'version.dart'; import 'vscode/vscode_validator.dart'; @@ -236,7 +236,7 @@ class Doctor { break; } - flutterUsage.sendEvent('doctorResult.${validator.runtimeType.toString()}', result.typeStr); + DoctorResultEvent(validator: validator, result: result).send(); if (result.statusInfo != null) { printStatus('${result.coloredLeadingBox} ${validator.title} (${result.statusInfo})', diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_build.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_build.dart index d52b0242f2..cb357cadb4 100644 --- a/packages/flutter_tools/lib/src/fuchsia/fuchsia_build.dart +++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_build.dart @@ -16,7 +16,7 @@ import '../convert.dart'; import '../devfs.dart'; import '../globals.dart'; import '../project.dart'; -import '../reporting/usage.dart'; +import '../reporting/reporting.dart'; import 'fuchsia_pm.dart'; import 'fuchsia_sdk.dart'; diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart index 5b5ca1f711..bbbf868503 100644 --- a/packages/flutter_tools/lib/src/ios/mac.dart +++ b/packages/flutter_tools/lib/src/ios/mac.dart @@ -24,7 +24,7 @@ import '../globals.dart'; import '../macos/cocoapod_utils.dart'; import '../macos/xcode.dart'; import '../project.dart'; -import '../reporting/usage.dart'; +import '../reporting/reporting.dart'; import '../services.dart'; import 'code_signing.dart'; import 'xcodeproj.dart'; @@ -472,13 +472,10 @@ Future diagnoseXcodeBuildFailure(XcodeBuildResult result) async { if (result.xcodeBuildExecution != null && result.xcodeBuildExecution.buildForPhysicalDevice && result.stdout?.toUpperCase()?.contains('BITCODE') == true) { - flutterUsage.sendEvent( - 'Xcode', - 'bitcode-failure', - parameters: { - 'build-commands': result.xcodeBuildExecution.buildCommands.toString(), - 'build-settings': result.xcodeBuildExecution.buildSettings.toString(), - }); + BuildEvent('xcode-bitcode-failure', + command: result.xcodeBuildExecution.buildCommands.toString(), + settings: result.xcodeBuildExecution.buildSettings.toString(), + ).send(); } if (result.xcodeBuildExecution != null && diff --git a/packages/flutter_tools/lib/src/linux/build_linux.dart b/packages/flutter_tools/lib/src/linux/build_linux.dart index 1271e38284..0e6fd7e442 100644 --- a/packages/flutter_tools/lib/src/linux/build_linux.dart +++ b/packages/flutter_tools/lib/src/linux/build_linux.dart @@ -13,7 +13,7 @@ import '../cache.dart'; import '../convert.dart'; import '../globals.dart'; import '../project.dart'; -import '../reporting/usage.dart'; +import '../reporting/reporting.dart'; /// Builds the Linux project through the Makefile. Future buildLinux(LinuxProject linuxProject, BuildInfo buildInfo, {String target = 'lib/main.dart'}) async { diff --git a/packages/flutter_tools/lib/src/macos/build_macos.dart b/packages/flutter_tools/lib/src/macos/build_macos.dart index 2ac3b54513..2b45525fcd 100644 --- a/packages/flutter_tools/lib/src/macos/build_macos.dart +++ b/packages/flutter_tools/lib/src/macos/build_macos.dart @@ -12,7 +12,7 @@ import '../convert.dart'; import '../globals.dart'; import '../ios/xcodeproj.dart'; import '../project.dart'; -import '../reporting/usage.dart'; +import '../reporting/reporting.dart'; import 'cocoapod_utils.dart'; diff --git a/packages/flutter_tools/lib/src/reporting/crash_reporting.dart b/packages/flutter_tools/lib/src/reporting/crash_reporting.dart index 5ab60f6597..e7d00429f1 100644 --- a/packages/flutter_tools/lib/src/reporting/crash_reporting.dart +++ b/packages/flutter_tools/lib/src/reporting/crash_reporting.dart @@ -2,17 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:async'; - -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'; +part of reporting; /// Tells crash backend that the error is from the Flutter CLI. const String _kProductId = 'Flutter_Tools'; diff --git a/packages/flutter_tools/lib/src/reporting/disabled_usage.dart b/packages/flutter_tools/lib/src/reporting/disabled_usage.dart index 68ad76a5bb..2e326da67e 100644 --- a/packages/flutter_tools/lib/src/reporting/disabled_usage.dart +++ b/packages/flutter_tools/lib/src/reporting/disabled_usage.dart @@ -2,9 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:async'; - -import 'usage.dart'; +part of reporting; class DisabledUsage implements Usage { @override diff --git a/packages/flutter_tools/lib/src/reporting/events.dart b/packages/flutter_tools/lib/src/reporting/events.dart new file mode 100644 index 0000000000..515314ea8e --- /dev/null +++ b/packages/flutter_tools/lib/src/reporting/events.dart @@ -0,0 +1,137 @@ +// 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 parameters = _useCdKeys({ + 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 parameters = _useCdKeys({ + 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'); +} diff --git a/packages/flutter_tools/lib/src/reporting/reporting.dart b/packages/flutter_tools/lib/src/reporting/reporting.dart new file mode 100644 index 0000000000..759f80809a --- /dev/null +++ b/packages/flutter_tools/lib/src/reporting/reporting.dart @@ -0,0 +1,30 @@ +// 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'; diff --git a/packages/flutter_tools/lib/src/reporting/usage.dart b/packages/flutter_tools/lib/src/reporting/usage.dart index 57d47a6ac3..5a5eed862f 100644 --- a/packages/flutter_tools/lib/src/reporting/usage.dart +++ b/packages/flutter_tools/lib/src/reporting/usage.dart @@ -2,78 +2,146 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:async'; - -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'; +part of reporting; const String _kFlutterUA = 'UA-67589403-6'; -// Attached to all `Usage.sendCommand` and `Usage.sendEvent`. -const String _kLocalTimeParameter = 'cd33'; +/// The collection of custom dimensions understood by the analytics backend. +/// 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'; -const String kSessionChannelName = 'cd2'; +String cdKey(CustomDimensions cd) => 'cd${cd.index + 1}'; -const String kEventReloadReasonParameterName = 'cd5'; -const String kEventReloadFinalLibraryCount = 'cd6'; -const String kEventReloadSyncedLibraryCount = 'cd7'; -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'; -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'; -const String kCommandCreateIosLanguage = 'cd17'; -const String kCommandCreateProjectType = 'cd19'; - -const String kCommandPackagesNumberPlugins = 'cd20'; -const String kCommandPackagesProjectModule = 'cd21'; - -const String kCommandBuildBundleTargetPlatform = 'cd24'; -const String kCommandBuildBundleIsModule = 'cd25'; - -const String kCommandResult = 'cd26'; -const String kCommandHasTerminal = 'cd31'; - -const String kCommandBuildAarTargetPlatform = 'cd34'; -const String kCommandBuildAarProjectType = 'cd35'; - -const String reloadExceptionTargetPlatform = 'cd27'; -const String reloadExceptionSdkName = 'cd28'; -const String reloadExceptionEmulator = 'cd29'; -const String reloadExceptionFullRestart = 'cd30'; - -const String enabledFlutterFeatures = 'cd32'; -// Next ID: cd36 +Map _useCdKeys(Map parameters) { + return parameters.map((CustomDimensions k, String v) => + MapEntry(cdKey(k), v)); +} Usage get flutterUsage => Usage.instance; -class Usage { +abstract class Usage { + factory Usage({ + String settingsName = 'flutter', + String versionOverride, + String configDirOverride + }) => _UsageImpl(settingsName: settingsName, + versionOverride: versionOverride, + configDirOverride: configDirOverride); + + /// Returns [Usage] active in the current app context. + static Usage get instance => context.get(); + + /// Uses the global [Usage] instance to send a 'command' to analytics. + static void command(String command, { + Map parameters, + }) => flutterUsage.sendCommand(command, parameters: _useCdKeys(parameters)); + + /// Whether this is the first run of the tool. + bool get isFirstRun; + + /// Whether analytics reporting should be supressed. + bool get suppressAnalytics; + + /// Suppress analytics for this session. + set suppressAnalytics(bool value); + + /// Whether analytics reporting is enabled. + bool get enabled; + + /// 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 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 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); + + /// Fires whenever analytics data is sent over the network. + @visibleForTesting + Stream> get onSend; + + /// Returns when the last analytics event has been sent, or after a fixed + /// (short) delay, whichever is less. + Future 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 /// 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 String version = versionOverride ?? flutterVersion.getVersionString(redactUnknownBranches: true); @@ -90,9 +158,10 @@ class Usage { LogToFileAnalytics(logFilePath); // 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". - _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 // separated list. final String enabledFeatures = allFeatures @@ -102,7 +171,7 @@ class Usage { }) .map((Feature feature) => feature.configSetting) .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. if (platform.environment.containsKey('FLUTTER_HOST')) { @@ -119,34 +188,34 @@ class Usage { } } - /// Returns [Usage] active in the current app context. - static Usage get instance => context.get(); - Analytics _analytics; bool _printedWelcome = false; bool _suppressAnalytics = false; + @override bool get isFirstRun => _analytics.firstRun; - bool get enabled => _analytics.enabled; - + @override bool get suppressAnalytics => _suppressAnalytics || _analytics.firstRun; - /// Suppress analytics for this session. + @override set suppressAnalytics(bool value) { _suppressAnalytics = value; } - /// Enable or disable reporting analytics. + @override + bool get enabled => _analytics.enabled; + + @override set enabled(bool value) { _analytics.enabled = value; } - /// A stable randomly generated UUID used to deduplicate multiple identical - /// reports coming from the same computer. + @override String get clientId => _analytics.clientId; + @override void sendCommand(String command, { Map parameters }) { if (suppressAnalytics) { return; @@ -154,11 +223,12 @@ class Usage { final Map paramsWithLocalTime = { ...?parameters, - _kLocalTimeParameter: systemClock.now().toString(), + cdKey(CustomDimensions.localTime): systemClock.now().toString(), }; _analytics.sendScreenView(command, parameters: paramsWithLocalTime); } + @override void sendEvent( String category, String parameter, { @@ -170,12 +240,13 @@ class Usage { final Map paramsWithLocalTime = { ...?parameters, - _kLocalTimeParameter: systemClock.now().toString(), + cdKey(CustomDimensions.localTime): systemClock.now().toString(), }; _analytics.sendEvent(category, parameter, parameters: paramsWithLocalTime); } + @override void sendTiming( String category, String variableName, @@ -193,6 +264,7 @@ class Usage { ); } + @override void sendException(dynamic exception) { if (suppressAnalytics) { return; @@ -200,12 +272,10 @@ class Usage { _analytics.sendException(exception.runtimeType.toString()); } - /// Fires whenever analytics data is sent over the network. - @visibleForTesting + @override Stream> get onSend => _analytics.onSend; - /// Returns when the last analytics event has been sent, or after a fixed - /// (short) delay, whichever is less. + @override Future ensureAnalyticsSent() async { // TODO(devoncarew): This may delay tool exit and could cause some analytics // events to not be reported. Perhaps we could send the analytics pings @@ -213,6 +283,7 @@ class Usage { await _analytics.waitForLastPing(timeout: const Duration(milliseconds: 250)); } + @override void printWelcome() { // This gets called if it's the first run by the selected command, if any, // and on exit, in case there was no command. @@ -255,7 +326,9 @@ class LogToFileAnalytics extends AnalyticsMock { final Map _sessionValues = {}; @override - Future sendScreenView(String viewName, {Map parameters}) { + Future sendScreenView(String viewName, { + Map parameters, + }) { parameters ??= {}; parameters['viewName'] = viewName; parameters.addAll(_sessionValues); diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart index 0e6eb3ffda..08a4956fb4 100644 --- a/packages/flutter_tools/lib/src/run_hot.dart +++ b/packages/flutter_tools/lib/src/run_hot.dart @@ -21,7 +21,7 @@ import 'convert.dart'; import 'devfs.dart'; import 'device.dart'; import 'globals.dart'; -import 'reporting/usage.dart'; +import 'reporting/reporting.dart'; import 'resident_runner.dart'; import 'vmservice.dart'; @@ -367,15 +367,12 @@ class HotRunner extends ResidentRunner { futures.add(view.flushUIThreadTasks()); await Future.wait(futures); } - } - Future _restartFromSources({ String reason, bool benchmarkMode = false }) async { - final Map analyticsParameters = - reason == null - ? null - : {kEventReloadReasonParameterName: reason}; - + Future _restartFromSources({ + String reason, + bool benchmarkMode = false + }) async { if (!_isPaused()) { printTrace('Refreshing active FlutterViews before restarting.'); await refreshViews(); @@ -387,8 +384,9 @@ class HotRunner extends ResidentRunner { final UpdateFSReport updatedDevFS = await _updateDevFS(fullRestart: true); if (!updatedDevFS.success) { for (FlutterDevice device in flutterDevices) { - if (device.generator != null) + if (device.generator != null) { await device.generator.reject(); + } } return OperationResult(1, 'DevFS synchronization failed'); } @@ -396,30 +394,32 @@ class HotRunner extends ResidentRunner { for (FlutterDevice device in flutterDevices) { // VM must have accepted the kernel binary, there will be no reload // report, so we let incremental compiler know that source code was accepted. - if (device.generator != null) + if (device.generator != null) { device.generator.accept(); + } } // Check if the isolate is paused and resume it. final List> futures = >[]; for (FlutterDevice device in flutterDevices) { for (FlutterView view in device.views) { - if (view.uiIsolate != null) { - // Reload the isolate. - final Completer completer = Completer(); - 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); }, - )); + if (view.uiIsolate == null) { + continue; } + // Reload the isolate. + final Completer completer = Completer(); + 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); @@ -432,7 +432,8 @@ class HotRunner extends ResidentRunner { _runningFromSnapshot = false; _addBenchmarkData('hotRestartMillisecondsToFrame', restartTimer.elapsed.inMilliseconds); - flutterUsage.sendEvent('hot', 'restart', parameters: analyticsParameters); + + // Send timing analytics. flutterUsage.sendTiming('hot', 'restart', restartTimer.elapsed); // In benchmark mode, make sure all stream notifications have finished. @@ -499,81 +500,156 @@ class HotRunner extends ResidentRunner { bool get supportsRestart => true; @override - Future restart({ bool fullRestart = false, bool pauseAfterRestart = false, String reason, bool benchmarkMode = false }) async { + Future 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(); if (fullRestart) { - if (!canHotRestart) { - return OperationResult(1, 'hotRestart not supported'); - } - final Status status = logger.startProgress( - 'Performing hot restart...', - timeout: timeoutConfiguration.fastOperation, - progressId: 'hot.restart', + final OperationResult result = await _fullRestartHelper( + targetPlatform: targetPlatform, + sdkName: sdkName, + emulator: emulator, + reason: reason, + benchmarkMode: benchmarkMode, ); - 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)}.'); - return OperationResult.ok; - } else { - 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; - bool showTime = true; - try { - result = await _reloadSources( - pause: pauseAfterRestart, - reason: reason, - onSlow: (String message) { - status?.cancel(); - status = logger.startProgress( - message, - timeout: timeoutConfiguration.slowOperation, - progressId: 'hot.reload', - ); - showTime = false; - }, - ); - } on rpc.RpcException { - await _measureJsonRpcException(flutterDevices, fullRestart); - return OperationResult(1, 'hot reload failed to complete', fatal: true); - } finally { - status.cancel(); - } - if (result.isOk) { - if (showTime) { - printStatus('${result.message} in ${getElapsedAsMilliseconds(timer.elapsed)}.'); - } else { - printStatus('${result.message}.'); - } - } return result; } + final OperationResult result = await _hotReloadHelper( + targetPlatform: targetPlatform, + sdkName: sdkName, + emulator: emulator, + reason: reason, + pauseAfterRestart: pauseAfterRestart, + ); + if (result.isOk) { + final String elapsed = getElapsedAsMilliseconds(timer.elapsed); + printStatus('${result.message} in $elapsed.'); + } + return result; } - Future _reloadSources({ bool pause = false, String reason, void Function(String message) onSlow }) async { - final Map analyticsParameters = {}; - if (reason != null) { - analyticsParameters[kEventReloadReasonParameterName] = reason; + Future _fullRestartHelper({ + String targetPlatform, + String sdkName, + bool emulator, + String reason, + bool benchmarkMode, + }) async { + if (!canHotRestart) { + return OperationResult(1, 'hotRestart not supported'); } + final Status status = logger.startProgress( + 'Performing hot restart...', + timeout: timeoutConfiguration.fastOperation, + progressId: 'hot.restart', + ); + OperationResult result; + String restartEvent = 'restart'; + try { + if (!(await hotRunnerConfig.setupHotRestart())) { + return OperationResult(1, 'setupHotRestart failed'); + } + result = await _restartFromSources( + reason: reason, + benchmarkMode: benchmarkMode, + ); + if (!result.isOk) { + restartEvent = 'restart-failed'; + } + } 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 _hotReloadHelper({ + String targetPlatform, + String sdkName, + 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 _reloadSources({ + String targetPlatform, + String sdkName, + bool emulator, + bool pause = false, + String reason, + void Function(String message) onSlow + }) async { for (FlutterDevice device in flutterDevices) { for (FlutterView view in device.views) { - if (view.uiIsolate == null) + if (view.uiIsolate == null) { throw 'Application isolate not found'; + } } } @@ -595,10 +671,12 @@ class HotRunner extends ResidentRunner { final UpdateFSReport updatedDevFS = await _updateDevFS(); // Record time it took to synchronize to DevFS. _addBenchmarkData('hotReloadDevFSSyncMilliseconds', devFSTimer.elapsed.inMilliseconds); - if (!updatedDevFS.success) + if (!updatedDevFS.success) { return OperationResult(1, 'DevFS synchronization failed'); + } String reloadMessage; final Stopwatch vmReloadTimer = Stopwatch()..start(); + Map firstReloadDetails; try { final String entryPath = fs.path.relative( getReloadPath(fullRestart: false), @@ -635,27 +713,23 @@ class HotRunner extends ResidentRunner { final Map reloadReport = report.reports[0]; if (!validateReloadReport(reloadReport)) { // 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'); - } 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 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 catch (error, stackTrace) { printTrace('Hot reload failed: $error\n$stackTrace'); @@ -666,7 +740,13 @@ class HotRunner extends ResidentRunner { 'the source code. Please address the error and then use "R" to ' 'restart the app.\n' '$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 (error code: $errorCode)'); @@ -783,8 +863,26 @@ class HotRunner extends ResidentRunner { final Duration reloadDuration = reloadTimer.elapsed; final int reloadInMs = reloadDuration.inMilliseconds; - analyticsParameters[kEventReloadOverallTimeInMs] = '$reloadInMs'; - flutterUsage.sendEvent('hot', 'reload', parameters: analyticsParameters); + // 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. + 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) { printTrace('Hot reload performed in ${getElapsedAsMilliseconds(reloadDuration)}.'); @@ -952,32 +1050,3 @@ class ProjectFileInvalidator { return invalidatedFiles; } } - -// This is an error case we would like to know more about. -Future _measureJsonRpcException(List 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: { - reloadExceptionTargetPlatform: targetPlatform, - reloadExceptionSdkName: deviceSdk, - reloadExceptionEmulator: emulator.toString(), - reloadExceptionFullRestart: fullRestart.toString(), - }, - ); -} diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart index 837e787dea..dd232476b4 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart @@ -28,7 +28,7 @@ import '../doctor.dart'; import '../features.dart'; import '../globals.dart'; import '../project.dart'; -import '../reporting/usage.dart'; +import '../reporting/reporting.dart'; import 'flutter_command_runner.dart'; export '../cache.dart' show DevelopmentArtifact; @@ -63,6 +63,21 @@ class FlutterCommandResult { /// [FlutterCommand] will automatically measure and report the command's /// complete time if not overridden. 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. @@ -366,7 +381,8 @@ abstract class FlutterCommand extends Command { } /// Additional usage values to be sent with the usage ping. - Future> get usageValues async => const {}; + Future> get usageValues async => + const {}; /// Runs this command. /// @@ -382,8 +398,9 @@ abstract class FlutterCommand extends Command { name: 'command', overrides: {FlutterCommand: () => this}, body: () async { - if (flutterUsage.isFirstRun) + if (flutterUsage.isFirstRun) { flutterUsage.printWelcome(); + } final String commandPath = await usagePath; FlutterCommandResult commandResult; try { @@ -411,21 +428,7 @@ abstract class FlutterCommand extends Command { } // Send command result. - String result = 'unspecified'; - 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); + CommandResultEvent(commandPath, commandResult).send(); // Send timing. final List labels = [ @@ -476,12 +479,12 @@ abstract class FlutterCommand extends Command { setupApplicationPackages(); if (commandPath != null) { - final Map additionalUsageValues = { - ...?await usageValues, - }; - additionalUsageValues[kCommandHasTerminal] = - io.stdout.hasTerminal ? 'true' : 'false'; - flutterUsage.sendCommand(commandPath, parameters: additionalUsageValues); + final Map additionalUsageValues = + { + ...?await usageValues, + CustomDimensions.commandHasTerminal: io.stdout.hasTerminal ? 'true' : 'false', + }; + Usage.command(commandPath, parameters: additionalUsageValues); } return await runCommand(); diff --git a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart index babe49cc2e..0e991b3a5a 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart @@ -30,7 +30,7 @@ import '../convert.dart'; import '../dart/package_map.dart'; import '../device.dart'; import '../globals.dart'; -import '../reporting/usage.dart'; +import '../reporting/reporting.dart'; import '../tester/flutter_tester.dart'; import '../version.dart'; import '../vmservice.dart'; diff --git a/packages/flutter_tools/lib/src/web/compile.dart b/packages/flutter_tools/lib/src/web/compile.dart index 752bae9175..d241998928 100644 --- a/packages/flutter_tools/lib/src/web/compile.dart +++ b/packages/flutter_tools/lib/src/web/compile.dart @@ -13,7 +13,7 @@ import '../build_info.dart'; import '../bundle.dart'; import '../globals.dart'; import '../project.dart'; -import '../reporting/usage.dart'; +import '../reporting/reporting.dart'; /// The [WebCompilationProxy] instance. WebCompilationProxy get webCompilationProxy => context.get(); diff --git a/packages/flutter_tools/lib/src/windows/build_windows.dart b/packages/flutter_tools/lib/src/windows/build_windows.dart index 1d9728840e..c61cef58b8 100644 --- a/packages/flutter_tools/lib/src/windows/build_windows.dart +++ b/packages/flutter_tools/lib/src/windows/build_windows.dart @@ -13,7 +13,7 @@ import '../cache.dart'; import '../convert.dart'; import '../globals.dart'; import '../project.dart'; -import '../reporting/usage.dart'; +import '../reporting/reporting.dart'; import 'msbuild_utils.dart'; import 'visual_studio.dart'; diff --git a/packages/flutter_tools/test/general.shard/analytics_test.dart b/packages/flutter_tools/test/general.shard/analytics_test.dart index d738ab224d..0cbda9de14 100644 --- a/packages/flutter_tools/test/general.shard/analytics_test.dart +++ b/packages/flutter_tools/test/general.shard/analytics_test.dart @@ -4,22 +4,21 @@ import 'package:args/command_runner.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/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/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/commands/build.dart'; import 'package:flutter_tools/src/commands/config.dart'; import 'package:flutter_tools/src/commands/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/version.dart'; +import 'package:mockito/mockito.dart'; import 'package:platform/platform.dart'; import '../src/common.dart'; @@ -94,7 +93,8 @@ void main() { final Usage usage = Usage(); 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: { FlutterVersion: () => FlutterVersion(const SystemClock()), Config: () => mockFlutterConfig, @@ -114,7 +114,8 @@ void main() { final Usage usage = Usage(); 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: { FlutterVersion: () => FlutterVersion(const SystemClock()), Config: () => mockFlutterConfig, diff --git a/packages/flutter_tools/test/general.shard/artifacts_test.dart b/packages/flutter_tools/test/general.shard/artifacts_test.dart index ee78164149..eaa7bf7eb6 100644 --- a/packages/flutter_tools/test/general.shard/artifacts_test.dart +++ b/packages/flutter_tools/test/general.shard/artifacts_test.dart @@ -3,11 +3,11 @@ // found in the LICENSE file. 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/platform.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/cache.dart'; -import 'package:flutter_tools/src/artifacts.dart'; import '../src/common.dart'; import '../src/context.dart'; diff --git a/packages/flutter_tools/test/general.shard/commands/build_aar_test.dart b/packages/flutter_tools/test/general.shard/commands/build_aar_test.dart index d02d764050..67cfb5be0d 100644 --- a/packages/flutter_tools/test/general.shard/commands/build_aar_test.dart +++ b/packages/flutter_tools/test/general.shard/commands/build_aar_test.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/cache.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 '../../src/common.dart'; @@ -51,7 +51,7 @@ void main() { final BuildAarCommand command = await runCommandIn(projectPath); expect(await command.usageValues, - containsPair(kCommandBuildAarProjectType, 'module')); + containsPair(CustomDimensions.commandBuildAarProjectType, 'module')); }, overrides: { AarBuilder: () => mockAarBuilder, @@ -63,7 +63,7 @@ void main() { final BuildAarCommand command = await runCommandIn(projectPath); expect(await command.usageValues, - containsPair(kCommandBuildAarProjectType, 'plugin')); + containsPair(CustomDimensions.commandBuildAarProjectType, 'plugin')); }, overrides: { AarBuilder: () => mockAarBuilder, @@ -76,7 +76,7 @@ void main() { final BuildAarCommand command = await runCommandIn(projectPath, arguments: ['--target-platform=android-arm']); expect(await command.usageValues, - containsPair(kCommandBuildAarTargetPlatform, 'android-arm')); + containsPair(CustomDimensions.commandBuildAarTargetPlatform, 'android-arm')); }, overrides: { AarBuilder: () => mockAarBuilder, diff --git a/packages/flutter_tools/test/general.shard/commands/build_bundle_test.dart b/packages/flutter_tools/test/general.shard/commands/build_bundle_test.dart index 782ac266ad..f37dba7aab 100644 --- a/packages/flutter_tools/test/general.shard/commands/build_bundle_test.dart +++ b/packages/flutter_tools/test/general.shard/commands/build_bundle_test.dart @@ -6,11 +6,11 @@ import 'package:args/command_runner.dart'; import 'package:file/memory.dart'; import 'package:flutter_tools/src/base/common.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/commands/build_bundle.dart'; -import 'package:flutter_tools/src/bundle.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 '../../src/common.dart'; @@ -70,7 +70,7 @@ void main() { final BuildBundleCommand command = await runCommandIn(projectPath); expect(await command.usageValues, - containsPair(kCommandBuildBundleIsModule, 'true')); + containsPair(CustomDimensions.commandBuildBundleIsModule, 'true')); }, timeout: allowForCreateFlutterProject); testUsingContext('bundle getUsage indicate that project is not a module', () async { @@ -80,7 +80,7 @@ void main() { final BuildBundleCommand command = await runCommandIn(projectPath); expect(await command.usageValues, - containsPair(kCommandBuildBundleIsModule, 'false')); + containsPair(CustomDimensions.commandBuildBundleIsModule, 'false')); }, timeout: allowForCreateFlutterProject); testUsingContext('bundle getUsage indicate the target platform', () async { @@ -90,7 +90,7 @@ void main() { final BuildBundleCommand command = await runCommandIn(projectPath); expect(await command.usageValues, - containsPair(kCommandBuildBundleTargetPlatform, 'android-arm')); + containsPair(CustomDimensions.commandBuildBundleTargetPlatform, 'android-arm')); }, timeout: allowForCreateFlutterProject); testUsingContext('bundle fails to build for Windows if feature is disabled', () async { diff --git a/packages/flutter_tools/test/general.shard/commands/create_usage_test.dart b/packages/flutter_tools/test/general.shard/commands/create_usage_test.dart index 6b805578f0..a8cc1a7351 100644 --- a/packages/flutter_tools/test/general.shard/commands/create_usage_test.dart +++ b/packages/flutter_tools/test/general.shard/commands/create_usage_test.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/commands/create.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/context.dart'; @@ -43,25 +43,35 @@ void main() { final CreateCommand command = CreateCommand(); final CommandRunner runner = createTestCommandRunner(command); - await runner.run(['create', '--flutter-root=flutter', '--no-pub', '--template=module', 'testy']); - expect(await command.usageValues, containsPair(kCommandCreateProjectType, 'module')); + await runner.run([ + 'create', '--flutter-root=flutter', '--no-pub', '--template=module', 'testy']); + expect(await command.usageValues, + containsPair(CustomDimensions.commandCreateProjectType, 'module')); - await runner.run(['create', '--flutter-root=flutter', '--no-pub', '--template=app', 'testy']); - expect(await command.usageValues, containsPair(kCommandCreateProjectType, 'app')); + await runner.run([ + 'create', '--flutter-root=flutter', '--no-pub', '--template=app', 'testy']); + expect(await command.usageValues, + containsPair(CustomDimensions.commandCreateProjectType, 'app')); - await runner.run(['create', '--flutter-root=flutter', '--no-pub', '--template=package', 'testy']); - expect(await command.usageValues, containsPair(kCommandCreateProjectType, 'package')); + await runner.run([ + 'create', '--flutter-root=flutter', '--no-pub', '--template=package', 'testy']); + expect(await command.usageValues, + containsPair(CustomDimensions.commandCreateProjectType, 'package')); - await runner.run(['create', '--flutter-root=flutter', '--no-pub', '--template=plugin', 'testy']); - expect(await command.usageValues, containsPair(kCommandCreateProjectType, 'plugin')); + await runner.run([ + '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 { final CreateCommand command = CreateCommand(); final CommandRunner runner = createTestCommandRunner(command); - await runner.run(['create', '--flutter-root=flutter', '--no-pub', '--template=app', 'testy']); - expect(await command.usageValues, containsPair(kCommandCreateIosLanguage, 'objc')); + await runner.run([ + 'create', '--flutter-root=flutter', '--no-pub', '--template=app', 'testy']); + expect(await command.usageValues, + containsPair(CustomDimensions.commandCreateIosLanguage, 'objc')); await runner.run([ 'create', @@ -71,7 +81,8 @@ void main() { '--ios-language=swift', 'testy', ]); - expect(await command.usageValues, containsPair(kCommandCreateIosLanguage, 'swift')); + expect(await command.usageValues, + containsPair(CustomDimensions.commandCreateIosLanguage, 'swift')); })); @@ -80,7 +91,8 @@ void main() { final CommandRunner runner = createTestCommandRunner(command); await runner.run(['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([ 'create', @@ -90,7 +102,8 @@ void main() { '--android-language=kotlin', 'testy', ]); - expect(await command.usageValues, containsPair(kCommandCreateAndroidLanguage, 'kotlin')); + expect(await command.usageValues, + containsPair(CustomDimensions.commandCreateAndroidLanguage, 'kotlin')); })); }); } diff --git a/packages/flutter_tools/test/general.shard/commands/doctor_test.dart b/packages/flutter_tools/test/general.shard/commands/doctor_test.dart index 26dd7c629b..555af06d24 100644 --- a/packages/flutter_tools/test/general.shard/commands/doctor_test.dart +++ b/packages/flutter_tools/test/general.shard/commands/doctor_test.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/globals.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_validator.dart'; -import 'package:flutter_tools/src/reporting/usage.dart'; import '../../src/common.dart'; import '../../src/context.dart'; diff --git a/packages/flutter_tools/test/general.shard/commands/packages_test.dart b/packages/flutter_tools/test/general.shard/commands/packages_test.dart index 5129568d43..02a20d7bbc 100644 --- a/packages/flutter_tools/test/general.shard/commands/packages_test.dart +++ b/packages/flutter_tools/test/general.shard/commands/packages_test.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/cache.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 '../../src/common.dart'; @@ -226,7 +226,8 @@ void main() { final PackagesCommand command = await runCommandIn(projectPath, 'get'); 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); testUsingContext('indicate that the project is not a module in usage value', () async { @@ -237,7 +238,8 @@ void main() { final PackagesCommand command = await runCommandIn(projectPath, 'get'); 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); testUsingContext('indicate that the project is a module in usage value', () async { @@ -248,7 +250,8 @@ void main() { final PackagesCommand command = await runCommandIn(projectPath, 'get'); 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); testUsingContext('upgrade fetches packages', () async { diff --git a/packages/flutter_tools/test/general.shard/compile_test.dart b/packages/flutter_tools/test/general.shard/compile_test.dart index debfc49b6c..16743a23ef 100644 --- a/packages/flutter_tools/test/general.shard/compile_test.dart +++ b/packages/flutter_tools/test/general.shard/compile_test.dart @@ -6,9 +6,9 @@ import 'dart:async'; import 'dart:convert'; 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/io.dart'; -import 'package:flutter_tools/src/base/context.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/terminal.dart'; diff --git a/packages/flutter_tools/test/general.shard/crash_reporting_test.dart b/packages/flutter_tools/test/general.shard/crash_reporting_test.dart index 1624f8a8df..fec3f0e918 100644 --- a/packages/flutter_tools/test/general.shard/crash_reporting_test.dart +++ b/packages/flutter_tools/test/general.shard/crash_reporting_test.dart @@ -8,17 +8,16 @@ import 'dart:convert'; import 'package:file/file.dart'; import 'package:file/local.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/src/base/context.dart'; import 'package:flutter_tools/src/base/io.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/reporting/crash_reporting.dart'; +import 'package:flutter_tools/src/reporting/reporting.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 '../src/common.dart'; diff --git a/packages/flutter_tools/test/general.shard/hot_test.dart b/packages/flutter_tools/test/general.shard/hot_test.dart index 2561daf132..304fcf75a6 100644 --- a/packages/flutter_tools/test/general.shard/hot_test.dart +++ b/packages/flutter_tools/test/general.shard/hot_test.dart @@ -129,6 +129,7 @@ void main() { final MockDevice mockDevice = MockDevice(); when(mockDevice.supportsHotReload).thenReturn(true); when(mockDevice.supportsHotRestart).thenReturn(false); + when(mockDevice.targetPlatform).thenAnswer((Invocation _) async => TargetPlatform.tester); // Trigger hot restart. final List devices = [ FlutterDevice(mockDevice, generator: residentCompiler, trackWidgetCreation: false, buildMode: BuildMode.debug)..devFS = mockDevFs, @@ -190,6 +191,7 @@ void main() { final MockDevice mockDevice = MockDevice(); when(mockDevice.supportsHotReload).thenReturn(true); when(mockDevice.supportsHotRestart).thenReturn(true); + when(mockDevice.targetPlatform).thenAnswer((Invocation _) async => TargetPlatform.tester); final List devices = [ FlutterDevice(mockDevice, generator: residentCompiler, trackWidgetCreation: false, buildMode: BuildMode.debug), ]; @@ -206,6 +208,7 @@ void main() { final MockDevice mockDevice = MockDevice(); when(mockDevice.supportsHotReload).thenReturn(true); when(mockDevice.supportsHotRestart).thenReturn(true); + when(mockDevice.targetPlatform).thenAnswer((Invocation _) async => TargetPlatform.tester); // Trigger hot restart. final List devices = [ FlutterDevice(mockDevice, generator: residentCompiler, trackWidgetCreation: false, buildMode: BuildMode.debug)..devFS = mockDevFs, diff --git a/packages/flutter_tools/test/general.shard/ios/mac_test.dart b/packages/flutter_tools/test/general.shard/ios/mac_test.dart index 6167b8a8bf..c6c0735951 100644 --- a/packages/flutter_tools/test/general.shard/ios/mac_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/mac_test.dart @@ -5,14 +5,14 @@ import 'dart:async'; 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/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/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/reporting/usage.dart'; +import 'package:flutter_tools/src/reporting/reporting.dart'; import 'package:mockito/mockito.dart'; import 'package:platform/platform.dart'; import 'package:process/process.dart'; @@ -183,9 +183,10 @@ void main() { ); await diagnoseXcodeBuildFailure(buildResult); - verify(mockUsage.sendEvent('Xcode', 'bitcode-failure', parameters: { - 'build-commands': buildCommands.toString(), - 'build-settings': buildSettings.toString(), + verify(mockUsage.sendEvent('build', 'xcode-bitcode-failure', + parameters: { + cdKey(CustomDimensions.buildEventCommand): buildCommands.toString(), + cdKey(CustomDimensions.buildEventSettings): buildSettings.toString(), })).called(1); }, overrides: { Usage: () => mockUsage, diff --git a/packages/flutter_tools/test/general.shard/project_test.dart b/packages/flutter_tools/test/general.shard/project_test.dart index fc6697c7f3..dd800b4efe 100644 --- a/packages/flutter_tools/test/general.shard/project_test.dart +++ b/packages/flutter_tools/test/general.shard/project_test.dart @@ -4,17 +4,17 @@ 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/context.dart'; +import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/flutter_manifest.dart'; import 'package:flutter_tools/src/ios/ios_workflow.dart'; import 'package:flutter_tools/src/ios/xcodeproj.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:mockito/mockito.dart'; diff --git a/packages/flutter_tools/test/general.shard/resident_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_runner_test.dart index d1d626c8f2..2b17c50090 100644 --- a/packages/flutter_tools/test/general.shard/resident_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_runner_test.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/devfs.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/run_hot.dart'; import 'package:flutter_tools/src/vmservice.dart'; @@ -53,6 +53,7 @@ void main() { when(mockDevFS.lastCompiled).thenReturn(DateTime(2000)); when(mockDevFS.sources).thenReturn([]); when(mockDevFS.destroy()).thenAnswer((Invocation invocation) async { }); + when(mockDevFS.assetPathsToEvict).thenReturn({}); // FlutterDevice Mocks. when(mockFlutterDevice.updateDevFS( // Intentionally provide empty list to match above mock. @@ -96,6 +97,19 @@ void main() { mockVMService, ]); when(mockFlutterDevice.refreshViews()).thenAnswer((Invocation invocation) async { }); + when(mockFlutterDevice.reloadSources(any, pause: anyNamed('pause'))).thenReturn(>>[ + Future>.value({ + 'type': 'ReloadReport', + 'success': true, + 'details': { + 'loadedLibraryCount': 1, + 'finalLibraryCount': 1, + 'receivedLibraryCount': 1, + 'receivedClassesCount': 1, + 'receivedProceduresCount': 1, + }, + }), + ]); // VMService mocks. when(mockVMService.wsAddress).thenReturn(testUri); when(mockVMService.done).thenAnswer((Invocation invocation) { @@ -108,6 +122,9 @@ void main() { when(mockIsolate.flutterExit()).thenAnswer((Invocation invocation) { return Future>.value(null); }); + when(mockIsolate.reload()).thenAnswer((Invocation invocation) { + return Future.value(null); + }); }); test('ResidentRunner can attach to device successfully', () => testbed.run(() async { @@ -162,15 +179,48 @@ void main() { expect(result.fatal, true); expect(result.code, 1); verify(flutterUsage.sendEvent('hot', 'exception', parameters: { - reloadExceptionTargetPlatform: getNameForTargetPlatform(TargetPlatform.android_arm), - reloadExceptionSdkName: 'Example', - reloadExceptionEmulator: 'false', - reloadExceptionFullRestart: 'false', + cdKey(CustomDimensions.hotEventTargetPlatform): + getNameForTargetPlatform(TargetPlatform.android_arm), + cdKey(CustomDimensions.hotEventSdkName): 'Example', + cdKey(CustomDimensions.hotEventEmulator): 'false', + cdKey(CustomDimensions.hotEventFullRestart): 'false', })).called(1); }, overrides: { 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 onConnectionInfo = Completer.sync(); + final Completer onAppStart = Completer.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: { + Usage: () => MockUsage(), + })); + test('ResidentRunner Can handle an RPC exception from hot restart', () => testbed.run(() async { when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async { return 'Example'; @@ -206,10 +256,11 @@ void main() { expect(result.fatal, true); expect(result.code, 1); verify(flutterUsage.sendEvent('hot', 'exception', parameters: { - reloadExceptionTargetPlatform: getNameForTargetPlatform(TargetPlatform.android_arm), - reloadExceptionSdkName: 'Example', - reloadExceptionEmulator: 'false', - reloadExceptionFullRestart: 'true', + cdKey(CustomDimensions.hotEventTargetPlatform): + getNameForTargetPlatform(TargetPlatform.android_arm), + cdKey(CustomDimensions.hotEventSdkName): 'Example', + cdKey(CustomDimensions.hotEventEmulator): 'false', + cdKey(CustomDimensions.hotEventFullRestart): 'true', })).called(1); }, overrides: { Usage: () => MockUsage(), diff --git a/packages/flutter_tools/test/general.shard/runner/flutter_command_test.dart b/packages/flutter_tools/test/general.shard/runner/flutter_command_test.dart index eb06c9d192..413e4c685e 100644 --- a/packages/flutter_tools/test/general.shard/runner/flutter_command_test.dart +++ b/packages/flutter_tools/test/general.shard/runner/flutter_command_test.dart @@ -2,10 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // 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/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/version.dart'; import 'package:mockito/mockito.dart'; diff --git a/packages/flutter_tools/test/general.shard/runner/utils.dart b/packages/flutter_tools/test/general.shard/runner/utils.dart index 57e8af6040..f393441afa 100644 --- a/packages/flutter_tools/test/general.shard/runner/utils.dart +++ b/packages/flutter_tools/test/general.shard/runner/utils.dart @@ -5,7 +5,7 @@ import 'dart:async'; 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:mockito/mockito.dart'; diff --git a/packages/flutter_tools/test/src/common.dart b/packages/flutter_tools/test/src/common.dart index 3a3a4ea3ef..ff4e549cd4 100644 --- a/packages/flutter_tools/test/src/common.dart +++ b/packages/flutter_tools/test/src/common.dart @@ -5,9 +5,6 @@ import 'dart:async'; 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/file_system.dart'; import 'package:flutter_tools/src/base/platform.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/runner/flutter_command.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. diff --git a/packages/flutter_tools/test/src/context.dart b/packages/flutter_tools/test/src/context.dart index fe82865081..2cafbcdad1 100644 --- a/packages/flutter_tools/test/src/context.dart +++ b/packages/flutter_tools/test/src/context.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/os.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/context_runner.dart'; import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/doctor.dart'; import 'package:flutter_tools/src/ios/simulators.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/reporting/usage.dart'; +import 'package:flutter_tools/src/reporting/reporting.dart'; import 'package:flutter_tools/src/version.dart'; import 'package:meta/meta.dart'; import 'package:mockito/mockito.dart'; diff --git a/packages/flutter_tools/test/src/testbed.dart b/packages/flutter_tools/test/src/testbed.dart index a0394e4d05..446d111719 100644 --- a/packages/flutter_tools/test/src/testbed.dart +++ b/packages/flutter_tools/test/src/testbed.dart @@ -8,17 +8,17 @@ import 'dart:io'; import 'dart:typed_data'; 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/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/os.dart'; +import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/terminal.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/context_runner.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 'context.dart';