[flutter_tool] Send build timing to analytics (#34049)

diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart
index 9216797..62f4475 100644
--- a/packages/flutter_tools/lib/src/android/gradle.dart
+++ b/packages/flutter_tools/lib/src/android/gradle.dart
@@ -4,6 +4,7 @@
 
 import 'dart:async';
 
+import 'package:crypto/crypto.dart';
 import 'package:meta/meta.dart';
 
 import '../android/android_sdk.dart';
@@ -352,6 +353,7 @@
     timeout: timeoutConfiguration.slowOperation,
     multilineOutput: true,
   );
+  final Stopwatch sw = Stopwatch()..start();
   final int exitCode = await runCommandAndStreamOutput(
     <String>[fs.file(gradle).absolute.path, 'build'],
     workingDirectory: project.android.hostAppGradleRoot.path,
@@ -359,6 +361,7 @@
     environment: _gradleEnv,
   );
   status.stop();
+  flutterUsage.sendTiming('build', 'gradle-v1', Duration(milliseconds: sw.elapsedMilliseconds));
 
   if (exitCode != 0)
     throwToolExit('Gradle build failed: $exitCode', exitCode: exitCode);
@@ -366,6 +369,25 @@
   printStatus('Built ${fs.path.relative(project.android.gradleAppOutV1File.path)}.');
 }
 
+String _hex(List<int> bytes) {
+  final StringBuffer result = StringBuffer();
+  for (int part in bytes)
+    result.write('${part < 16 ? '0' : ''}${part.toRadixString(16)}');
+  return result.toString();
+}
+
+String _calculateSha(File file) {
+  final Stopwatch sw = Stopwatch()..start();
+  final List<int> bytes = file.readAsBytesSync();
+  printTrace('calculateSha: reading file took ${sw.elapsedMilliseconds}us');
+  flutterUsage.sendTiming('build', 'apk-sha-read', Duration(milliseconds: sw.elapsedMilliseconds));
+  sw.reset();
+  final String sha = _hex(sha1.convert(bytes).bytes);
+  printTrace('calculateSha: computing sha took ${sw.elapsedMilliseconds}us');
+  flutterUsage.sendTiming('build', 'apk-sha-calc', Duration(milliseconds: sw.elapsedMilliseconds));
+  return sha;
+}
+
 Future<void> _buildGradleProjectV2(
   FlutterProject flutterProject,
   String gradle,
@@ -439,30 +461,35 @@
 
   command.add(assembleTask);
   bool potentialAndroidXFailure = false;
-  final int exitCode = await runCommandAndStreamOutput(
-    command,
-    workingDirectory: flutterProject.android.hostAppGradleRoot.path,
-    allowReentrantFlutter: true,
-    environment: _gradleEnv,
-    // TODO(mklim): if AndroidX warnings are no longer required, this
-    // mapFunction and all its associated variabled can be replaced with just
-    // `filter: ndkMessagefilter`.
-    mapFunction: (String line) {
-      final bool isAndroidXPluginWarning = androidXPluginWarningRegex.hasMatch(line);
-      if (!isAndroidXPluginWarning && androidXFailureRegex.hasMatch(line)) {
-        potentialAndroidXFailure = true;
-      }
-      // Always print the full line in verbose mode.
-      if (logger.isVerbose) {
-        return line;
-      } else if (isAndroidXPluginWarning || !ndkMessageFilter.hasMatch(line)) {
-        return null;
-      }
+  final Stopwatch sw = Stopwatch()..start();
+  int exitCode = 1;
+  try {
+    exitCode = await runCommandAndStreamOutput(
+      command,
+      workingDirectory: flutterProject.android.hostAppGradleRoot.path,
+      allowReentrantFlutter: true,
+      environment: _gradleEnv,
+      // TODO(mklim): if AndroidX warnings are no longer required, this
+      // mapFunction and all its associated variabled can be replaced with just
+      // `filter: ndkMessagefilter`.
+      mapFunction: (String line) {
+        final bool isAndroidXPluginWarning = androidXPluginWarningRegex.hasMatch(line);
+        if (!isAndroidXPluginWarning && androidXFailureRegex.hasMatch(line)) {
+          potentialAndroidXFailure = true;
+        }
+        // Always print the full line in verbose mode.
+        if (logger.isVerbose) {
+          return line;
+        } else if (isAndroidXPluginWarning || !ndkMessageFilter.hasMatch(line)) {
+          return null;
+        }
 
-      return line;
-    },
-  );
-  status.stop();
+        return line;
+      },
+    );
+  } finally {
+    status.stop();
+  }
 
   if (exitCode != 0) {
     if (potentialAndroidXFailure) {
@@ -478,6 +505,7 @@
     }
     throwToolExit('Gradle task $assembleTask failed with exit code $exitCode', exitCode: exitCode);
   }
+  flutterUsage.sendTiming('build', 'gradle-v2', Duration(milliseconds: sw.elapsedMilliseconds));
 
   if (!isBuildingBundle) {
     final File apkFile = _findApkFile(project, buildInfo);
@@ -488,7 +516,7 @@
 
     printTrace('calculateSha: ${project.apkDirectory}/app.apk');
     final File apkShaFile = project.apkDirectory.childFile('app.apk.sha1');
-    apkShaFile.writeAsStringSync(calculateSha(apkFile));
+    apkShaFile.writeAsStringSync(_calculateSha(apkFile));
 
     String appSize;
     if (buildInfo.mode == BuildMode.debug) {
diff --git a/packages/flutter_tools/lib/src/base/build.dart b/packages/flutter_tools/lib/src/base/build.dart
index 418f685..4b46edb 100644
--- a/packages/flutter_tools/lib/src/base/build.dart
+++ b/packages/flutter_tools/lib/src/base/build.dart
@@ -15,6 +15,7 @@
 import '../globals.dart';
 import '../macos/xcode.dart';
 import '../project.dart';
+import '../usage.dart';
 import 'context.dart';
 import 'file_system.dart';
 import 'fingerprint.dart';
@@ -194,7 +195,9 @@
     // }
 
     final SnapshotType snapshotType = SnapshotType(platform, buildMode);
-    final int genSnapshotExitCode = await _timedStep('snapshot(CompileTime)', () => genSnapshot.run(
+    final int genSnapshotExitCode =
+      await _timedStep('snapshot(CompileTime)', 'aot-snapshot',
+        () => genSnapshot.run(
       snapshotType: snapshotType,
       additionalArgs: genSnapshotArgs,
       iosArch: iosArch,
@@ -281,7 +284,9 @@
 
     final String depfilePath = fs.path.join(outputPath, 'kernel_compile.d');
     final KernelCompiler kernelCompiler = await kernelCompilerFactory.create(flutterProject);
-    final CompilerOutput compilerOutput = await _timedStep('frontend(CompileTime)', () => kernelCompiler.compile(
+    final CompilerOutput compilerOutput =
+      await _timedStep('frontend(CompileTime)', 'aot-kernel',
+        () => kernelCompiler.compile(
       sdkRoot: artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath, mode: buildMode),
       mainPath: mainPath,
       packagesPath: packagesPath,
@@ -323,12 +328,13 @@
   /// to find.
   /// Important: external performance tracking tools expect format of this
   /// output to be stable.
-  Future<T> _timedStep<T>(String marker, FutureOr<T> Function() action) async {
+  Future<T> _timedStep<T>(String marker, String analyticsVar, FutureOr<T> Function() action) async {
     final Stopwatch sw = Stopwatch()..start();
     final T value = await action();
     if (reportTimings) {
       printStatus('$marker: ${sw.elapsedMilliseconds} ms.');
     }
+    flutterUsage.sendTiming('build', analyticsVar, Duration(milliseconds: sw.elapsedMilliseconds));
     return value;
   }
 }
diff --git a/packages/flutter_tools/lib/src/base/utils.dart b/packages/flutter_tools/lib/src/base/utils.dart
index d0eea9c..1b6d2c3 100644
--- a/packages/flutter_tools/lib/src/base/utils.dart
+++ b/packages/flutter_tools/lib/src/base/utils.dart
@@ -5,7 +5,6 @@
 import 'dart:async';
 import 'dart:math' show Random, max;
 
-import 'package:crypto/crypto.dart';
 import 'package:intl/intl.dart';
 
 import '../convert.dart';
@@ -53,24 +52,6 @@
   return botDetector.isRunningOnBot;
 }
 
-String hex(List<int> bytes) {
-  final StringBuffer result = StringBuffer();
-  for (int part in bytes)
-    result.write('${part < 16 ? '0' : ''}${part.toRadixString(16)}');
-  return result.toString();
-}
-
-String calculateSha(File file) {
-  final Stopwatch stopwatch = Stopwatch()..start();
-  final List<int> bytes = file.readAsBytesSync();
-  printTrace('calculateSha: reading file took ${stopwatch.elapsedMicroseconds}us');
-  stopwatch.reset();
-  final String sha = hex(sha1.convert(bytes).bytes);
-  stopwatch.stop();
-  printTrace('calculateSha: computing sha took ${stopwatch.elapsedMicroseconds}us');
-  return sha;
-}
-
 /// Convert `foo_bar` to `fooBar`.
 String camelCase(String str) {
   int index = str.indexOf('_');
diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_build.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_build.dart
index 00d95fa..376920e 100644
--- a/packages/flutter_tools/lib/src/fuchsia/fuchsia_build.dart
+++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_build.dart
@@ -2,6 +2,8 @@
 // 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 '../asset.dart';
@@ -12,11 +14,20 @@
 import '../bundle.dart';
 import '../convert.dart';
 import '../devfs.dart';
+import '../globals.dart';
 import '../project.dart';
+import '../usage.dart';
 
 import 'fuchsia_pm.dart';
 import 'fuchsia_sdk.dart';
 
+Future<void> _timedBuildStep(String name, Future<void> Function() action) async {
+  final Stopwatch sw = Stopwatch()..start();
+  await action();
+  printTrace('$name: ${sw.elapsedMilliseconds} ms.');
+  flutterUsage.sendTiming('build', name, Duration(milliseconds: sw.elapsedMilliseconds));
+}
+
 // Building a Fuchsia package has a few steps:
 // 1. Do the custom kernel compile using the kernel compiler from the Fuchsia
 //    SDK. This produces .dilp files (among others) and a manifest file.
@@ -32,10 +43,13 @@
     outDir.createSync(recursive: true);
   }
 
-  await fuchsiaSdk.fuchsiaKernelCompiler.build(
-      fuchsiaProject: fuchsiaProject, target: target, buildInfo: buildInfo);
-  await _buildAssets(fuchsiaProject, target, buildInfo);
-  await _buildPackage(fuchsiaProject, target, buildInfo);
+  await _timedBuildStep('fuchsia-kernel-compile',
+    () => fuchsiaSdk.fuchsiaKernelCompiler.build(
+      fuchsiaProject: fuchsiaProject, target: target, buildInfo: buildInfo));
+  await _timedBuildStep('fuchsia-build-assets',
+    () => _buildAssets(fuchsiaProject, target, buildInfo));
+  await _timedBuildStep('fuchsia-build-package',
+    () => _buildPackage(fuchsiaProject, target, buildInfo));
 }
 
 Future<void> _buildAssets(
diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart
index 59a0153..a5cf7c0 100644
--- a/packages/flutter_tools/lib/src/ios/mac.dart
+++ b/packages/flutter_tools/lib/src/ios/mac.dart
@@ -24,6 +24,7 @@
 import '../macos/xcode.dart';
 import '../project.dart';
 import '../services.dart';
+import '../usage.dart';
 import 'code_signing.dart';
 import 'xcodeproj.dart';
 
@@ -374,7 +375,7 @@
     buildCommands.add('SCRIPT_OUTPUT_STREAM_FILE=${scriptOutputPipeFile.absolute.path}');
   }
 
-  final Stopwatch buildStopwatch = Stopwatch()..start();
+  final Stopwatch sw = Stopwatch()..start();
   initialBuildStatus = logger.startProgress('Running Xcode build...', timeout: timeoutConfiguration.fastOperation);
   final RunResult buildResult = await runAsync(
     buildCommands,
@@ -387,11 +388,11 @@
   buildSubStatus = null;
   initialBuildStatus?.cancel();
   initialBuildStatus = null;
-  buildStopwatch.stop();
   printStatus(
     'Xcode build done.'.padRight(kDefaultStatusPadding + 1)
-        + '${getElapsedAsSeconds(buildStopwatch.elapsed).padLeft(5)}',
+        + '${getElapsedAsSeconds(sw.elapsed).padLeft(5)}',
   );
+  flutterUsage.sendTiming('build', 'xcode-ios', Duration(milliseconds: sw.elapsedMilliseconds));
 
   // Run -showBuildSettings again but with the exact same parameters as the build.
   final Map<String, String> buildSettings = parseXcodeBuildSettings(runCheckedSync(
diff --git a/packages/flutter_tools/lib/src/linux/build_linux.dart b/packages/flutter_tools/lib/src/linux/build_linux.dart
index 9010095..2043731 100644
--- a/packages/flutter_tools/lib/src/linux/build_linux.dart
+++ b/packages/flutter_tools/lib/src/linux/build_linux.dart
@@ -13,6 +13,7 @@
 import '../convert.dart';
 import '../globals.dart';
 import '../project.dart';
+import '../usage.dart';
 
 /// Builds the Linux project through the Makefile.
 Future<void> buildLinux(LinuxProject linuxProject, BuildInfo buildInfo, {String target = 'lib/main.dart'}) async {
@@ -37,6 +38,8 @@
     ..createSync(recursive: true)
     ..writeAsStringSync(buffer.toString());
 
+  // Invoke make.
+  final Stopwatch sw = Stopwatch()..start();
   final Process process = await processManager.start(<String>[
     'make',
     '-C',
@@ -63,4 +66,5 @@
   if (result != 0) {
     throwToolExit('Build process failed');
   }
+  flutterUsage.sendTiming('build', 'make-linux', Duration(milliseconds: sw.elapsedMilliseconds));
 }
diff --git a/packages/flutter_tools/lib/src/macos/build_macos.dart b/packages/flutter_tools/lib/src/macos/build_macos.dart
index 12d2787..50159b2 100644
--- a/packages/flutter_tools/lib/src/macos/build_macos.dart
+++ b/packages/flutter_tools/lib/src/macos/build_macos.dart
@@ -12,6 +12,7 @@
 import '../globals.dart';
 import '../ios/xcodeproj.dart';
 import '../project.dart';
+import '../usage.dart';
 import 'cocoapod_utils.dart';
 
 /// Builds the macOS project through xcode build.
@@ -37,6 +38,7 @@
     config = 'Release';
   }
   // Run build script provided by application.
+  final Stopwatch sw = Stopwatch()..start();
   final Process process = await processManager.start(<String>[
     '/usr/bin/env',
     'xcrun',
@@ -69,4 +71,5 @@
   if (result != 0) {
     throwToolExit('Build process failed');
   }
+  flutterUsage.sendTiming('build', 'xcode-macos', Duration(milliseconds: sw.elapsedMilliseconds));
 }
diff --git a/packages/flutter_tools/lib/src/web/compile.dart b/packages/flutter_tools/lib/src/web/compile.dart
index 90b7ea3..3816377 100644
--- a/packages/flutter_tools/lib/src/web/compile.dart
+++ b/packages/flutter_tools/lib/src/web/compile.dart
@@ -13,12 +13,14 @@
 import '../bundle.dart';
 import '../globals.dart';
 import '../project.dart';
+import '../usage.dart';
 
 /// The [WebCompilationProxy] instance.
 WebCompilationProxy get webCompilationProxy => context.get<WebCompilationProxy>();
 
 Future<void> buildWeb(FlutterProject flutterProject, String target, BuildInfo buildInfo) async {
   final Status status = logger.startProgress('Compiling $target for the Web...', timeout: null);
+  final Stopwatch sw = Stopwatch()..start();
   final Directory outputDir = fs.directory(getWebBuildDirectory())
     ..createSync(recursive: true);
   bool result;
@@ -55,6 +57,11 @@
   if (result == false) {
     throwToolExit('Failed to compile $target for the Web.');
   }
+  String buildName = 'ddc';
+  if (buildInfo.isRelease) {
+    buildName = 'dart2js';
+  }
+  flutterUsage.sendTiming('build', buildName, Duration(milliseconds: sw.elapsedMilliseconds));
 }
 
 /// An indirection on web compilation.
diff --git a/packages/flutter_tools/lib/src/windows/build_windows.dart b/packages/flutter_tools/lib/src/windows/build_windows.dart
index 1d08075..781b095 100644
--- a/packages/flutter_tools/lib/src/windows/build_windows.dart
+++ b/packages/flutter_tools/lib/src/windows/build_windows.dart
@@ -13,6 +13,7 @@
 import '../convert.dart';
 import '../globals.dart';
 import '../project.dart';
+import '../usage.dart';
 import 'msbuild_utils.dart';
 import 'visual_studio.dart';
 
@@ -48,6 +49,7 @@
 
   final String configuration = buildInfo.isDebug ? 'Debug' : 'Release';
   final String solutionPath = windowsProject.solutionFile.path;
+  final Stopwatch sw = Stopwatch()..start();
   // Run the script with a relative path to the project using the enclosing
   // directory as the workingDirectory, to avoid hitting the limit on command
   // lengths in batch scripts if the absolute path to the project is long.
@@ -78,4 +80,5 @@
   if (result != 0) {
     throwToolExit('Build process failed');
   }
+  flutterUsage.sendTiming('build', 'vs_build', Duration(milliseconds: sw.elapsedMilliseconds));
 }