Add test-based benchmark for Tivi app dump.

Bug: 221811893
Change-Id: I26df69871ca9a08f128d1b4d62b9699ac20fea04
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index a522278..b2e0f6d 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import com.android.tools.r8.dump.DumpOptions;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexItemFactory;
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index c8923f8..b75995f 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -7,6 +7,7 @@
 
 import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
 import com.android.tools.r8.dex.Marker.Tool;
+import com.android.tools.r8.dump.DumpOptions;
 import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
@@ -526,7 +527,7 @@
   }
 
   private DumpOptions dumpOptions() {
-    DumpOptions.Builder builder = DumpOptions.builder(Tool.D8);
+    DumpOptions.Builder builder = DumpOptions.builder(Tool.D8).readCurrentSystemProperties();
     dumpBaseCommandOptions(builder);
     return builder
         .setIntermediate(intermediate)
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index 8678a9d..b1965fb 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.dex.Marker.Tool;
+import com.android.tools.r8.dump.DumpOptions;
 import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
@@ -442,7 +443,7 @@
   }
 
   private DumpOptions dumpOptions() {
-    DumpOptions.Builder builder = DumpOptions.builder(Tool.L8);
+    DumpOptions.Builder builder = DumpOptions.builder(Tool.L8).readCurrentSystemProperties();
     dumpBaseCommandOptions(builder);
     if (r8Command != null) {
       builder.setProguardConfiguration(r8Command.getInternalOptions().getProguardConfiguration());
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 3c376ec..11a1e5d 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.dex.Marker.Tool;
+import com.android.tools.r8.dump.DumpOptions;
 import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
 import com.android.tools.r8.experimental.startup.StartupConfiguration;
@@ -1010,7 +1011,7 @@
   }
 
   private DumpOptions dumpOptions() {
-    DumpOptions.Builder builder = DumpOptions.builder(Tool.R8);
+    DumpOptions.Builder builder = DumpOptions.builder(Tool.R8).readCurrentSystemProperties();
     dumpBaseCommandOptions(builder);
     return builder
         .setIncludeDataResources(includeDataResources)
diff --git a/src/main/java/com/android/tools/r8/dump/CompilerDump.java b/src/main/java/com/android/tools/r8/dump/CompilerDump.java
new file mode 100644
index 0000000..a303c14
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/dump/CompilerDump.java
@@ -0,0 +1,56 @@
+// Copyright (c) 2022, 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.dump;
+
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.ZipUtils;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+public class CompilerDump {
+
+  private final Path directory;
+
+  public static CompilerDump fromArchive(Path dumpArchive, Path dumpExtractionDirectory)
+      throws IOException {
+    ZipUtils.unzip(dumpArchive, dumpExtractionDirectory);
+    return new CompilerDump(dumpExtractionDirectory);
+  }
+
+  public CompilerDump(Path directory) {
+    this.directory = directory;
+  }
+
+  public Path getProgramArchive() {
+    return directory.resolve("program.jar");
+  }
+
+  public Path getClasspathArchive() {
+    return directory.resolve("classpath.jar");
+  }
+
+  public Path getLibraryArchive() {
+    return directory.resolve("library.jar");
+  }
+
+  public Path getBuildPropertiesFile() {
+    return directory.resolve("build.properties");
+  }
+
+  public Path getProguardConfigFile() {
+    return directory.resolve("proguard.config");
+  }
+
+  public DumpOptions getBuildProperties() throws IOException {
+    if (Files.exists(getBuildPropertiesFile())) {
+      DumpOptions.Builder builder = new DumpOptions.Builder();
+      DumpOptions.parse(
+          FileUtils.readTextFile(getBuildPropertiesFile(), StandardCharsets.UTF_8), builder);
+      return builder.build();
+    }
+    return null;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/DumpOptions.java b/src/main/java/com/android/tools/r8/dump/DumpOptions.java
similarity index 72%
rename from src/main/java/com/android/tools/r8/DumpOptions.java
rename to src/main/java/com/android/tools/r8/dump/DumpOptions.java
index 2726086..224d24a 100644
--- a/src/main/java/com/android/tools/r8/DumpOptions.java
+++ b/src/main/java/com/android/tools/r8/dump/DumpOptions.java
@@ -1,9 +1,10 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2022, 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.dump;
 
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.experimental.startup.StartupConfiguration;
 import com.android.tools.r8.features.FeatureSplitConfiguration;
@@ -12,7 +13,10 @@
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
 import com.android.tools.r8.utils.ThreadUtils;
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 
 @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@@ -55,6 +59,8 @@
   private final ProguardConfiguration proguardConfiguration;
   private final List<ProguardConfigurationRule> mainDexKeepRules;
 
+  private final Map<String, String> systemProperties;
+
   // Reporting only.
   private final boolean dumpInputToFile;
 
@@ -74,6 +80,7 @@
       FeatureSplitConfiguration featureSplitConfiguration,
       ProguardConfiguration proguardConfiguration,
       List<ProguardConfigurationRule> mainDexKeepRules,
+      Map<String, String> systemProperties,
       boolean dumpInputToFile) {
     this.tool = tool;
     this.compilationMode = compilationMode;
@@ -90,6 +97,7 @@
     this.featureSplitConfiguration = featureSplitConfiguration;
     this.proguardConfiguration = proguardConfiguration;
     this.mainDexKeepRules = mainDexKeepRules;
+    this.systemProperties = systemProperties;
     this.dumpInputToFile = dumpInputToFile;
   }
 
@@ -112,18 +120,93 @@
     addOptionalDumpEntry(builder, TREE_SHAKING_KEY, treeShaking);
     addOptionalDumpEntry(builder, MINIFICATION_KEY, minification);
     addOptionalDumpEntry(builder, FORCE_PROGUARD_COMPATIBILITY_KEY, forceProguardCompatibility);
-    System.getProperties()
-        .stringPropertyNames()
-        .forEach(
-            name -> {
-              if (name.startsWith("com.android.tools.r8.")) {
-                String value = System.getProperty(name);
-                addDumpEntry(builder, SYSTEM_PROPERTY_PREFIX + name, value);
-              }
-            });
+    ArrayList<String> sortedKeys = new ArrayList<>(systemProperties.keySet());
+    sortedKeys.sort(String::compareTo);
+    sortedKeys.forEach(
+        key -> addDumpEntry(builder, SYSTEM_PROPERTY_PREFIX + key, systemProperties.get(key)));
     return builder.toString();
   }
 
+  public static void parse(String content, DumpOptions.Builder builder) {
+    String[] lines = content.split("\n");
+    for (String line : lines) {
+      String trimmed = line.trim();
+      int i = trimmed.indexOf('=');
+      if (i < 0) {
+        throw new RuntimeException("Invalid dump line. Expected = in line: '" + trimmed + "'");
+      }
+      String key = trimmed.substring(0, i).trim();
+      String value = trimmed.substring(i + 1).trim();
+      parseKeyValue(builder, key, value);
+    }
+  }
+
+  private static void parseKeyValue(Builder builder, String key, String value) {
+    switch (key) {
+      case TOOL_KEY:
+        builder.setTool(Tool.valueOf(value));
+        return;
+      case MODE_KEY:
+        if (value.equals(DEBUG_MODE_VALUE)) {
+          builder.setCompilationMode(CompilationMode.DEBUG);
+        } else if (value.equals(RELEASE_MODE_VALUE)) {
+          builder.setCompilationMode(CompilationMode.RELEASE);
+        } else {
+          parseKeyValueError(key, value);
+        }
+        return;
+      case MIN_API_KEY:
+        builder.setMinApi(Integer.parseInt(value));
+        return;
+      case OPTIMIZE_MULTIDEX_FOR_LINEAR_ALLOC_KEY:
+        builder.setOptimizeMultidexForLinearAlloc(Boolean.parseBoolean(value));
+        return;
+      case THREAD_COUNT_KEY:
+        builder.setThreadCount(Integer.parseInt(value));
+        return;
+      case DESUGAR_STATE_KEY:
+        builder.setDesugarState(DesugarState.valueOf(value));
+        return;
+      case INTERMEDIATE_KEY:
+        builder.setIntermediate(Boolean.parseBoolean(value));
+        return;
+      case INCLUDE_DATA_RESOURCES_KEY:
+        builder.setIncludeDataResources(Optional.of(Boolean.parseBoolean(value)));
+        return;
+      case TREE_SHAKING_KEY:
+        builder.setTreeShaking(Boolean.parseBoolean(value));
+        return;
+      case MINIFICATION_KEY:
+        builder.setMinification(Boolean.parseBoolean(value));
+        return;
+      case FORCE_PROGUARD_COMPATIBILITY_KEY:
+        builder.setForceProguardCompatibility(Boolean.parseBoolean(value));
+        return;
+      default:
+        if (key.startsWith(SYSTEM_PROPERTY_PREFIX)) {
+          builder.setSystemProperty(key.substring(SYSTEM_PROPERTY_PREFIX.length()), value);
+        } else {
+          parseKeyValueError(key, value);
+        }
+    }
+  }
+
+  private static void parseKeyValueError(String key, String value) {
+    throw new RuntimeException("Unknown key value pair: " + key + " = " + value);
+  }
+
+  public Tool getTool() {
+    return tool;
+  }
+
+  public CompilationMode getCompilationMode() {
+    return compilationMode;
+  }
+
+  public int getMinApi() {
+    return minApi;
+  }
+
   private void addOptionalDumpEntry(StringBuilder builder, String key, Optional<?> optionalValue) {
     optionalValue.ifPresent(bool -> addDumpEntry(builder, key, bool));
   }
@@ -169,11 +252,11 @@
   }
 
   public static Builder builder(Tool tool) {
-    return new Builder(tool);
+    return new Builder().setTool(tool);
   }
 
   public static class Builder {
-    private final Tool tool;
+    private Tool tool;
     private CompilationMode compilationMode;
     private int minApi;
     private boolean optimizeMultidexForLinearAlloc;
@@ -190,11 +273,16 @@
     private ProguardConfiguration proguardConfiguration;
     private List<ProguardConfigurationRule> mainDexKeepRules;
 
+    private Map<String, String> systemProperties = new HashMap<>();
+
     // Reporting only.
     private boolean dumpInputToFile;
 
-    public Builder(Tool tool) {
+    public Builder() {}
+
+    public Builder setTool(Tool tool) {
       this.tool = tool;
+      return this;
     }
 
     public Builder setCompilationMode(CompilationMode compilationMode) {
@@ -274,7 +362,26 @@
       return this;
     }
 
+    public Builder setSystemProperty(String key, String value) {
+      this.systemProperties.put(key, value);
+      return this;
+    }
+
+    public Builder readCurrentSystemProperties() {
+      System.getProperties()
+          .stringPropertyNames()
+          .forEach(
+              name -> {
+                if (name.startsWith("com.android.tools.r8.")) {
+                  String value = System.getProperty(name);
+                  setSystemProperty(name, value);
+                }
+              });
+      return this;
+    }
+
     public DumpOptions build() {
+      assert tool != null;
       return new DumpOptions(
           tool,
           compilationMode,
@@ -291,6 +398,7 @@
           featureSplitConfiguration,
           proguardConfiguration,
           mainDexKeepRules,
+          systemProperties,
           dumpInputToFile);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 959238e..3b7706b 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -21,7 +21,6 @@
 import com.android.tools.r8.DexFilePerClassFileConsumer;
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.DirectoryClassFileProvider;
-import com.android.tools.r8.DumpOptions;
 import com.android.tools.r8.FeatureSplit;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.ProgramResource;
@@ -31,6 +30,7 @@
 import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.StringResource;
 import com.android.tools.r8.Version;
+import com.android.tools.r8.dump.DumpOptions;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.errors.Unreachable;
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 2b8d72a..f18c96f 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.DesugarGraphConsumer;
 import com.android.tools.r8.DexFilePerClassFileConsumer;
 import com.android.tools.r8.DexIndexedConsumer;
-import com.android.tools.r8.DumpOptions;
 import com.android.tools.r8.FeatureSplit;
 import com.android.tools.r8.MapIdProvider;
 import com.android.tools.r8.ProgramConsumer;
@@ -23,6 +22,7 @@
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.dex.Marker.Backend;
 import com.android.tools.r8.dex.Marker.Tool;
+import com.android.tools.r8.dump.DumpOptions;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.ExperimentalClassFileVersionDiagnostic;
 import com.android.tools.r8.errors.IncompleteNestNestDesugarDiagnosic;
diff --git a/src/test/java/com/android/tools/r8/AndroidAppDumpsTest.java b/src/test/java/com/android/tools/r8/AndroidAppDumpsTest.java
index 7a35696..28be79a 100644
--- a/src/test/java/com/android/tools/r8/AndroidAppDumpsTest.java
+++ b/src/test/java/com/android/tools/r8/AndroidAppDumpsTest.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.DataResourceProvider.Visitor;
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.dex.Marker.Tool;
+import com.android.tools.r8.dump.DumpOptions;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index ab32b4f..10bc820 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -125,7 +125,7 @@
         builder, rules -> box.syntheticProguardRules = rules);
     libraryDesugaringTestConfiguration.configure(builder);
     ToolHelper.runAndBenchmarkR8WithoutResult(
-        builder.build(),
+        builder,
         optionsConsumer.andThen(
             options -> box.proguardConfiguration = options.getProguardConfiguration()),
         benchmarkResults);
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index efe353d..a476d13 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -509,7 +509,7 @@
   }
 
   public CR assertNoInfoMessages() {
-    getDiagnosticMessages().assertInfosCount(0);
+    getDiagnosticMessages().assertNoInfos();
     return self();
   }
 
@@ -529,7 +529,7 @@
   }
 
   public CR assertNoWarningMessages() {
-    getDiagnosticMessages().assertWarningsCount(0);
+    getDiagnosticMessages().assertNoWarnings();
     return self();
   }
 
@@ -544,7 +544,7 @@
   }
 
   public CR assertNoErrorMessages() {
-    getDiagnosticMessages().assertErrorsCount(0);
+    getDiagnosticMessages().assertNoErrors();
     return self();
   }
 
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index dc4d111..2b6d46b 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -1272,23 +1272,18 @@
     return runR8WithFullResult(command, optionsConsumer);
   }
 
-  public static void runR8WithoutResult(
-      R8Command command, Consumer<InternalOptions> optionsConsumer)
-      throws CompilationFailedException {
-    runAndBenchmarkR8WithoutResult(command, optionsConsumer, null);
-  }
-
   public static void runAndBenchmarkR8WithoutResult(
-      R8Command command,
+      R8Command.Builder commandBuilder,
       Consumer<InternalOptions> optionsConsumer,
       BenchmarkResults benchmarkResults)
       throws CompilationFailedException {
-    InternalOptions internalOptions = command.getInternalOptions();
-    optionsConsumer.accept(internalOptions);
     long start = 0;
     if (benchmarkResults != null) {
       start = System.nanoTime();
     }
+    R8Command command = commandBuilder.build();
+    InternalOptions internalOptions = command.getInternalOptions();
+    optionsConsumer.accept(internalOptions);
     R8.runForTesting(command.getInputApp(), internalOptions);
     if (benchmarkResults != null) {
       long end = System.nanoTime();
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollection.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollection.java
index c741f27..5dc4bd9 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollection.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollection.java
@@ -5,6 +5,7 @@
 
 import static java.util.Collections.emptyList;
 
+import com.android.tools.r8.benchmarks.appdumps.TiviBenchmarks;
 import com.android.tools.r8.benchmarks.desugaredlib.LegacyDesugaredLibraryBenchmark;
 import com.android.tools.r8.benchmarks.helloworld.HelloWorldBenchmark;
 import java.io.IOException;
@@ -45,6 +46,7 @@
     // Every benchmark that should be active on golem must be setup in this method.
     HelloWorldBenchmark.configs().forEach(collection::addBenchmark);
     LegacyDesugaredLibraryBenchmark.configs().forEach(collection::addBenchmark);
+    TiviBenchmarks.configs().forEach(collection::addBenchmark);
     return collection;
   }
 
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkTarget.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkTarget.java
index cda7f6e..0832e6c 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkTarget.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkTarget.java
@@ -4,10 +4,11 @@
 package com.android.tools.r8.benchmarks;
 
 public enum BenchmarkTarget {
+
   // Possible dashboard targets on golem.
   D8("d8", "D8"),
   R8_COMPAT("r8-compat", "R8"),
-  R8_NON_COMPAT("r8", "R8-full"),
+  R8_NON_COMPAT("r8-full", "R8-full"),
   R8_FORCE_OPT("r8-force", "R8-full-minify-optimize-shrink");
 
   private final String idName;
diff --git a/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java b/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java
new file mode 100644
index 0000000..e04b43f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java
@@ -0,0 +1,103 @@
+// Copyright (c) 2022, 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.appdumps;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.benchmarks.BenchmarkBase;
+import com.android.tools.r8.benchmarks.BenchmarkConfig;
+import com.android.tools.r8.benchmarks.BenchmarkConfigError;
+import com.android.tools.r8.benchmarks.BenchmarkDependency;
+import com.android.tools.r8.benchmarks.BenchmarkEnvironment;
+import com.android.tools.r8.benchmarks.BenchmarkMethod;
+import com.android.tools.r8.benchmarks.BenchmarkTarget;
+import com.android.tools.r8.dump.CompilerDump;
+import com.android.tools.r8.dump.DumpOptions;
+import java.io.IOException;
+import java.nio.file.Path;
+
+public class AppDumpBenchmarkBuilder {
+
+  public static AppDumpBenchmarkBuilder builder() {
+    return new AppDumpBenchmarkBuilder();
+  }
+
+  private String name;
+  private BenchmarkDependency dumpDependency;
+  private int fromRevision = -1;
+
+  public void verify() {
+    if (name == null) {
+      throw new BenchmarkConfigError("Missing name");
+    }
+    if (dumpDependency == null) {
+      throw new BenchmarkConfigError("Missing dump");
+    }
+    if (fromRevision < 0) {
+      throw new BenchmarkConfigError("Missing from-revision");
+    }
+  }
+
+  public AppDumpBenchmarkBuilder setName(String name) {
+    this.name = name;
+    return this;
+  }
+
+  public AppDumpBenchmarkBuilder setDumpDependencyPath(Path dumpDependencyPath) {
+    return setDumpDependency(
+        new BenchmarkDependency(
+            dumpDependencyPath.getFileName().toString(), dumpDependencyPath.getParent()));
+  }
+
+  public AppDumpBenchmarkBuilder setDumpDependency(BenchmarkDependency dependency) {
+    this.dumpDependency = dependency;
+    return this;
+  }
+
+  public AppDumpBenchmarkBuilder setFromRevision(int fromRevision) {
+    this.fromRevision = fromRevision;
+    return this;
+  }
+
+  public BenchmarkConfig build() {
+    verify();
+    return BenchmarkConfig.builder()
+        .setName(name)
+        .setTarget(BenchmarkTarget.R8_NON_COMPAT)
+        .setMethod(run(this))
+        .setFromRevision(fromRevision)
+        .addDependency(dumpDependency)
+        .measureRunTime()
+        .measureCodeSize()
+        .build();
+  }
+
+  private CompilerDump getExtractedDump(BenchmarkEnvironment environment) throws IOException {
+    Path dump = dumpDependency.getRoot(environment).resolve("dump_app.zip");
+    return CompilerDump.fromArchive(dump, environment.getTemp().newFolder().toPath());
+  }
+
+  private static BenchmarkMethod run(AppDumpBenchmarkBuilder builder) {
+    return environment ->
+        BenchmarkBase.runner(environment.getConfig())
+            .setWarmupIterations(1)
+            .run(
+                results -> {
+                  CompilerDump dump = builder.getExtractedDump(environment);
+                  DumpOptions dumpProperties = dump.getBuildProperties();
+                  TestBase.testForR8(environment.getTemp(), Backend.DEX)
+                      // TODO(b/221811893): mock a typical setup of program providers from agp.
+                      .addProgramFiles(dump.getProgramArchive())
+                      .addLibraryFiles(dump.getLibraryArchive())
+                      .addKeepRuleFiles(dump.getProguardConfigFile())
+                      .setMinApi(dumpProperties.getMinApi())
+                      .allowUnusedDontWarnPatterns()
+                      .allowUnusedProguardConfigurationRules()
+                      // TODO(b/222228826): Disallow unrecognized diagnostics.
+                      .allowDiagnosticMessages()
+                      .benchmarkCompile(results)
+                      .benchmarkCodeSize(results);
+                });
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/benchmarks/appdumps/TiviBenchmarks.java b/src/test/java/com/android/tools/r8/benchmarks/appdumps/TiviBenchmarks.java
new file mode 100644
index 0000000..235c559
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/benchmarks/appdumps/TiviBenchmarks.java
@@ -0,0 +1,39 @@
+// Copyright (c) 2022, 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.appdumps;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.benchmarks.BenchmarkBase;
+import com.android.tools.r8.benchmarks.BenchmarkConfig;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class TiviBenchmarks extends BenchmarkBase {
+
+  private static final Path dump = Paths.get("third_party", "opensource-apps", "tivi");
+
+  public TiviBenchmarks(BenchmarkConfig config, TestParameters parameters) {
+    super(config, parameters);
+  }
+
+  @Parameters(name = "{0}")
+  public static List<Object[]> data() {
+    return parametersFromConfigs(configs());
+  }
+
+  public static List<BenchmarkConfig> configs() {
+    return ImmutableList.of(
+        AppDumpBenchmarkBuilder.builder()
+            .setName("Tivi")
+            .setDumpDependencyPath(dump)
+            .setFromRevision(12170)
+            .build());
+  }
+}