Merge "Benchmarks for incremental builds of framework."
diff --git a/src/main/java/com/android/tools/r8/benchmarks/BenchmarkUtils.java b/src/main/java/com/android/tools/r8/benchmarks/BenchmarkUtils.java
new file mode 100644
index 0000000..987580f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/benchmarks/BenchmarkUtils.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2017, 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.benchmarks;
+
+public class BenchmarkUtils {
+
+  public static void printRuntimeNanoseconds(String name, double nano) {
+    printRuntimeMilliseconds(name, nano / 1000000.0);
+  }
+
+  public static void printRuntimeMilliseconds(String name, double ms) {
+    System.out.println(name + "(RunTime): " + ms + " ms");
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/benchmarks/FrameworkIncrementalDexingBenchmark.java b/src/main/java/com/android/tools/r8/benchmarks/FrameworkIncrementalDexingBenchmark.java
new file mode 100644
index 0000000..2120714
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/benchmarks/FrameworkIncrementalDexingBenchmark.java
@@ -0,0 +1,214 @@
+// Copyright (c) 2017, 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.benchmarks;
+
+import static com.android.tools.r8.benchmarks.BenchmarkUtils.printRuntimeNanoseconds;
+
+import com.android.tools.r8.ClassFileResourceProvider;
+import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.D8Output;
+import com.android.tools.r8.Resource;
+import com.android.tools.r8.Resource.Origin;
+import com.android.tools.r8.Resource.PathOrigin;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.OutputMode;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.ZipUtils;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+
+public class FrameworkIncrementalDexingBenchmark {
+  private static final int ITERATIONS = 100;
+  private static final int API = 24;
+  private static final Path JAR_DESUGARED =
+      Paths.get("third_party", "framework", "framework_14082017_desugared.jar");
+  private static final Path JAR_NOT_DESUGARED =
+      Paths.get("third_party", "framework", "framework_14082017.jar");
+  private static final Path LIB =
+      Paths.get("third_party", "android_jar", "lib-v" + API, "android.jar");
+
+  static class InMemoryClassPathProvider implements ClassFileResourceProvider {
+    Origin origin;
+    Map<String, byte[]> resources;
+
+    InMemoryClassPathProvider(Path archive) throws IOException {
+      origin = new PathOrigin(archive, Origin.root());
+      ImmutableMap.Builder<String, byte[]> builder = ImmutableMap.builder();
+      ZipUtils.iter(
+          archive.toString(),
+          (entry, stream) -> {
+            String name = entry.getName();
+            if (FileUtils.isClassFile(Paths.get(name))) {
+              String descriptor = DescriptorUtils.guessTypeDescriptor(name);
+              builder.put(descriptor, ByteStreams.toByteArray(stream));
+            }
+          });
+      resources = builder.build();
+    }
+
+    @Override
+    public Set<String> getClassDescriptors() {
+      return resources.keySet();
+    }
+
+    @Override
+    public Resource getResource(String descriptor) {
+      byte[] bytes = resources.get(descriptor);
+      return bytes == null
+          ? null
+          : Resource.fromBytes(
+              new EntryOrigin(descriptor, origin), bytes, Collections.singleton(descriptor));
+    }
+  }
+
+  static class EntryOrigin extends Origin {
+    final String descriptor;
+
+    public EntryOrigin(String descriptor, Origin parent) {
+      super(parent);
+      this.descriptor = descriptor;
+    }
+
+    @Override
+    public String part() {
+      return descriptor;
+    }
+  }
+
+  private static String title(String title, boolean desugar) {
+    return "FrameworkIncremental" + (desugar ? title : "NoDesugar" + title);
+  }
+
+  private static void compileAll(
+      Path input,
+      InMemoryClassPathProvider provider,
+      boolean desugar,
+      Map<String, Resource> outputs,
+      ExecutorService executor)
+      throws IOException, CompilationException {
+    long start = System.nanoTime();
+    D8Output out =
+        D8.run(
+            D8Command.builder()
+                .setMinApiLevel(API)
+                .setIntermediate(true)
+                .setMode(CompilationMode.DEBUG)
+                .addProgramFiles(input)
+                .addLibraryFiles(LIB)
+                .setOutputMode(OutputMode.FilePerInputClass)
+                .setEnableDesugaring(desugar)
+                .build(),
+            executor);
+    printRuntimeNanoseconds(title("DexAll", desugar), System.nanoTime() - start);
+    for (Resource resource : out.getDexResources()) {
+      for (String descriptor : resource.getClassDescriptors()) {
+        assert !outputs.containsKey(descriptor);
+        if (provider.resources.containsKey(descriptor)) {
+          outputs.put(descriptor, resource);
+        }
+      }
+    }
+  }
+
+  private static void compileGroupsOf(
+      int count,
+      List<String> descriptors,
+      InMemoryClassPathProvider provider,
+      boolean desugar,
+      Map<String, Resource> outputs,
+      ExecutorService executor)
+      throws IOException, CompilationException {
+    descriptors.sort(String::compareTo);
+    int increment = descriptors.size() / ITERATIONS;
+    long start = System.nanoTime();
+    for (int iteration = 0; iteration < ITERATIONS; iteration++) {
+      int index = iteration * increment;
+      List<byte[]> inputs = new ArrayList<>(count);
+      for (int j = 0; j < count; j++) {
+        provider.resources.get(descriptors.get(index + j));
+      }
+      D8Output out =
+          D8.run(
+              D8Command.builder()
+                  .setMinApiLevel(API)
+                  .setIntermediate(true)
+                  .setMode(CompilationMode.DEBUG)
+                  .addClassProgramData(inputs)
+                  .addClasspathResourceProvider(provider)
+                  .addLibraryFiles(LIB)
+                  .setOutputMode(OutputMode.FilePerInputClass)
+                  .setEnableDesugaring(desugar)
+                  .build(),
+              executor);
+      for (Resource resource : out.getDexResources()) {
+        for (String descriptor : resource.getClassDescriptors()) {
+          if (provider.resources.containsKey(descriptor)) {
+            outputs.put(descriptor, resource);
+          }
+        }
+      }
+    }
+    printRuntimeNanoseconds(title("DexGroupsOf" + count, desugar), System.nanoTime() - start);
+  }
+
+  private static D8Output merge(
+      boolean desugar,
+      Map<String, Resource> outputs,
+      ExecutorService executor)
+      throws IOException, CompilationException {
+    List<byte[]> bytes = new ArrayList<>(outputs.size());
+    for (Resource input : outputs.values()) {
+      bytes.add(ByteStreams.toByteArray(input.getStream()));
+    }
+    long start = System.nanoTime();
+    D8Output out =
+        D8.run(
+            D8Command.builder()
+                .setMinApiLevel(API)
+                .setIntermediate(false)
+                .setMode(CompilationMode.DEBUG)
+                .addDexProgramData(bytes)
+                .setOutputMode(OutputMode.Indexed)
+                .setEnableDesugaring(false) // never need to desugar when merging dex.
+                .build(),
+            executor);
+    printRuntimeNanoseconds(title("DexMerge", desugar), System.nanoTime() - start);
+    return out;
+  }
+
+  public static void main(String[] args) throws IOException, CompilationException {
+    boolean desugar = Arrays.asList(args).contains("--desugar");
+    Path input = desugar ? JAR_NOT_DESUGARED : JAR_DESUGARED;
+    InMemoryClassPathProvider provider = new InMemoryClassPathProvider(input);
+    List<String> descriptors = new ArrayList<>(provider.getClassDescriptors());
+    Map<String, Resource> outputs = new HashMap<>(provider.getClassDescriptors().size());
+    int threads = Integer.min(Runtime.getRuntime().availableProcessors(), 16) / 2;
+    ExecutorService executor = ThreadUtils.getExecutorService(threads);
+    try {
+      compileAll(input, provider, desugar, outputs, executor);
+      compileGroupsOf(1, descriptors, provider, desugar, outputs, executor);
+      compileGroupsOf(10, descriptors, provider, desugar, outputs, executor);
+      compileGroupsOf(100, descriptors, provider, desugar, outputs, executor);
+      merge(desugar, outputs, executor);
+      // TODO: We should run dex2oat to verify the compilation.
+    } finally {
+      executor.shutdown();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/IncrementalDexingBenchmark.java b/src/main/java/com/android/tools/r8/benchmarks/IncrementalDexingBenchmark.java
similarity index 80%
rename from src/main/java/com/android/tools/r8/IncrementalDexingBenchmark.java
rename to src/main/java/com/android/tools/r8/benchmarks/IncrementalDexingBenchmark.java
index 4e1f2c9..257f814 100644
--- a/src/main/java/com/android/tools/r8/IncrementalDexingBenchmark.java
+++ b/src/main/java/com/android/tools/r8/benchmarks/IncrementalDexingBenchmark.java
@@ -1,8 +1,13 @@
 // Copyright (c) 2017, 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;
+package com.android.tools.r8.benchmarks;
 
+import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.D8Output;
 import com.android.tools.r8.utils.ThreadUtils;
 import java.io.IOException;
 import java.nio.file.Paths;
@@ -33,7 +38,7 @@
         compile(executor);
       }
       double elapsedMs = (System.nanoTime() - start) / 1000000.0;
-      System.out.println("IncrementalDexing(RunTime): " + elapsedMs + " ms");
+      BenchmarkUtils.printRuntimeMilliseconds("IncrementalDexing", elapsedMs);
     } finally {
       executor.shutdown();
     }