Benchmark for compiling a trivial hello world program.
Change-Id: Idac927a5fadb005a218cf7b7ae53345496b0ef90
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())