Emit build speed and threads in metadata

Bug: b/367558466
Change-Id: Iec25f5f9305dc286b96827e0a5e33c253c7087fc
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 8fccc8d..ae6a649 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -328,7 +328,8 @@
       reportSyntheticInformation(appView);
 
       if (options.isGeneratingClassFiles()) {
-        new CfApplicationWriter(appView, marker).write(options.getClassFileConsumer(), inputApp);
+        new CfApplicationWriter(appView, marker)
+            .write(options.getClassFileConsumer(), executor, inputApp);
       } else {
         ApplicationWriter.create(appView, marker).write(executor, inputApp);
       }
diff --git a/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java b/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
index 4b2ab5a..f0445dd 100644
--- a/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
+++ b/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
@@ -135,7 +135,7 @@
               } else {
                 assert options.isGeneratingClassFiles();
                 new CfApplicationWriter(appView, options.getMarker())
-                    .write(options.getClassFileConsumer(), app);
+                    .write(options.getClassFileConsumer(), executorService, app);
               }
             } catch (ExecutionException e) {
               throw unwrapExecutionException(e);
diff --git a/src/main/java/com/android/tools/r8/L8.java b/src/main/java/com/android/tools/r8/L8.java
index 1c1e64c..f744a67 100644
--- a/src/main/java/com/android/tools/r8/L8.java
+++ b/src/main/java/com/android/tools/r8/L8.java
@@ -149,7 +149,8 @@
               appView));
       new GenericSignatureRewriter(appView).run(appView.appInfo().classes(), executor);
 
-      new CfApplicationWriter(appView, options.getMarker()).write(options.getClassFileConsumer());
+      new CfApplicationWriter(appView, options.getMarker())
+          .write(options.getClassFileConsumer(), executor);
       options.printWarnings();
     } catch (ExecutionException e) {
       throw unwrapExecutionException(e);
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 93926dd..46f11fd 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -222,7 +222,8 @@
       Marker marker = options.getMarker();
       assert marker != null;
       if (options.isGeneratingClassFiles()) {
-        new CfApplicationWriter(appView, marker).write(options.getClassFileConsumer(), inputApp);
+        new CfApplicationWriter(appView, marker)
+            .write(options.getClassFileConsumer(), executorService, inputApp);
       } else {
         ApplicationWriter.create(appView, marker).write(executorService, inputApp);
       }
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index b6c9ae4..52ae388 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -739,6 +739,7 @@
     }
 
     private R8Command makeR8Command() {
+      long created = System.nanoTime();
       Reporter reporter = getReporter();
       DexItemFactory factory = new DexItemFactory();
       List<ProguardConfigurationRule> mainDexKeepRules =
@@ -839,7 +840,8 @@
               resourceShrinkerConfiguration,
               keepSpecifications,
               buildMetadataConsumer,
-              partialCompilationConfiguration);
+              partialCompilationConfiguration,
+              created);
 
       if (inputDependencyGraphConsumer != null) {
         inputDependencyGraphConsumer.finished();
@@ -1062,6 +1064,7 @@
   private final ResourceShrinkerConfiguration resourceShrinkerConfiguration;
   private final Consumer<? super R8BuildMetadata> buildMetadataConsumer;
   private final R8PartialCompilationConfiguration partialCompilationConfiguration;
+  private final long created;
 
   /** Get a new {@link R8Command.Builder}. */
   public static Builder builder() {
@@ -1162,7 +1165,8 @@
       ResourceShrinkerConfiguration resourceShrinkerConfiguration,
       List<KeepSpecificationSource> keepSpecifications,
       Consumer<? super R8BuildMetadata> buildMetadataConsumer,
-      R8PartialCompilationConfiguration partialCompilationConfiguration) {
+      R8PartialCompilationConfiguration partialCompilationConfiguration,
+      long created) {
     super(
         inputApp,
         mode,
@@ -1213,6 +1217,7 @@
     this.resourceShrinkerConfiguration = resourceShrinkerConfiguration;
     this.buildMetadataConsumer = buildMetadataConsumer;
     this.partialCompilationConfiguration = partialCompilationConfiguration;
+    this.created = created;
   }
 
   private R8Command(boolean printHelp, boolean printVersion) {
@@ -1243,6 +1248,7 @@
     resourceShrinkerConfiguration = null;
     buildMetadataConsumer = null;
     partialCompilationConfiguration = null;
+    created = -1;
   }
 
   public DexItemFactory getDexItemFactory() {
@@ -1267,6 +1273,7 @@
   @Override
   InternalOptions getInternalOptions() {
     InternalOptions internal = new InternalOptions(getMode(), proguardConfiguration, getReporter());
+    internal.created = created;
     assert !internal.testing.allowOutlinerInterfaceArrayArguments;  // Only allow in tests.
     internal.programConsumer = getProgramConsumer();
     internal.setMinApiLevel(AndroidApiLevel.getAndroidApiLevel(getMinApiLevel()));
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 9554f4e..c1b8ee6 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -452,7 +452,7 @@
       options.reporter.failIfPendingErrors();
       // Supply info to all additional resource consumers.
       if (!(programConsumer instanceof ConvertedCfFiles)) {
-        supplyAdditionalConsumers(appView, virtualFiles);
+        supplyAdditionalConsumers(appView, executorService, virtualFiles);
       }
     } finally {
       timing.end();
@@ -655,7 +655,8 @@
   }
 
   @SuppressWarnings("DefaultCharset")
-  public static void supplyAdditionalConsumers(AppView<?> appView, List<VirtualFile> virtualFiles) {
+  public static void supplyAdditionalConsumers(
+      AppView<?> appView, ExecutorService executorService, List<VirtualFile> virtualFiles) {
     InternalOptions options = appView.options();
     Reporter reporter = options.reporter;
     appView.getArtProfileCollection().supplyConsumers(appView);
@@ -711,7 +712,7 @@
     if (options.r8BuildMetadataConsumer != null) {
       assert appView.hasClassHierarchy();
       options.r8BuildMetadataConsumer.accept(
-          BuildMetadataFactory.create(appView.withClassHierarchy(), virtualFiles));
+          BuildMetadataFactory.create(appView.withClassHierarchy(), executorService, virtualFiles));
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 20de02a..6ccbf71 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -66,6 +66,7 @@
 import java.util.Comparator;
 import java.util.List;
 import java.util.Optional;
+import java.util.concurrent.ExecutorService;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 import org.objectweb.asm.AnnotationVisitor;
@@ -113,15 +114,16 @@
     return appView.getNamingLens();
   }
 
-  public void write(ClassFileConsumer consumer) {
+  public void write(ClassFileConsumer consumer, ExecutorService executorService) {
     assert !options.hasMappingFileSupport();
-    write(consumer, null);
+    write(consumer, executorService, null);
   }
 
-  public void write(ClassFileConsumer consumer, AndroidApp inputApp) {
+  public void write(
+      ClassFileConsumer consumer, ExecutorService executorService, AndroidApp inputApp) {
     application.timing.begin("CfApplicationWriter.write");
     try {
-      writeApplication(inputApp, consumer);
+      writeApplication(inputApp, consumer, executorService);
     } finally {
       application.timing.end();
     }
@@ -138,7 +140,8 @@
     return true;
   }
 
-  private void writeApplication(AndroidApp inputApp, ClassFileConsumer consumer) {
+  private void writeApplication(
+      AndroidApp inputApp, ClassFileConsumer consumer, ExecutorService executorService) {
     ProguardMapId proguardMapId = null;
     if (options.hasMappingFileSupport()) {
       assert marker.isPresent();
@@ -187,7 +190,7 @@
       }
       globalsConsumer.finished(appView);
     }
-    ApplicationWriter.supplyAdditionalConsumers(appView, Collections.emptyList());
+    ApplicationWriter.supplyAdditionalConsumers(appView, executorService, Collections.emptyList());
   }
 
   private void writeClassCatchingErrors(
diff --git a/src/main/java/com/android/tools/r8/metadata/R8BuildMetadata.java b/src/main/java/com/android/tools/r8/metadata/R8BuildMetadata.java
index c3f13b0..096f514 100644
--- a/src/main/java/com/android/tools/r8/metadata/R8BuildMetadata.java
+++ b/src/main/java/com/android/tools/r8/metadata/R8BuildMetadata.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.metadata.impl.R8ApiModelingOptionsImpl;
 import com.android.tools.r8.metadata.impl.R8BaselineProfileRewritingOptionsImpl;
 import com.android.tools.r8.metadata.impl.R8BuildMetadataImpl;
+import com.android.tools.r8.metadata.impl.R8CompilationInfoImpl;
 import com.android.tools.r8.metadata.impl.R8KeepAttributesOptionsImpl;
 import com.android.tools.r8.metadata.impl.R8LibraryDesugaringOptionsImpl;
 import com.android.tools.r8.metadata.impl.R8OptionsImpl;
@@ -28,6 +29,7 @@
         .registerTypeAdapter(
             R8BaselineProfileRewritingOptions.class,
             deserializeTo(R8BaselineProfileRewritingOptionsImpl.class))
+        .registerTypeAdapter(R8CompilationInfo.class, deserializeTo(R8CompilationInfoImpl.class))
         .registerTypeAdapter(
             R8KeepAttributesOptions.class, deserializeTo(R8KeepAttributesOptionsImpl.class))
         .registerTypeAdapter(
@@ -53,6 +55,8 @@
    */
   R8BaselineProfileRewritingOptions getBaselineProfileRewritingOptions();
 
+  R8CompilationInfo getCompilationInfo();
+
   /**
    * @return null if not compiling to dex.
    */
diff --git a/src/main/java/com/android/tools/r8/metadata/R8CompilationInfo.java b/src/main/java/com/android/tools/r8/metadata/R8CompilationInfo.java
new file mode 100644
index 0000000..2bc4a6a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/metadata/R8CompilationInfo.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.metadata;
+
+import com.android.tools.r8.keepanno.annotations.KeepForApi;
+
+@KeepForApi
+public interface R8CompilationInfo {
+
+  long getBuildTime();
+
+  long getNumberOfThreads();
+}
diff --git a/src/main/java/com/android/tools/r8/metadata/impl/BuildMetadataFactory.java b/src/main/java/com/android/tools/r8/metadata/impl/BuildMetadataFactory.java
index 69e906c..31a76a8 100644
--- a/src/main/java/com/android/tools/r8/metadata/impl/BuildMetadataFactory.java
+++ b/src/main/java/com/android/tools/r8/metadata/impl/BuildMetadataFactory.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.metadata.R8BuildMetadata;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.List;
+import java.util.concurrent.ExecutorService;
 
 public class BuildMetadataFactory {
 
@@ -23,11 +24,14 @@
   }
 
   public static R8BuildMetadata create(
-      AppView<? extends AppInfoWithClassHierarchy> appView, List<VirtualFile> virtualFiles) {
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      ExecutorService executorService,
+      List<VirtualFile> virtualFiles) {
     InternalOptions options = appView.options();
     return R8BuildMetadataImpl.builder()
         .setOptions(new R8OptionsImpl(options))
         .setBaselineProfileRewritingOptions(R8BaselineProfileRewritingOptionsImpl.create(options))
+        .setCompilationInfo(R8CompilationInfoImpl.create(executorService, options))
         .applyIf(options.isGeneratingDex(), builder -> builder.setDexChecksums(virtualFiles))
         .setResourceOptimizationOptions(R8ResourceOptimizationOptionsImpl.create(options))
         .setStartupOptimizationOptions(
diff --git a/src/main/java/com/android/tools/r8/metadata/impl/R8BuildMetadataImpl.java b/src/main/java/com/android/tools/r8/metadata/impl/R8BuildMetadataImpl.java
index 47d97b5..daf5193 100644
--- a/src/main/java/com/android/tools/r8/metadata/impl/R8BuildMetadataImpl.java
+++ b/src/main/java/com/android/tools/r8/metadata/impl/R8BuildMetadataImpl.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.keepanno.annotations.UsedByReflection;
 import com.android.tools.r8.metadata.R8BaselineProfileRewritingOptions;
 import com.android.tools.r8.metadata.R8BuildMetadata;
+import com.android.tools.r8.metadata.R8CompilationInfo;
 import com.android.tools.r8.metadata.R8Options;
 import com.android.tools.r8.metadata.R8ResourceOptimizationOptions;
 import com.android.tools.r8.metadata.R8StartupOptimizationOptions;
@@ -41,6 +42,10 @@
   private final R8BaselineProfileRewritingOptions baselineProfileRewritingOptions;
 
   @Expose
+  @SerializedName("compilationInfo")
+  private final R8CompilationInfo compilationInfo;
+
+  @Expose
   @SerializedName("dexChecksums")
   private final List<String> dexChecksums;
 
@@ -59,12 +64,14 @@
   public R8BuildMetadataImpl(
       R8Options options,
       R8BaselineProfileRewritingOptions baselineProfileRewritingOptions,
+      R8CompilationInfo compilationInfo,
       List<String> dexChecksums,
       R8ResourceOptimizationOptions resourceOptimizationOptions,
       R8StartupOptimizationOptions startupOptimizationOptions,
       String version) {
     this.options = options;
     this.baselineProfileRewritingOptions = baselineProfileRewritingOptions;
+    this.compilationInfo = compilationInfo;
     this.dexChecksums = dexChecksums;
     this.resourceOptimizationOptions = resourceOptimizationOptions;
     this.startupOptimizationOptions = startupOptimizationOptions;
@@ -86,6 +93,11 @@
   }
 
   @Override
+  public R8CompilationInfo getCompilationInfo() {
+    return compilationInfo;
+  }
+
+  @Override
   public List<String> getDexChecksums() {
     return dexChecksums;
   }
@@ -114,6 +126,7 @@
 
     private R8Options options;
     private R8BaselineProfileRewritingOptions baselineProfileRewritingOptions;
+    private R8CompilationInfo compilationInfo;
     private List<String> dexChecksums;
     private R8ResourceOptimizationOptions resourceOptimizationOptions;
     private R8StartupOptimizationOptions startupOptimizationOptions;
@@ -137,6 +150,11 @@
       return this;
     }
 
+    public Builder setCompilationInfo(R8CompilationInfo compilationInfo) {
+      this.compilationInfo = compilationInfo;
+      return this;
+    }
+
     public Builder setDexChecksums(List<VirtualFile> virtualFiles) {
       this.dexChecksums =
           virtualFiles.stream()
@@ -167,6 +185,7 @@
       return new R8BuildMetadataImpl(
           options,
           baselineProfileRewritingOptions,
+          compilationInfo,
           dexChecksums,
           resourceOptimizationOptions,
           startupOptimizationOptions,
diff --git a/src/main/java/com/android/tools/r8/metadata/impl/R8CompilationInfoImpl.java b/src/main/java/com/android/tools/r8/metadata/impl/R8CompilationInfoImpl.java
new file mode 100644
index 0000000..e68225d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/metadata/impl/R8CompilationInfoImpl.java
@@ -0,0 +1,56 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.metadata.impl;
+
+import com.android.tools.r8.keepanno.annotations.AnnotationPattern;
+import com.android.tools.r8.keepanno.annotations.FieldAccessFlags;
+import com.android.tools.r8.keepanno.annotations.KeepConstraint;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.UsedByReflection;
+import com.android.tools.r8.metadata.R8CompilationInfo;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+import java.util.concurrent.ExecutorService;
+
+@UsedByReflection(
+    description = "Keep and preserve @SerializedName for correct (de)serialization",
+    constraints = {KeepConstraint.LOOKUP},
+    constrainAnnotations = @AnnotationPattern(constant = SerializedName.class),
+    kind = KeepItemKind.CLASS_AND_FIELDS,
+    fieldAccess = {FieldAccessFlags.PRIVATE},
+    fieldAnnotatedByClassConstant = SerializedName.class)
+public class R8CompilationInfoImpl implements R8CompilationInfo {
+
+  @Expose
+  @SerializedName("buildTime")
+  private final long buildTime;
+
+  @Expose
+  @SerializedName("numberOfThreads")
+  private final long numberOfThreads;
+
+  private R8CompilationInfoImpl(long buildTime, int numberOfThreads) {
+    this.buildTime = buildTime;
+    this.numberOfThreads = numberOfThreads;
+  }
+
+  public static R8CompilationInfoImpl create(
+      ExecutorService executorService, InternalOptions options) {
+    assert options.created > 0;
+    long buildTime = System.nanoTime() - options.created;
+    return new R8CompilationInfoImpl(buildTime, ThreadUtils.getNumberOfThreads(executorService));
+  }
+
+  @Override
+  public long getBuildTime() {
+    return buildTime;
+  }
+
+  @Override
+  public long getNumberOfThreads() {
+    return numberOfThreads;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/relocator/Relocator.java b/src/main/java/com/android/tools/r8/relocator/Relocator.java
index a0824ca..d74a6e9 100644
--- a/src/main/java/com/android/tools/r8/relocator/Relocator.java
+++ b/src/main/java/com/android/tools/r8/relocator/Relocator.java
@@ -81,7 +81,8 @@
       appView.setAppServices(AppServices.builder(appView).build());
       appView.setNamingLens(command.getMapping().compute(appView));
       new GenericSignatureRewriter(appView).run(appInfo.classes(), executor);
-      new CfApplicationWriter(appView, new Marker(Tool.Relocator)).write(command.getConsumer());
+      new CfApplicationWriter(appView, new Marker(Tool.Relocator))
+          .write(command.getConsumer(), executor);
       options.printWarnings();
     } catch (ExecutionException e) {
       throw unwrapExecutionException(e);
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 763198e..ad6845e 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -241,6 +241,7 @@
     return proguardConfiguration;
   }
 
+  public long created = -1;
   private final ProguardConfiguration proguardConfiguration;
   public final Reporter reporter;