Benchmark for compiling a trivial hello world program.

Change-Id: Idac927a5fadb005a218cf7b7ae53345496b0ef90
diff --git a/.gitignore b/.gitignore
index 9432723..30a0e6e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -103,6 +103,8 @@
 third_party/openjdk/openjdk-rt-1.8.tar.gz
 third_party/r8
 third_party/r8.tar.gz
+third_party/sample_libraries
+third_party/sample_libraries.tar.gz
 src/test/jack/ub-jack
 gradle-app.setting
 gradlew
diff --git a/third_party/sample_libraries.tar.gz.sha1 b/third_party/sample_libraries.tar.gz.sha1
new file mode 100644
index 0000000..4edf7c6
--- /dev/null
+++ b/third_party/sample_libraries.tar.gz.sha1
@@ -0,0 +1 @@
+93bbc2800540f2bb9d27132975c9f3301b4905c8
\ No newline at end of file
diff --git a/tools/golem.py b/tools/golem.py
index 6552145..dd3a25b 100755
--- a/tools/golem.py
+++ b/tools/golem.py
@@ -20,6 +20,7 @@
     'opensource_apps',
     'proguardsettings',
     'r8',
+    'sample_libraries',
     'youtube',
 ]
 
diff --git a/tools/test_helloexample.py b/tools/test_helloexample.py
new file mode 100755
index 0000000..c0761ef
--- /dev/null
+++ b/tools/test_helloexample.py
@@ -0,0 +1,251 @@
+#!/usr/bin/env python
+# Copyright (c) 2019, 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.
+
+# Run R8 on a simple Hello World program
+# Report Golem-compatible RunTimeRaw values:
+#
+#     <NAME>-Total(RunTimeRaw): <time> ms
+#
+# where <NAME> is Hello{,Dex}{,Large}{,NoOpt}
+
+import argparse
+import os
+import subprocess
+import sys
+import time
+import zipfile
+
+import golem
+import jdk
+import utils
+
+HELLO_JAR = os.path.join(utils.BUILD, 'test', 'examples', 'hello.jar')
+
+EXTRA_INPUTS = [
+  os.path.join(utils.THIRD_PARTY, 'sample_libraries', lib) for lib in [
+    'animal-sniffer-annotations-1.17.jar',
+    'annotations-13.0.jar',
+    'checker-compat-qual-2.5.2.jar',
+    'collections-28.0.0.jar',
+    'common-1.1.1.jar',
+    'commons-collections4-4.3.jar',
+    'commons-compress-1.18.jar',
+    'commons-lang3-3.8.1.jar',
+    'commons-math3-3.6.1.jar',
+    'constraint-layout-solver-1.1.3.jar',
+    'converter-gson-2.5.0.jar',
+    'dagger-2.22.1.jar',
+    'error_prone_annotations-2.2.0.jar',
+    'failureaccess-1.0.1.jar',
+    'gson-2.8.2.jar',
+    'guava-27.1-android.jar',
+    'j2objc-annotations-1.1.jar',
+    'javax.inject-1.jar',
+    'jsr305-3.0.2.jar',
+    'kotlin-stdlib-1.3.21.jar',
+    'kotlin-stdlib-common-1.3.21.jar',
+    'kotlin-stdlib-jdk7-1.3.21.jar',
+    'listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar',
+    'okhttp-3.14.0.jar',
+    'okio-1.17.2.jar',
+    'play-services-ads-17.2.0-javadoc.jar',
+    'play-services-ads-base-17.2.0-javadoc.jar',
+    'play-services-ads-lite-17.2.0-javadoc.jar',
+    'play-services-analytics-16.0.8-javadoc.jar',
+    'play-services-analytics-impl-16.0.8-javadoc.jar',
+    'play-services-base-16.1.0-javadoc.jar',
+    'play-services-basement-16.2.0-javadoc.jar',
+    'play-services-cast-16.1.2-javadoc.jar',
+    'play-services-drive-16.1.0-javadoc.jar',
+    'play-services-fitness-16.0.1-javadoc.jar',
+    'play-services-games-17.0.0-javadoc.jar',
+    'play-services-gass-17.2.0-javadoc.jar',
+    'play-services-gcm-16.1.0-javadoc.jar',
+    'play-services-iid-16.0.1-javadoc.jar',
+    'play-services-measurement-16.4.0-javadoc.jar',
+    'play-services-measurement-api-16.4.0-javadoc.jar',
+    'play-services-measurement-base-16.4.0-javadoc.jar',
+    'play-services-measurement-impl-16.4.0-javadoc.jar',
+    'play-services-measurement-sdk-16.4.0-javadoc.jar',
+    'play-services-measurement-sdk-api-16.4.0-javadoc.jar',
+    'play-services-tagmanager-v4-impl-16.0.8-javadoc.jar',
+    'play-services-vision-17.0.2-javadoc.jar',
+    'play-services-vision-common-17.0.2-javadoc.jar',
+    'protobuf-lite-3.0.1.jar',
+    'reactive-streams-1.0.2.jar',
+    'retrofit-2.5.0.jar',
+    'rxjava-2.2.8.jar',
+    'support-annotations-28.0.0.jar',
+  ]
+]
+
+EXTRA_KEEP_RULES = ['-dontwarn java.lang.ClassValue']
+
+def parse_arguments():
+  parser = argparse.ArgumentParser(
+      description = 'Compile a hello world example program')
+  parser.add_argument('--tool',
+                      choices = ['d8', 'r8', 'pg'],
+                      required = True,
+                      help = 'Compiler tool to use.')
+  parser.add_argument('--output-mode',
+                      choices = ['dex', 'cf'],
+                      required = True,
+                      help = 'Output mode to compile to.')
+  parser.add_argument('--golem',
+                      help = 'Running on golem, link in third_party resources.',
+                      default = False,
+                      action = 'store_true')
+  parser.add_argument('--large',
+                      help = 'Add many additional program inputs.',
+                      default = False,
+                      action = 'store_true')
+  parser.add_argument('--noopt',
+                      help = 'Disable most optimizations/processing.',
+                      default = False,
+                      action = 'store_true')
+  parser.add_argument('--print-memoryuse',
+                      help = 'Prints the line \'<NAME>-Total(MemoryUse):'
+                             ' <mem>\' at the end where <mem> is the peak'
+                             ' peak resident set size (VmHWM) in bytes.',
+                      default = False,
+                      action = 'store_true')
+  parser.add_argument('--output',
+                      help = 'Output directory to keep the generated files')
+  return parser.parse_args()
+
+def GetConfRules(extra, noopt):
+  rules = ['-keep class hello.Hello { void main(java.lang.String[]); }']
+  if len(extra) > 0:
+    rules.extend(EXTRA_KEEP_RULES)
+  if noopt:
+    rules.extend([
+      '-dontoptimize',
+      '-dontshrink',
+      '-dontobfuscate',
+      '-keepattributes *',
+    ])
+  return rules
+
+def GetCompilerPrefix(tool, mode, output, input, lib, extra, noopt):
+  return [
+    jdk.GetJavaExecutable(),
+    '-jar', utils.R8_JAR if tool == 'r8' else utils.D8_JAR,
+    '--output', output,
+    '--lib', lib,
+    '--debug' if noopt else '--release',
+    input,
+  ] + ([] if mode == 'cf' else ['--min-api', '21']) + extra
+
+def Compile(tool, output_mode, lib, extra, output_dir, noopt, temp_dir):
+  output = os.path.join(output_dir, 'out.zip')
+  if tool == 'd8':
+    if output_mode != 'dex':
+      raise ValueError('Invalid output mode for D8')
+    return [
+      GetCompilerPrefix(tool, output_mode, output, HELLO_JAR, lib, extra, noopt)
+    ]
+  # The compilation is either R8 or PG.
+  # Write keep rules to a temporary file.
+  rules = GetConfRules(extra, noopt)
+  rules_file = os.path.join(temp_dir, 'rules.conf')
+  open(rules_file, 'w').write('\n'.join(rules))
+  if tool == 'r8':
+    cmd = GetCompilerPrefix(
+        tool, output_mode, output, HELLO_JAR, lib, extra, noopt)
+    cmd.extend(['--pg-conf', rules_file])
+    if output_mode == 'cf':
+      cmd.append('--classfile')
+    return [cmd]
+  if tool == 'pg':
+    # Build PG invokation with additional rules to silence warnings.
+    pg_out = output if output_mode == 'cf' \
+      else os.path.join(output_dir, 'pgout.zip')
+    cmds = [[
+      jdk.GetJavaExecutable(),
+      '-jar', utils.PROGUARD_JAR,
+      '-injars', ':'.join([HELLO_JAR] + extra),
+      '-libraryjars', lib,
+      '-outjars', pg_out,
+      '-dontwarn **',
+      '@' + rules_file
+    ]]
+    if output_mode == 'dex':
+      cmds.append(
+          GetCompilerPrefix('d8', 'dex', output, pg_out, lib, [], noopt))
+    return cmds
+  raise ValueError('Unknown tool: ' + tool)
+
+def ProcessInput(input, tmp_dir):
+  if not input.endswith('.aar'):
+    return input
+  out_dir = os.path.join(tmp_dir, input)
+  os.makedirs(out_dir)
+  zip = zipfile.ZipFile(input, 'r')
+  zip.extractall(out_dir)
+  zip.close()
+  return os.path.join(out_dir, 'classes.jar')
+
+def Main():
+  args = parse_arguments()
+  if args.golem:
+    golem.link_third_party()
+  utils.check_java_version()
+
+  with utils.TempDir() as temp_dir:
+    cmd_prefix = []
+    output_dir = args.output if args.output else temp_dir
+    temp_dir = os.path.join(args.output, 'tmp') if args.output else temp_dir
+
+    track_memory_file = None
+    if args.print_memoryuse:
+      track_memory_file = os.path.join(output_dir, utils.MEMORY_USE_TMP_FILE)
+      cmd_prefix.extend(['tools/track_memory.sh', track_memory_file])
+
+    name = 'Hello'
+
+    tool = args.tool
+    output_mode = args.output_mode
+    lib = None
+    if output_mode == 'dex':
+      name += 'Dex'
+      lib = utils.get_android_jar(28)
+    else:
+      lib = utils.RT_JAR
+
+    extra = []
+    if args.large:
+      name += 'Large'
+      extra = EXTRA_INPUTS
+
+    if args.noopt:
+      name += 'NoOpt'
+
+    cmds = Compile(
+      tool,
+      output_mode,
+      lib,
+      extra,
+      output_dir,
+      args.noopt,
+      temp_dir,
+    )
+
+    t0 = time.time()
+    for cmd in cmds:
+      fullcmd = cmd_prefix + cmd
+      utils.PrintCmd(fullcmd)
+      subprocess.check_call(fullcmd)
+    dt = time.time() - t0
+
+    if args.print_memoryuse:
+      print('{}(MemoryUse): {}'
+            .format(name, utils.grep_memoryuse(track_memory_file)))
+
+    print('{}(RunTimeRaw): {} ms'
+          .format(name, 1000.0 * dt))
+
+if __name__ == '__main__':
+  sys.exit(Main())
diff --git a/tools/utils.py b/tools/utils.py
index 0b7684d..4e658cc 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -4,9 +4,7 @@
 
 # Different utility functions used accross scripts
 
-import defines
 import hashlib
-import jdk
 import os
 import re
 import shutil
@@ -16,6 +14,9 @@
 import tempfile
 import zipfile
 
+import defines
+import jdk
+
 ANDROID_JAR_DIR = 'third_party/android_jar/lib-v{api}'
 ANDROID_JAR = os.path.join(ANDROID_JAR_DIR, 'android.jar')
 TOOLS_DIR = defines.TOOLS_DIR
@@ -63,6 +64,12 @@
     'proguard6.0.1',
     'lib',
     'retrace.jar')
+PROGUARD_JAR = os.path.join(
+    THIRD_PARTY,
+    'proguard',
+    'proguard6.0.1',
+    'lib',
+    'proguard.jar')
 CF_SEGMENTS_TOOL = os.path.join(THIRD_PARTY, 'cf_segments')
 PINNED_R8_JAR = os.path.join(REPO_ROOT, 'third_party/r8/r8.jar')
 PINNED_PGR8_JAR = os.path.join(REPO_ROOT, 'third_party/r8/r8-pg6.0.1.jar')