aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Duffin <paulduffin@google.com>2015-11-23 20:52:41 +0000
committerandroid-build-merger <android-build-merger@google.com>2015-11-23 20:52:41 +0000
commit662d147a88551c506b185cdde2bb15fb14d78b88 (patch)
treebf446b297661a7afaeef97d54243269204597781
parent9060798c8673ba69f4f4dd5e0c8fc848cd793dfa (diff)
parent7fc0b45feb5ac6191547bb0a175a718cb41ec4f9 (diff)
downloadcaliper-662d147a88551c506b185cdde2bb15fb14d78b88.tar.gz
Added version 0.5-rc1
am: 7fc0b45feb * commit '7fc0b45feb5ac6191547bb0a175a718cb41ec4f9': Added version 0.5-rc1 Initial empty repository
-rw-r--r--Android.mk43
-rw-r--r--COPYING202
-rw-r--r--README8
-rw-r--r--caliper/pom.xml134
-rw-r--r--caliper/src/main/java/com/google/caliper/AllocationMeasurer.java165
-rw-r--r--caliper/src/main/java/com/google/caliper/Arguments.java420
-rw-r--r--caliper/src/main/java/com/google/caliper/Benchmark.java55
-rw-r--r--caliper/src/main/java/com/google/caliper/CaliperRc.java63
-rw-r--r--caliper/src/main/java/com/google/caliper/ConfigurationException.java33
-rw-r--r--caliper/src/main/java/com/google/caliper/ConfiguredBenchmark.java67
-rw-r--r--caliper/src/main/java/com/google/caliper/ConsoleReport.java427
-rw-r--r--caliper/src/main/java/com/google/caliper/CountingPrintStream.java77
-rw-r--r--caliper/src/main/java/com/google/caliper/DalvikVm.java62
-rw-r--r--caliper/src/main/java/com/google/caliper/DebugMeasurer.java42
-rw-r--r--caliper/src/main/java/com/google/caliper/Environment.java63
-rw-r--r--caliper/src/main/java/com/google/caliper/EnvironmentGetter.java178
-rw-r--r--caliper/src/main/java/com/google/caliper/InProcessRunner.java117
-rw-r--r--caliper/src/main/java/com/google/caliper/InstancesAllocationMeasurer.java34
-rw-r--r--caliper/src/main/java/com/google/caliper/Json.java226
-rw-r--r--caliper/src/main/java/com/google/caliper/LogConstants.java43
-rw-r--r--caliper/src/main/java/com/google/caliper/Measurement.java93
-rw-r--r--caliper/src/main/java/com/google/caliper/MeasurementSet.java264
-rw-r--r--caliper/src/main/java/com/google/caliper/MeasurementType.java24
-rw-r--r--caliper/src/main/java/com/google/caliper/Measurer.java44
-rw-r--r--caliper/src/main/java/com/google/caliper/MemoryAllocationMeasurer.java34
-rw-r--r--caliper/src/main/java/com/google/caliper/Param.java61
-rw-r--r--caliper/src/main/java/com/google/caliper/Parameter.java202
-rw-r--r--caliper/src/main/java/com/google/caliper/Result.java56
-rw-r--r--caliper/src/main/java/com/google/caliper/ResultsReader.java64
-rw-r--r--caliper/src/main/java/com/google/caliper/Run.java93
-rw-r--r--caliper/src/main/java/com/google/caliper/Runner.java438
-rw-r--r--caliper/src/main/java/com/google/caliper/Scenario.java80
-rw-r--r--caliper/src/main/java/com/google/caliper/ScenarioResult.java88
-rw-r--r--caliper/src/main/java/com/google/caliper/ScenarioSelection.java253
-rw-r--r--caliper/src/main/java/com/google/caliper/SimpleBenchmark.java227
-rw-r--r--caliper/src/main/java/com/google/caliper/StandardVm.java46
-rw-r--r--caliper/src/main/java/com/google/caliper/TimeMeasurer.java180
-rw-r--r--caliper/src/main/java/com/google/caliper/TypeConverter.java65
-rw-r--r--caliper/src/main/java/com/google/caliper/UploadResults.java28
-rw-r--r--caliper/src/main/java/com/google/caliper/UserException.java196
-rw-r--r--caliper/src/main/java/com/google/caliper/Vm.java46
-rw-r--r--caliper/src/main/java/com/google/caliper/VmFactory.java47
-rw-r--r--caliper/src/main/java/com/google/caliper/Xml.java160
-rw-r--r--caliper/src/main/java/com/google/caliper/XmlUtils.java49
-rw-r--r--caliper/src/main/java/com/google/caliper/util/InterleavedReader.java128
-rw-r--r--caliper/src/main/java/com/google/caliper/util/LinearTranslation.java48
-rw-r--r--caliper/src/main/resources/CaliperCore.gwt.xml15
-rw-r--r--caliper/src/test/java/com/google/caliper/AllTests.java33
-rw-r--r--caliper/src/test/java/com/google/caliper/CaliperTest.java93
-rw-r--r--caliper/src/test/java/com/google/caliper/JsonTest.java67
-rw-r--r--caliper/src/test/java/com/google/caliper/MeasurementSetTest.java216
-rw-r--r--caliper/src/test/java/com/google/caliper/ParameterTest.java71
-rw-r--r--caliper/src/test/java/com/google/caliper/WarmupOverflowTest.java81
-rw-r--r--examples/pom.xml88
-rw-r--r--examples/src/main/java/examples/ArraySortBenchmark.java102
-rw-r--r--examples/src/main/java/examples/BitSetBenchmark.java186
-rw-r--r--examples/src/main/java/examples/CharacterBenchmark.java300
-rw-r--r--examples/src/main/java/examples/CompressionSizeBenchmark.java90
-rw-r--r--examples/src/main/java/examples/ContainsBenchmark.java78
-rw-r--r--examples/src/main/java/examples/CopyArrayBenchmark.java356
-rw-r--r--examples/src/main/java/examples/DemoBenchmark.java74
-rw-r--r--examples/src/main/java/examples/DoubleToStringBenchmark.java107
-rw-r--r--examples/src/main/java/examples/DoubleToStringBenchmark2.java111
-rw-r--r--examples/src/main/java/examples/EnumSetContainsBenchmark.java92
-rw-r--r--examples/src/main/java/examples/ExpensiveObjectsBenchmark.java76
-rw-r--r--examples/src/main/java/examples/FormatterBenchmark.java76
-rw-r--r--examples/src/main/java/examples/IntModBenchmark.java94
-rw-r--r--examples/src/main/java/examples/ListIterationBenchmark.java73
-rw-r--r--examples/src/main/java/examples/LoopingBackwardsBenchmark.java52
-rw-r--r--examples/src/main/java/examples/MessageDigestCreationBenchmark.java44
-rw-r--r--examples/src/main/java/examples/StringBuilderBenchmark.java132
-rw-r--r--lib/gson-1.7.1.jarbin0 -> 173590 bytes
-rw-r--r--lib/java-allocation-instrumenter-2.0.jarbin0 -> 20567 bytes
-rw-r--r--scripts/caliper9
-rw-r--r--tutorial/Tutorial.java187
75 files changed, 8376 insertions, 0 deletions
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..c747764
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,43 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+# build caliper jar
+# ============================================================
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := caliper-target
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := JAVA_LIBRARIES
+LOCAL_SRC_FILES := $(call all-java-files-under, caliper/src/main/java/)
+LOCAL_JAVA_RESOURCE_DIRS := caliper/src/main/resources
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ caliper-gson \
+ caliper-java-allocation-instrumenter \
+ guava
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# Build dependencies.
+# ============================================================
+include $(CLEAR_VARS)
+
+LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := \
+ caliper-gson:lib/gson-1.7.1$(COMMON_JAVA_PACKAGE_SUFFIX) \
+ caliper-java-allocation-instrumenter:lib/java-allocation-instrumenter-2.0$(COMMON_JAVA_PACKAGE_SUFFIX)
+
+include $(BUILD_MULTI_PREBUILT)
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/README b/README
new file mode 100644
index 0000000..c54683c
--- /dev/null
+++ b/README
@@ -0,0 +1,8 @@
+To build this project with Maven:
+
+1. cd caliper
+mvn eclipse:configure-workspace eclipse:eclipse install
+
+2. To build examples
+cd examples
+mvn eclipse:configure-workspace eclipse:eclipse install
diff --git a/caliper/pom.xml b/caliper/pom.xml
new file mode 100644
index 0000000..befd583
--- /dev/null
+++ b/caliper/pom.xml
@@ -0,0 +1,134 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>com.google.caliper</groupId>
+ <artifactId>caliper</artifactId>
+ <packaging>jar</packaging>
+ <version>0.5-rc1</version>
+ <inceptionYear>2009</inceptionYear>
+ <name>caliper</name>
+ <parent>
+ <groupId>org.sonatype.oss</groupId>
+ <artifactId>oss-parent</artifactId>
+ <version>7</version>
+ </parent>
+ <url>http://code.google.com/p/caliper/</url>
+ <description>Caliper: Microbenchmarking Framework for Java</description>
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ </properties>
+ <licenses>
+ <license>
+ <name>The Apache Software License, Version 2.0</name>
+ <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+ <distribution>repo</distribution>
+ </license>
+ </licenses>
+ <scm>
+ <connection>scm:git:http://code.google.com/p/caliper/</connection>
+ <developerConnection>scm:git:http://code.google.com/p/caliper/</developerConnection>
+ <url>http://code.google.com/p/caliper/source/browse</url>
+ </scm>
+ <issueManagement>
+ <system>Google Code Issue Tracking</system>
+ <url>http://code.google.com/p/caliper/issues/list</url>
+ </issueManagement>
+ <organization>
+ <name>Google, Inc.</name>
+ <url>http://www.google.com</url>
+ </organization>
+ <dependencies>
+ <dependency>
+ <groupId>com.google.code.findbugs</groupId>
+ <artifactId>jsr305</artifactId>
+ <version>1.3.9</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.code.gson</groupId>
+ <artifactId>gson</artifactId>
+ <version>1.7.1</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <version>11.0.1</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.code.java-allocation-instrumenter</groupId>
+ <artifactId>java-allocation-instrumenter</artifactId>
+ <version>2.0</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>3.8.2</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <defaultGoal>package</defaultGoal>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>2.3.2</version>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-eclipse-plugin</artifactId>
+ <version>2.8</version>
+ <configuration>
+ <downloadSources>true</downloadSources>
+ <downloadJavadocs>true</downloadJavadocs>
+ <workspace>../eclipse-ws/</workspace>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ <version>2.1.2</version>
+ <executions>
+ <execution>
+ <id>attach-sources</id>
+ <phase>verify</phase>
+ <goals>
+ <goal>jar-no-fork</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <version>2.7</version>
+ <executions>
+ <execution>
+ <id>generate-javadocs</id>
+ <phase>site</phase>
+ <goals>
+ <goal>javadoc</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <links>
+ <link>http://download.oracle.com/javase/1.5.0/docs/api/</link>
+ </links>
+ <version>true</version>
+ <show>public</show>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <developers>
+ <developer>
+ <name>Jesse Wilson</name>
+ <organization>Google Inc.</organization>
+ </developer>
+ </developers>
+</project>
diff --git a/caliper/src/main/java/com/google/caliper/AllocationMeasurer.java b/caliper/src/main/java/com/google/caliper/AllocationMeasurer.java
new file mode 100644
index 0000000..2ec3488
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/AllocationMeasurer.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import com.google.caliper.UserException.NonConstantMemoryUsage;
+import com.google.common.base.Supplier;
+import com.google.monitoring.runtime.instrumentation.AllocationRecorder;
+import com.google.monitoring.runtime.instrumentation.Sampler;
+
+public abstract class AllocationMeasurer extends Measurer {
+
+ protected static final int ALLOCATION_DISPLAY_THRESHOLD = 50;
+
+ private boolean log;
+ private long tempAllocationCount;
+ private long allocationsToIgnore;
+ private long numberOfAllocations;
+ private long allocationCount;
+ private long outOfThreadAllocationCount;
+ private boolean recordAllocations;
+ protected String type;
+
+ protected AllocationMeasurer() {
+ log = false;
+ allocationsToIgnore = 0;
+ numberOfAllocations = 0;
+ allocationCount = 0;
+ outOfThreadAllocationCount = 0;
+ recordAllocations = false;
+
+ final Thread allocatingThread = Thread.currentThread();
+ AllocationRecorder.addSampler(new Sampler() {
+ // allocated {@code newObj} of type {@code desc}, whose size is {@code size}.
+ // if this was not an array, {@code count} is -1. If it was array, {@code count} is the
+ // size of the array.
+ @Override public void sampleAllocation(int count, String desc, Object newObj, long size) {
+ if (recordAllocations) {
+ if (Thread.currentThread().equals(allocatingThread)) {
+ if (log) {
+ logAllocation(count, desc, size);
+ } else if (numberOfAllocations == 0) {
+ log("see first run for list of allocations");
+ }
+ allocationCount = incrementAllocationCount(allocationCount, count, size);
+ tempAllocationCount++;
+ numberOfAllocations++;
+ } else {
+ outOfThreadAllocationCount = incrementAllocationCount(outOfThreadAllocationCount, count, size);
+ numberOfAllocations++;
+ }
+ }
+ }
+ });
+ }
+
+ protected abstract long incrementAllocationCount(long orig, int count, long size);
+
+ private void logAllocation(int count, String desc, long size) {
+ if (numberOfAllocations >= allocationsToIgnore) {
+ if (numberOfAllocations < ALLOCATION_DISPLAY_THRESHOLD + allocationsToIgnore) {
+ log("allocating " + desc + (count == -1 ? "" : " array with " + count + " elements")
+ + " with size " + size + " bytes");
+ } else if (numberOfAllocations == ALLOCATION_DISPLAY_THRESHOLD + allocationsToIgnore) {
+ log("...more allocations...");
+ }
+ }
+ }
+
+ @Override public MeasurementSet run(Supplier<ConfiguredBenchmark> testSupplier) throws Exception {
+
+ // warm up, for some reason the very first time anything is measured, it will have a few more
+ // allocations.
+ measureAllocations(testSupplier.get(), 1, 0);
+
+ // The "one" case serves as a base line. There may be caching, lazy loading, etc going on here.
+ tempAllocationCount = 0; // count the number of times the sampler is called in one rep
+ long one = measureAllocationsTotal(testSupplier.get(), 1);
+ long oneAllocations = tempAllocationCount;
+
+ // we expect that the delta between any two consecutive reps will be constant
+ tempAllocationCount = 0; // count the number of times the sampler is called in two reps
+ long two = measureAllocationsTotal(testSupplier.get(), 2);
+ long twoAllocations = tempAllocationCount;
+ long expectedDiff = two - one;
+ // there is some overhead on the first call that we can ignore for the purposes of measurement
+ long unitsToIgnore = one - expectedDiff;
+ allocationsToIgnore = 2 * oneAllocations - twoAllocations;
+ log("ignoring " + allocationsToIgnore + " allocation(s) per measurement as overhead");
+
+ Measurement[] allocationMeasurements = new Measurement[4];
+ log = true;
+ allocationMeasurements[0] = measureAllocations(testSupplier.get(), 1, unitsToIgnore);
+ log = false;
+ for (int i = 1; i < allocationMeasurements.length; i++) {
+ allocationMeasurements[i] =
+ measureAllocations(testSupplier.get(), i + 1, unitsToIgnore);
+ if (Math.round(allocationMeasurements[i].getRaw()) != expectedDiff) {
+ throw new NonConstantMemoryUsage();
+ }
+ }
+
+ // The above logic guarantees that all the measurements are equal, so we only need to return a
+ // single measurement.
+ allocationsToIgnore = 0;
+ return new MeasurementSet(allocationMeasurements[0]);
+ }
+
+ private Measurement measureAllocations(ConfiguredBenchmark benchmark, int reps, long toIgnore)
+ throws Exception {
+ prepareForTest();
+ log(LogConstants.MEASURED_SECTION_STARTING);
+ resetAllocations();
+ recordAllocations = true;
+ benchmark.run(reps);
+ recordAllocations = false;
+ log(LogConstants.MEASURED_SECTION_DONE);
+ long allocations = (allocationCount - toIgnore) / reps;
+ long outOfThreadAllocations = outOfThreadAllocationCount;
+ log(allocations + " " + type + "(s) allocated per rep");
+ log(outOfThreadAllocations + " out of thread " + type + "(s) allocated in " + reps + " reps");
+ benchmark.close();
+ return getMeasurement(benchmark, allocations);
+ }
+
+ protected abstract Measurement getMeasurement(ConfiguredBenchmark benchmark, long allocations);
+
+ private long measureAllocationsTotal(ConfiguredBenchmark benchmark, int reps)
+ throws Exception {
+ prepareForTest();
+ log(LogConstants.MEASURED_SECTION_STARTING);
+ resetAllocations();
+ recordAllocations = true;
+ benchmark.run(reps);
+ recordAllocations = false;
+ log(LogConstants.MEASURED_SECTION_DONE);
+ long allocations = allocationCount;
+ long outOfThreadAllocations = outOfThreadAllocationCount;
+ log(allocations + " " + type + "(s) allocated in " + reps + " reps");
+ if (outOfThreadAllocations > 0) {
+ log(outOfThreadAllocations + " out of thread " + type + "(s) allocated in " + reps + " reps");
+ }
+ benchmark.close();
+ return allocations;
+ }
+
+ private void resetAllocations() {
+ allocationCount = 0;
+ outOfThreadAllocationCount = 0;
+ numberOfAllocations = 0;
+ }
+}
diff --git a/caliper/src/main/java/com/google/caliper/Arguments.java b/caliper/src/main/java/com/google/caliper/Arguments.java
new file mode 100644
index 0000000..cceb079
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/Arguments.java
@@ -0,0 +1,420 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import com.google.caliper.UserException.DisplayUsageException;
+import com.google.caliper.UserException.IncompatibleArgumentsException;
+import com.google.caliper.UserException.InvalidParameterValueException;
+import com.google.caliper.UserException.MultipleBenchmarkClassesException;
+import com.google.caliper.UserException.NoBenchmarkClassException;
+import com.google.caliper.UserException.UnrecognizedOptionException;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Iterators;
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+
+import java.io.File;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Parse command line arguments for the runner and in-process runner.
+ */
+public final class Arguments {
+ private String suiteClassName;
+
+ /** JVMs to run in the benchmark */
+ private final Set<String> userVms = Sets.newLinkedHashSet();
+
+ /**
+ * Parameter values specified by the user on the command line. Parameters with
+ * no value in this multimap will get their values from the benchmark suite.
+ */
+ private final Multimap<String, String> userParameters = LinkedHashMultimap.create();
+
+ /**
+ * VM parameters like {memory=[-Xmx64,-Xmx128],jit=[-client,-server]}
+ */
+ private final Multimap<String, String> vmParameters = LinkedHashMultimap.create();
+
+ private int trials = 1;
+ private long warmupMillis = 3000;
+ private long runMillis = 1000;
+ private String timeUnit = null;
+ private String instanceUnit = null;
+ private String memoryUnit = null;
+ private File saveResultsFile = null;
+ private File uploadResultsFile = null;
+ private boolean captureVmLog = false;
+ private boolean printScore = false;
+ private boolean measureMemory = false;
+ private boolean debug = false;
+ private int debugReps = defaultDebugReps;
+ private MeasurementType measurementType;
+ private MeasurementType primaryMeasurementType;
+
+ /**
+ * A signal that indicates a JSON object interleaved with the process output.
+ * Should be short, but unlikely to show up in the processes natural output.
+ */
+ private String marker = "//ZxJ/";
+
+ private static final String defaultDelimiter = ",";
+ private static final int defaultDebugReps = 1000;
+
+ public String getSuiteClassName() {
+ return suiteClassName;
+ }
+
+ public Set<String> getUserVms() {
+ return userVms;
+ }
+
+ public int getTrials() {
+ return trials;
+ }
+
+ public Multimap<String, String> getVmParameters() {
+ return vmParameters;
+ }
+
+ public Multimap<String, String> getUserParameters() {
+ return userParameters;
+ }
+
+ public long getWarmupMillis() {
+ return warmupMillis;
+ }
+
+ public long getRunMillis() {
+ return runMillis;
+ }
+
+ public String getTimeUnit() {
+ return timeUnit;
+ }
+
+ public String getInstanceUnit() {
+ return instanceUnit;
+ }
+
+ public String getMemoryUnit() {
+ return memoryUnit;
+ }
+
+ public File getSaveResultsFile() {
+ return saveResultsFile;
+ }
+
+ public File getUploadResultsFile() {
+ return uploadResultsFile;
+ }
+
+ public boolean getCaptureVmLog() {
+ return captureVmLog;
+ }
+
+ public boolean printScore() {
+ return printScore;
+ }
+
+ public boolean getMeasureMemory() {
+ return measureMemory;
+ }
+
+ public MeasurementType getMeasurementType() {
+ return measurementType;
+ }
+
+ public MeasurementType getPrimaryMeasurementType() {
+ return primaryMeasurementType;
+ }
+
+ public boolean getDebug() {
+ return debug;
+ }
+
+ public int getDebugReps() {
+ return debugReps;
+ }
+
+ public String getMarker() {
+ return marker;
+ }
+
+ public static Arguments parse(String[] argsArray) {
+ Arguments result = new Arguments();
+
+ Iterator<String> args = Iterators.forArray(argsArray);
+ String delimiter = defaultDelimiter;
+ Map<String, String> userParameterStrings = Maps.newLinkedHashMap();
+ Map<String, String> vmParameterStrings = Maps.newLinkedHashMap();
+ String vmString = null;
+ boolean standardRun = false;
+ while (args.hasNext()) {
+ String arg = args.next();
+
+ if ("--help".equals(arg)) {
+ throw new DisplayUsageException();
+ }
+
+ if (arg.startsWith("-D") || arg.startsWith("-J")) {
+
+ /*
+ * Handle user parameters (-D) and VM parameters (-J) of these forms:
+ *
+ * -Dlength=100
+ * -Jmemory=-Xmx1024M
+ * -Dlength=100,200
+ * -Jmemory=-Xmx1024M,-Xmx2048M
+ * -Dlength 100
+ * -Jmemory -Xmx1024M
+ * -Dlength 100,200
+ * -Jmemory -Xmx1024M,-Xmx2048M
+ */
+
+ String name;
+ String value;
+ int equalsSign = arg.indexOf('=');
+ if (equalsSign == -1) {
+ name = arg.substring(2);
+ value = args.next();
+ } else {
+ name = arg.substring(2, equalsSign);
+ value = arg.substring(equalsSign + 1);
+ }
+
+ String previousValue;
+ if (arg.startsWith("-D")) {
+ previousValue = userParameterStrings.put(name, value);
+ } else {
+ previousValue = vmParameterStrings.put(name, value);
+ }
+ if (previousValue != null) {
+ throw new UserException.DuplicateParameterException(arg);
+ }
+ standardRun = true;
+ // TODO: move warmup/run to caliperrc
+ } else if ("--captureVmLog".equals(arg)) {
+ result.captureVmLog = true;
+ standardRun = true;
+ } else if ("--warmupMillis".equals(arg)) {
+ result.warmupMillis = Long.parseLong(args.next());
+ standardRun = true;
+ } else if ("--runMillis".equals(arg)) {
+ result.runMillis = Long.parseLong(args.next());
+ standardRun = true;
+ } else if ("--trials".equals(arg)) {
+ String value = args.next();
+ try {
+ result.trials = Integer.parseInt(value);
+ if (result.trials < 1) {
+ throw new UserException.InvalidTrialsException(value);
+ }
+ } catch (NumberFormatException e) {
+ throw new UserException.InvalidTrialsException(value);
+ }
+ standardRun = true;
+ } else if ("--vm".equals(arg)) {
+ if (vmString != null) {
+ throw new UserException.DuplicateParameterException(arg);
+ }
+ vmString = args.next();
+ standardRun = true;
+ } else if ("--delimiter".equals(arg)) {
+ delimiter = args.next();
+ standardRun = true;
+ } else if ("--timeUnit".equals(arg)) {
+ result.timeUnit = args.next();
+ standardRun = true;
+ } else if ("--instanceUnit".equals(arg)) {
+ result.instanceUnit = args.next();
+ standardRun = true;
+ } else if ("--memoryUnit".equals(arg)) {
+ result.memoryUnit = args.next();
+ standardRun = true;
+ } else if ("--saveResults".equals(arg) || "--xmlSave".equals(arg)) {
+ // TODO: unsupport legacy --xmlSave
+ result.saveResultsFile = new File(args.next());
+ standardRun = true;
+ } else if ("--uploadResults".equals(arg)) {
+ result.uploadResultsFile = new File(args.next());
+ } else if ("--printScore".equals(arg)) {
+ result.printScore = true;
+ standardRun = true;
+ } else if ("--measureMemory".equals(arg)) {
+ result.measureMemory = true;
+ standardRun = true;
+ } else if ("--debug".equals(arg)) {
+ result.debug = true;
+ } else if ("--debug-reps".equals(arg)) {
+ String value = args.next();
+ try {
+ result.debugReps = Integer.parseInt(value);
+ if (result.debugReps < 1) {
+ throw new UserException.InvalidDebugRepsException(value);
+ }
+ } catch (NumberFormatException e) {
+ throw new UserException.InvalidDebugRepsException(value);
+ }
+ } else if ("--marker".equals(arg)) {
+ result.marker = args.next();
+ } else if ("--measurementType".equals(arg)) {
+ String measurementType = args.next();
+ try {
+ result.measurementType = MeasurementType.valueOf(measurementType);
+ } catch (Exception e) {
+ throw new InvalidParameterValueException(arg, measurementType);
+ }
+ standardRun = true;
+ } else if ("--primaryMeasurementType".equals(arg)) {
+ String measurementType = args.next().toUpperCase();
+ try {
+ result.primaryMeasurementType = MeasurementType.valueOf(measurementType);
+ } catch (Exception e) {
+ throw new InvalidParameterValueException(arg, measurementType);
+ }
+ standardRun = true;
+ } else if (arg.startsWith("-")) {
+ throw new UnrecognizedOptionException(arg);
+
+ } else {
+ if (result.suiteClassName != null) {
+ throw new MultipleBenchmarkClassesException(result.suiteClassName, arg);
+ }
+ result.suiteClassName = arg;
+ }
+ }
+
+ Splitter delimiterSplitter = Splitter.on(delimiter);
+
+ if (vmString != null) {
+ Iterables.addAll(result.userVms, delimiterSplitter.split(vmString));
+ }
+
+ Set<String> duplicates = Sets.intersection(
+ userParameterStrings.keySet(), vmParameterStrings.keySet());
+ if (!duplicates.isEmpty()) {
+ throw new UserException.DuplicateParameterException(duplicates);
+ }
+
+ for (Map.Entry<String, String> entry : userParameterStrings.entrySet()) {
+ result.userParameters.putAll(entry.getKey(), delimiterSplitter.split(entry.getValue()));
+ }
+ for (Map.Entry<String, String> entry : vmParameterStrings.entrySet()) {
+ result.vmParameters.putAll(entry.getKey(), delimiterSplitter.split(entry.getValue()));
+ }
+
+ if (standardRun && result.uploadResultsFile != null) {
+ throw new IncompatibleArgumentsException("--uploadResults");
+ }
+
+ if (result.suiteClassName == null && result.uploadResultsFile == null) {
+ throw new NoBenchmarkClassException();
+ }
+
+ if (result.primaryMeasurementType != null
+ && result.primaryMeasurementType != MeasurementType.TIME && !result.measureMemory) {
+ throw new IncompatibleArgumentsException(
+ "--primaryMeasurementType " + result.primaryMeasurementType.toString().toLowerCase());
+ }
+
+ return result;
+ }
+
+ public static void printUsage() {
+ System.out.println();
+ System.out.println("Usage: Runner [OPTIONS...] <benchmark>");
+ System.out.println();
+ System.out.println(" <benchmark>: a benchmark class or suite");
+ System.out.println();
+ System.out.println("OPTIONS");
+ System.out.println();
+ System.out.println(" -D<param>=<value>: fix a benchmark parameter to a given value.");
+ System.out.println(" Multiple values can be supplied by separating them with the");
+ System.out.println(" delimiter specified in the --delimiter argument.");
+ System.out.println();
+ System.out.println(" For example: \"-Dfoo=bar,baz,bat\"");
+ System.out.println();
+ System.out.println(" \"benchmark\" is a special parameter that can be used to specify");
+ System.out.println(" which benchmark methods to run. For example, if a benchmark has");
+ System.out.println(" the method \"timeFoo\", it can be run alone by using");
+ System.out.println(" \"-Dbenchmark=Foo\". \"benchmark\" also accepts a delimiter");
+ System.out.println(" separated list of methods to run.");
+ System.out.println();
+ System.out.println(" -J<param>=<value>: set a JVM argument to the given value.");
+ System.out.println(" Multiple values can be supplied by separating them with the");
+ System.out.println(" delimiter specified in the --delimiter argument.");
+ System.out.println();
+ System.out.println(" For example: \"-JmemoryMax=-Xmx32M,-Xmx512M\"");
+ System.out.println();
+ System.out.println(" --delimiter <delimiter>: character or string to use as a delimiter");
+ System.out.println(" for parameter and vm values.");
+ System.out.println(" Default: \"" + defaultDelimiter + "\"");
+ System.out.println();
+ System.out.println(" --warmupMillis <millis>: duration to warmup each benchmark");
+ System.out.println();
+ System.out.println(" --runMillis <millis>: duration to execute each benchmark");
+ System.out.println();
+ System.out.println(" --captureVmLog: record the VM's just-in-time compiler and GC logs.");
+ System.out.println(" This may slow down or break benchmark display tools.");
+ System.out.println();
+ System.out.println(" --measureMemory: measure the number of allocations done and the amount of");
+ System.out.println(" memory used by invocations of the benchmark.");
+ System.out.println(" Default: off");
+ System.out.println();
+ System.out.println(" --vm <vm>: executable to test benchmark on. Multiple VMs may be passed");
+ System.out.println(" in as a list separated by the delimiter specified in the");
+ System.out.println(" --delimiter argument.");
+ System.out.println();
+ System.out.println(" --timeUnit <unit>: unit of time to use for result. Depends on the units");
+ System.out.println(" defined in the benchmark's getTimeUnitNames() method, if defined.");
+ System.out.println(" Default Options: ns, us, ms, s");
+ System.out.println();
+ System.out.println(" --instanceUnit <unit>: unit to use for allocation instances result.");
+ System.out.println(" Depends on the units defined in the benchmark's");
+ System.out.println(" getInstanceUnitNames() method, if defined.");
+ System.out.println(" Default Options: instances, K instances, M instances, B instances");
+ System.out.println();
+ System.out.println(" --memoryUnit <unit>: unit to use for allocation memory size result.");
+ System.out.println(" Depends on the units defined in the benchmark's");
+ System.out.println(" getMemoryUnitNames() method, if defined.");
+ System.out.println(" Default Options: B, KB, MB, GB");
+ System.out.println();
+ System.out.println(" --saveResults <file/dir>: write results to this file or directory");
+ System.out.println();
+ System.out.println(" --printScore: if present, also display an aggregate score for this run,");
+ System.out.println(" where higher is better. This number has no particular meaning,");
+ System.out.println(" but can be compared to scores from other runs that use the exact");
+ System.out.println(" same arguments.");
+ System.out.println();
+ System.out.println(" --uploadResults <file/dir>: upload this file or directory of files");
+ System.out.println(" to the web app. This argument ends Caliper early and is thus");
+ System.out.println(" incompatible with all other arguments.");
+ System.out.println();
+ System.out.println(" --debug: run without measurement for use with debugger or profiling.");
+ System.out.println();
+ System.out.println(" --debug-reps: fixed number of reps to run with --debug.");
+ System.out.println(" Default: \"" + defaultDebugReps + "\"");
+
+ // adding new options? don't forget to update executeForked()
+ }
+}
diff --git a/caliper/src/main/java/com/google/caliper/Benchmark.java b/caliper/src/main/java/com/google/caliper/Benchmark.java
new file mode 100644
index 0000000..06bda82
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/Benchmark.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A collection of benchmarks that share a set of configuration parameters.
+ */
+public interface Benchmark {
+
+ Set<String> parameterNames();
+
+ Set<String> parameterValues(String parameterName);
+
+ ConfiguredBenchmark createBenchmark(Map<String, String> parameterValues);
+
+ /**
+ * A mapping of units to their values. Their values must be integers, but all values are relative,
+ * so if one unit is 1.5 times the size of another, then these units can be expressed as
+ * {"unit1"=10,"unit2"=15}. The smallest unit given by the function will be used to display
+ * immediate results when running at the command line.
+ *
+ * e.g. 0% Scenario{...} 16.08<SMALLEST-UNIT>; σ=1.72<SMALLEST-UNIT> @ 3 trials
+ */
+ Map<String, Integer> getTimeUnitNames();
+
+ Map<String, Integer> getInstanceUnitNames();
+
+ Map<String, Integer> getMemoryUnitNames();
+
+ /**
+ * Converts nanoseconds to the smallest unit defined in {@link #getTimeUnitNames()}.
+ */
+ double nanosToUnits(double nanos);
+
+ double instancesToUnits(long instances);
+
+ double bytesToUnits(long bytes);
+} \ No newline at end of file
diff --git a/caliper/src/main/java/com/google/caliper/CaliperRc.java b/caliper/src/main/java/com/google/caliper/CaliperRc.java
new file mode 100644
index 0000000..21a283f
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/CaliperRc.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+public class CaliperRc {
+ public static final CaliperRc INSTANCE = new CaliperRc();
+
+ private final Properties properties = new Properties();
+
+ private CaliperRc() {
+ try {
+ String caliperRcEnvVar = System.getenv("CALIPERRC");
+ File caliperRcFile = (caliperRcEnvVar == null)
+ ? new File(System.getProperty("user.home"), ".caliperrc")
+ : new File(caliperRcEnvVar);
+ if (caliperRcFile.exists()) {
+ InputStream in = new FileInputStream(caliperRcFile);
+ properties.load(in);
+ in.close();
+ } else {
+ // create it with a template
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public String getApiKey() {
+ return properties.getProperty("apiKey");
+ }
+
+ public String getPostUrl() {
+ return properties.getProperty("postUrl");
+ }
+
+ /**
+ * The HTTP proxy host name and port number separated by a colon, such as
+ * foo.com:8080
+ */
+ public String getProxy() {
+ return properties.getProperty("proxy");
+ }
+}
diff --git a/caliper/src/main/java/com/google/caliper/ConfigurationException.java b/caliper/src/main/java/com/google/caliper/ConfigurationException.java
new file mode 100644
index 0000000..c4a35ec
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/ConfigurationException.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+/**
+ * Thrown upon occurrence of a configuration error.
+ */
+final class ConfigurationException extends RuntimeException {
+
+ ConfigurationException(String s) {
+ super(s);
+ }
+
+ ConfigurationException(Throwable cause) {
+ super(cause);
+ }
+
+ private static final long serialVersionUID = 0;
+}
diff --git a/caliper/src/main/java/com/google/caliper/ConfiguredBenchmark.java b/caliper/src/main/java/com/google/caliper/ConfiguredBenchmark.java
new file mode 100644
index 0000000..f150ec1
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/ConfiguredBenchmark.java
@@ -0,0 +1,67 @@
+/**
+ * Copyright (C) 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import java.util.Map;
+
+public abstract class ConfiguredBenchmark {
+
+ private final Benchmark underlyingBenchmark;
+
+ protected ConfiguredBenchmark(Benchmark underlyingBenchmark) {
+ this.underlyingBenchmark = underlyingBenchmark;
+ }
+
+ /**
+ * Runs the benchmark through {@code reps} iterations.
+ *
+ * @return any object or null. Benchmark implementors may keep an accumulating
+ * value to prevent the runtime from optimizing away the code under test.
+ * Such an accumulator value can be returned here.
+ */
+ public abstract Object run(int reps) throws Exception;
+
+ public abstract void close() throws Exception;
+
+ public final Benchmark getBenchmark() {
+ return underlyingBenchmark;
+ }
+
+ public final double nanosToUnits(double nanos) {
+ return underlyingBenchmark.nanosToUnits(nanos);
+ }
+
+ public final Map<String, Integer> timeUnitNames() {
+ return underlyingBenchmark.getTimeUnitNames();
+ }
+
+ public final double instancesToUnits(long instances) {
+ return underlyingBenchmark.instancesToUnits(instances);
+ }
+
+ public final Map<String, Integer> instanceUnitNames() {
+ return underlyingBenchmark.getInstanceUnitNames();
+ }
+
+ public final double bytesToUnits(long bytes) {
+ return underlyingBenchmark.bytesToUnits(bytes);
+ }
+
+ public final Map<String, Integer> memoryUnitNames() {
+ return underlyingBenchmark.getMemoryUnitNames();
+ }
+}
diff --git a/caliper/src/main/java/com/google/caliper/ConsoleReport.java b/caliper/src/main/java/com/google/caliper/ConsoleReport.java
new file mode 100644
index 0000000..8449bda
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/ConsoleReport.java
@@ -0,0 +1,427 @@
+/**
+ * Copyright (C) 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import com.google.caliper.util.LinearTranslation;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Ordering;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Prints a report containing the tested values and the corresponding
+ * measurements. Measurements are grouped by variable using indentation.
+ * Alongside numeric values, quick-glance ascii art bar charts are printed.
+ * Sample output (this may not represent the exact form that is produced):
+ * <pre>
+ * benchmark d ns linear runtime
+ * ConcatenationBenchmark 3.14159265 4397 ========================
+ * ConcatenationBenchmark -0.0 223 ===============
+ * FormatterBenchmark 3.14159265 33999 ==============================
+ * FormatterBenchmark -0.0 26399 =============================
+ * </pre>
+ */
+final class ConsoleReport {
+
+ private static final int barGraphWidth = 30;
+
+ private static final int UNITS_FOR_SCORE_100 = 1;
+ private static final int UNITS_FOR_SCORE_10 = 1000000000; // 1 s
+
+ private static final LinearTranslation scoreTranslation =
+ new LinearTranslation(Math.log(UNITS_FOR_SCORE_10), 10,
+ Math.log(UNITS_FOR_SCORE_100), 100);
+
+ public static final Ordering<Entry<String, Integer>> UNIT_ORDERING =
+ new Ordering<Entry<String, Integer>>() {
+ @Override public int compare(Entry<String, Integer> a, Entry<String, Integer> b) {
+ return a.getValue().compareTo(b.getValue());
+ }
+ };
+
+ private final List<Variable> variables;
+ private final Run run;
+ private final List<Scenario> scenarios;
+
+ private final List<MeasurementType> orderedMeasurementTypes;
+ private final MeasurementType type;
+ private final double maxValue;
+ private final double logMinValue;
+ private final double logMaxValue;
+ private final EnumMap<MeasurementType, Integer> decimalDigitsMap =
+ new EnumMap<MeasurementType, Integer>(MeasurementType.class);
+ private final EnumMap<MeasurementType, Double> divideByMap =
+ new EnumMap<MeasurementType, Double>(MeasurementType.class);
+ private final EnumMap<MeasurementType, String> unitMap =
+ new EnumMap<MeasurementType, String>(MeasurementType.class);
+ private final EnumMap<MeasurementType, Integer> measurementColumnLengthMap =
+ new EnumMap<MeasurementType, Integer>(MeasurementType.class);
+ private boolean printScore;
+
+ ConsoleReport(Run run, Arguments arguments) {
+ this.run = run;
+ unitMap.put(MeasurementType.TIME, arguments.getTimeUnit());
+ unitMap.put(MeasurementType.INSTANCE, arguments.getInstanceUnit());
+ unitMap.put(MeasurementType.MEMORY, arguments.getMemoryUnit());
+
+ if (arguments.getMeasureMemory()) {
+ orderedMeasurementTypes = Arrays.asList(
+ MeasurementType.TIME, MeasurementType.INSTANCE, MeasurementType.MEMORY);
+ } else {
+ orderedMeasurementTypes = Arrays.asList(MeasurementType.TIME);
+ }
+
+ if (arguments.getPrimaryMeasurementType() != null) {
+ this.type = arguments.getPrimaryMeasurementType();
+ } else {
+ this.type = MeasurementType.TIME;
+ }
+
+ double min = Double.POSITIVE_INFINITY;
+ double max = 0;
+
+ Multimap<String, String> nameToValues = LinkedHashMultimap.create();
+ List<Variable> variablesBuilder = new ArrayList<Variable>();
+ for (Entry<Scenario, ScenarioResult> entry : this.run.getMeasurements().entrySet()) {
+ Scenario scenario = entry.getKey();
+ double d = entry.getValue().getMeasurementSet(type).medianUnits();
+
+ min = Math.min(min, d);
+ max = Math.max(max, d);
+
+ for (Entry<String, String> variable : scenario.getVariables().entrySet()) {
+ String name = variable.getKey();
+ nameToValues.put(name, variable.getValue());
+ }
+ }
+
+ for (Entry<String, Collection<String>> entry : nameToValues.asMap().entrySet()) {
+ Variable variable = new Variable(entry.getKey(), entry.getValue());
+ variablesBuilder.add(variable);
+ }
+
+ /*
+ * Figure out how much influence each variable has on the measured value.
+ * We sum the measurements taken with each value of each variable. For
+ * variable that have influence on the measurement, the sums will differ
+ * by value. If the variable has little influence, the sums will be similar
+ * to one another and close to the overall average. We take the standard
+ * deviation across each variable's collection of sums. Higher standard
+ * deviation implies higher influence on the measured result.
+ */
+ double sumOfAllMeasurements = 0;
+ for (ScenarioResult measurement : this.run.getMeasurements().values()) {
+ sumOfAllMeasurements += measurement.getMeasurementSet(type).medianUnits();
+ }
+ for (Variable variable : variablesBuilder) {
+ int numValues = variable.values.size();
+ double[] sumForValue = new double[numValues];
+ for (Entry<Scenario, ScenarioResult> entry
+ : this.run.getMeasurements().entrySet()) {
+ Scenario scenario = entry.getKey();
+ sumForValue[variable.index(scenario)] +=
+ entry.getValue().getMeasurementSet(type).medianUnits();
+ }
+ double mean = sumOfAllMeasurements / sumForValue.length;
+ double stdDeviationSquared = 0;
+ for (double value : sumForValue) {
+ double distance = value - mean;
+ stdDeviationSquared += distance * distance;
+ }
+ variable.stdDeviation = Math.sqrt(stdDeviationSquared / numValues);
+ }
+
+ this.variables = new StandardDeviationOrdering().reverse().sortedCopy(variablesBuilder);
+ this.scenarios = new ByVariablesOrdering().sortedCopy(this.run.getMeasurements().keySet());
+ this.maxValue = max;
+ this.logMinValue = Math.log(min);
+ this.logMaxValue = Math.log(max);
+
+ EnumMap<MeasurementType, Integer> digitsBeforeDecimalMap =
+ new EnumMap<MeasurementType, Integer>(MeasurementType.class);
+ EnumMap<MeasurementType, Integer> decimalPointMap =
+ new EnumMap<MeasurementType, Integer>(MeasurementType.class);
+ for (MeasurementType measurementType : orderedMeasurementTypes) {
+ double maxForType = 0;
+ double minForType = Double.POSITIVE_INFINITY;
+ for (Entry<Scenario, ScenarioResult> entry : this.run.getMeasurements().entrySet()) {
+ double d = entry.getValue().getMeasurementSet(measurementType).medianUnits();
+ minForType = Math.min(minForType, d);
+ maxForType = Math.max(maxForType, d);
+ }
+
+ unitMap.put(measurementType,
+ getUnit(unitMap.get(measurementType), measurementType, minForType));
+
+ divideByMap.put(measurementType,
+ (double) getUnits(measurementType).get(unitMap.get(measurementType)));
+
+ int numDigitsInMin = ceil(Math.log10(minForType));
+ decimalDigitsMap.put(measurementType,
+ ceil(Math.max(0, ceil(Math.log10(divideByMap.get(measurementType))) + 3 - numDigitsInMin)));
+
+ digitsBeforeDecimalMap.put(measurementType,
+ Math.max(1, ceil(Math.log10(maxForType / divideByMap.get(measurementType)))));
+
+ decimalPointMap.put(measurementType, decimalDigitsMap.get(measurementType) > 0 ? 1 : 0);
+
+ measurementColumnLengthMap.put(measurementType, Math.max(maxForType > 0
+ ? digitsBeforeDecimalMap.get(measurementType) + decimalPointMap.get(measurementType)
+ + decimalDigitsMap.get(measurementType)
+ : 1, unitMap.get(measurementType).trim().length()));
+ }
+
+ this.printScore = arguments.printScore();
+ }
+
+ private String getUnit(String userSuppliedUnit, MeasurementType measurementType, double min) {
+ Map<String, Integer> units = getUnits(measurementType);
+
+ if (userSuppliedUnit == null) {
+ List<Entry<String, Integer>> entries = UNIT_ORDERING.reverse().sortedCopy(units.entrySet());
+ for (Entry<String, Integer> entry : entries) {
+ if (min / entry.getValue() >= 1) {
+ return entry.getKey();
+ }
+ }
+ // if no unit works, just use the smallest available unit.
+ return entries.get(entries.size() - 1).getKey();
+ }
+
+ if (!units.keySet().contains(userSuppliedUnit)) {
+ throw new RuntimeException("\"" + unitMap.get(measurementType) + "\" is not a valid unit.");
+ }
+ return userSuppliedUnit;
+ }
+
+ private Map<String, Integer> getUnits(MeasurementType measurementType) {
+ Map<String, Integer> units = null;
+ for (Entry<Scenario, ScenarioResult> entry : run.getMeasurements().entrySet()) {
+ if (units == null) {
+ units = entry.getValue().getMeasurementSet(measurementType).getUnitNames();
+ } else {
+ if (!units.equals(entry.getValue().getMeasurementSet(measurementType).getUnitNames())) {
+ throw new RuntimeException("measurement sets for run contain multiple, incompatible unit"
+ + " sets.");
+ }
+ }
+ }
+ if (units == null) {
+ throw new RuntimeException("run has no measurements.");
+ }
+ if (units.isEmpty()) {
+ throw new RuntimeException("no units specified.");
+ }
+ return units;
+ }
+
+ /**
+ * A variable and the set of values to which it has been assigned.
+ */
+ private static class Variable {
+ final String name;
+ final ImmutableList<String> values;
+ final int maxLength;
+ double stdDeviation;
+
+ Variable(String name, Collection<String> values) {
+ this.name = name;
+ this.values = ImmutableList.copyOf(values);
+
+ int maxLen = name.length();
+ for (String value : values) {
+ maxLen = Math.max(maxLen, value.length());
+ }
+ this.maxLength = maxLen;
+ }
+
+ String get(Scenario scenario) {
+ return scenario.getVariables().get(name);
+ }
+
+ int index(Scenario scenario) {
+ return values.indexOf(get(scenario));
+ }
+
+ boolean isInteresting() {
+ return values.size() > 1;
+ }
+ }
+
+ /**
+ * Orders the different variables by their standard deviation. This results
+ * in an appropriate grouping of output values.
+ */
+ private static class StandardDeviationOrdering extends Ordering<Variable> {
+ public int compare(Variable a, Variable b) {
+ return Double.compare(a.stdDeviation, b.stdDeviation);
+ }
+ }
+
+ /**
+ * Orders scenarios by the variables.
+ */
+ private class ByVariablesOrdering extends Ordering<Scenario> {
+ public int compare(Scenario a, Scenario b) {
+ for (Variable variable : variables) {
+ int aValue = variable.values.indexOf(variable.get(a));
+ int bValue = variable.values.indexOf(variable.get(b));
+ int diff = aValue - bValue;
+ if (diff != 0) {
+ return diff;
+ }
+ }
+ return 0;
+ }
+ }
+
+ void displayResults() {
+ printValues();
+ System.out.println();
+ printUninterestingVariables();
+ printCharCounts();
+ }
+
+ private void printCharCounts() {
+ int systemOutCharCount = 0;
+ int systemErrCharCount = 0;
+ for (ScenarioResult scenarioResult : run.getMeasurements().values()) {
+ for (MeasurementType measurementType : MeasurementType.values()) {
+ MeasurementSet measurementSet = scenarioResult.getMeasurementSet(measurementType);
+ if (measurementSet != null) {
+ systemOutCharCount += measurementSet.getSystemOutCharCount();
+ systemErrCharCount += measurementSet.getSystemErrCharCount();
+ }
+ }
+ }
+ if (systemOutCharCount > 0 || systemErrCharCount > 0) {
+ System.out.println();
+ System.out.println("Note: benchmarks printed " + systemOutCharCount
+ + " characters to System.out and " + systemErrCharCount + " characters to System.err."
+ + " Use --debug to see this output.");
+ }
+ }
+
+ /**
+ * Prints a table of values.
+ */
+ private void printValues() {
+ // header
+ for (Variable variable : variables) {
+ if (variable.isInteresting()) {
+ System.out.printf("%" + variable.maxLength + "s ", variable.name);
+ }
+ }
+ // doesn't make sense to show graphs at all for 1
+ // scenario, since it leads to vacuous graphs.
+ boolean showGraphs = scenarios.size() > 1;
+
+ EnumMap<MeasurementType, String> numbersFormatMap =
+ new EnumMap<MeasurementType, String>(MeasurementType.class);
+ for (MeasurementType measurementType : orderedMeasurementTypes) {
+ if (measurementType != type) {
+ System.out.printf("%" + measurementColumnLengthMap.get(measurementType) + "s ",
+ unitMap.get(measurementType).trim());
+ }
+
+ numbersFormatMap.put(measurementType,
+ "%" + measurementColumnLengthMap.get(measurementType)
+ + "." + decimalDigitsMap.get(measurementType) + "f"
+ + (type == measurementType ? "" : " "));
+ }
+
+ System.out.printf("%" + measurementColumnLengthMap.get(type) + "s", unitMap.get(type).trim());
+ if (showGraphs) {
+ System.out.print(" linear runtime");
+ }
+ System.out.println();
+
+ double sumOfLogs = 0.0;
+
+ for (Scenario scenario : scenarios) {
+ for (Variable variable : variables) {
+ if (variable.isInteresting()) {
+ System.out.printf("%" + variable.maxLength + "s ", variable.get(scenario));
+ }
+ }
+ ScenarioResult measurement = run.getMeasurements().get(scenario);
+ sumOfLogs += Math.log(measurement.getMeasurementSet(type).medianUnits());
+
+ for (MeasurementType measurementType : orderedMeasurementTypes) {
+ if (measurementType != type) {
+ System.out.printf(numbersFormatMap.get(measurementType),
+ measurement.getMeasurementSet(measurementType).medianUnits() / divideByMap.get(measurementType));
+ }
+ }
+
+ System.out.printf(numbersFormatMap.get(type),
+ measurement.getMeasurementSet(type).medianUnits() / divideByMap.get(type));
+ if (showGraphs) {
+ System.out.printf(" %s", barGraph(measurement.getMeasurementSet(type).medianUnits()));
+ }
+ System.out.println();
+ }
+
+ if (printScore) {
+ // arithmetic mean of logs, aka log of geometric mean
+ double meanLogUnits = sumOfLogs / scenarios.size();
+ System.out.format("%nScore: %.3f%n", scoreTranslation.translate(meanLogUnits));
+ }
+ }
+
+ /**
+ * Prints variables with only one unique value.
+ */
+ private void printUninterestingVariables() {
+ for (Variable variable : variables) {
+ if (!variable.isInteresting()) {
+ System.out.println(variable.name + ": " + Iterables.getOnlyElement(variable.values));
+ }
+ }
+ }
+
+ /**
+ * Returns a string containing a bar of proportional width to the specified
+ * value.
+ */
+ private String barGraph(double value) {
+ int graphLength = floor(value / maxValue * barGraphWidth);
+ graphLength = Math.max(1, graphLength);
+ graphLength = Math.min(barGraphWidth, graphLength);
+ return Strings.repeat("=", graphLength);
+ }
+
+ @SuppressWarnings("NumericCastThatLosesPrecision")
+ private static int floor(double d) {
+ return (int) d;
+ }
+
+ @SuppressWarnings("NumericCastThatLosesPrecision")
+ private static int ceil(double d) {
+ return (int) Math.ceil(d);
+ }
+}
diff --git a/caliper/src/main/java/com/google/caliper/CountingPrintStream.java b/caliper/src/main/java/com/google/caliper/CountingPrintStream.java
new file mode 100644
index 0000000..5c19a61
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/CountingPrintStream.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import java.io.PrintStream;
+
+/**
+ * Counts how many characters were written.
+ */
+final class CountingPrintStream extends PrintStream {
+
+ private final PrintStream delegate;
+ private int count;
+
+ CountingPrintStream(PrintStream delegate) {
+ super(delegate);
+ this.delegate = delegate;
+ }
+
+ public int getCount() {
+ return count;
+ }
+
+ @Override public void flush() {
+ delegate.flush();
+ }
+
+ @Override public void close() {
+ delegate.close();
+ }
+
+ @Override public boolean checkError() {
+ return delegate.checkError();
+ }
+
+ @Override protected void setError() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override protected void clearError() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override public void write(int b) {
+ count++;
+ delegate.write(b);
+ }
+
+ @Override public void write(byte[] buffer, int offset, int length) {
+ count += length;
+ delegate.write(buffer, offset, length);
+ }
+
+ @Override public void print(char[] chars) {
+ count += chars.length;
+ delegate.print(chars);
+ }
+
+ @Override public void print(String s) {
+ count += s.length();
+ delegate.print(s);
+ }
+}
diff --git a/caliper/src/main/java/com/google/caliper/DalvikVm.java b/caliper/src/main/java/com/google/caliper/DalvikVm.java
new file mode 100644
index 0000000..f89cc28
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/DalvikVm.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import com.google.common.collect.ImmutableList;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The dalvikvm run on Android devices via the app_process executable.
+ */
+final class DalvikVm extends Vm {
+
+ public static boolean isDalvikVm() {
+ return "Dalvik".equals(System.getProperty("java.vm.name"));
+ }
+
+ public static String vmName() {
+ return "app_process";
+ }
+
+ @Override public List<String> getVmSpecificOptions(MeasurementType type, Arguments arguments) {
+ if (!arguments.getCaptureVmLog()) {
+ return ImmutableList.of();
+ }
+
+ List<String> result = new ArrayList<String>();
+ if (arguments.getCaptureVmLog()) {
+ // TODO: currently GC goes to logcat.
+ // result.add("-verbose:gc");
+ }
+ return result;
+ }
+
+ @Override public ProcessBuilder newProcessBuilder(File workingDirectory, String classPath,
+ ImmutableList<String> vmArgs, String className, ImmutableList<String> applicationArgs) {
+ ProcessBuilder result = new ProcessBuilder();
+ result.directory(workingDirectory);
+ result.command().addAll(vmArgs);
+ result.command().add("-Djava.class.path=" + classPath);
+ result.command().add(workingDirectory.getPath());
+ result.command().add(className);
+ result.command().addAll(applicationArgs);
+ return result;
+ }
+}
diff --git a/caliper/src/main/java/com/google/caliper/DebugMeasurer.java b/caliper/src/main/java/com/google/caliper/DebugMeasurer.java
new file mode 100644
index 0000000..64d4b7f
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/DebugMeasurer.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.base.Supplier;
+
+/**
+ * Measure's the benchmark's per-trial execution time.
+ */
+class DebugMeasurer extends Measurer {
+
+ private final int reps;
+
+ DebugMeasurer(int reps) {
+ checkArgument(reps > 0);
+ this.reps = reps;
+ }
+
+ @Override public MeasurementSet run(Supplier<ConfiguredBenchmark> testSupplier)
+ throws Exception {
+ ConfiguredBenchmark benchmark = testSupplier.get();
+ benchmark.run(reps);
+ benchmark.close();
+ return null;
+ }
+}
diff --git a/caliper/src/main/java/com/google/caliper/Environment.java b/caliper/src/main/java/com/google/caliper/Environment.java
new file mode 100644
index 0000000..75c2813
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/Environment.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import com.google.common.annotations.GwtCompatible;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A description of an environment in which benchmarks are run.
+ *
+ * WARNING: a JSON representation of this class is stored on the app engine server. If any changes
+ * are made to this class, a deserialization adapter must be written for this class to ensure
+ * backwards compatibility.
+ *
+ * <p>Gwt-safe
+ */
+@SuppressWarnings("serial")
+@GwtCompatible
+public final class Environment
+ implements Serializable /* for GWT Serialization */ {
+ private /*final*/ Map<String, String> propertyMap;
+
+ public Environment(Map<String, String> propertyMap) {
+ this.propertyMap = new HashMap<String, String>(propertyMap);
+ }
+
+ public Map<String, String> getProperties() {
+ return propertyMap;
+ }
+
+ @Override public boolean equals(Object o) {
+ return o instanceof Environment
+ && ((Environment) o).propertyMap.equals(propertyMap);
+ }
+
+ @Override public int hashCode() {
+ return propertyMap.hashCode();
+ }
+
+ @Override public String toString() {
+ return propertyMap.toString();
+ }
+
+ @SuppressWarnings("unused")
+ private Environment() {} // for GWT Serialization
+}
diff --git a/caliper/src/main/java/com/google/caliper/EnvironmentGetter.java b/caliper/src/main/java/com/google/caliper/EnvironmentGetter.java
new file mode 100644
index 0000000..9b7150b
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/EnvironmentGetter.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.ImmutableMultiset;
+import com.google.common.collect.Multimap;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public final class EnvironmentGetter {
+
+ public Environment getEnvironmentSnapshot() {
+ Map<String, String> propertyMap = new HashMap<String, String>();
+
+ @SuppressWarnings("unchecked")
+ Map<String, String> sysProps = (Map<String, String>) (Map) System.getProperties();
+
+ // Sometimes java.runtime.version is more descriptive than java.version
+ String version = sysProps.get("java.version");
+ String alternateVersion = sysProps.get("java.runtime.version");
+ if (alternateVersion != null && alternateVersion.length() > version.length()) {
+ version = alternateVersion;
+ }
+ propertyMap.put("jre.version", version);
+
+ propertyMap.put("jre.vmname", sysProps.get("java.vm.name"));
+ propertyMap.put("jre.vmversion", sysProps.get("java.vm.version"));
+ propertyMap.put("jre.availableProcessors",
+ Integer.toString(Runtime.getRuntime().availableProcessors()));
+
+ String osName = sysProps.get("os.name");
+ propertyMap.put("os.name", osName);
+ propertyMap.put("os.version", sysProps.get("os.version"));
+ propertyMap.put("os.arch", sysProps.get("os.arch"));
+
+ try {
+ propertyMap.put("host.name", InetAddress.getLocalHost().getHostName());
+ } catch (UnknownHostException ignored) {
+ }
+
+ if (osName.equals("Linux")) {
+ getLinuxEnvironment(propertyMap);
+ }
+
+ return new Environment(propertyMap);
+ }
+
+ private void getLinuxEnvironment(Map<String, String> propertyMap) {
+ // the following probably doesn't work on ALL linux
+ Multimap<String, String> cpuInfo = propertiesFromLinuxFile("/proc/cpuinfo");
+ propertyMap.put("host.cpus", Integer.toString(cpuInfo.get("processor").size()));
+ String s = "cpu cores";
+ propertyMap.put("host.cpu.cores", describe(cpuInfo, s));
+ propertyMap.put("host.cpu.names", describe(cpuInfo, "model name"));
+ propertyMap.put("host.cpu.cachesize", describe(cpuInfo, "cache size"));
+
+ Multimap<String, String> memInfo = propertiesFromLinuxFile("/proc/meminfo");
+ // TODO redo memInfo.toString() so we don't get square brackets
+ propertyMap.put("host.memory.physical", memInfo.get("MemTotal").toString());
+ propertyMap.put("host.memory.swap", memInfo.get("SwapTotal").toString());
+
+ getAndroidEnvironment(propertyMap);
+ }
+
+ private void getAndroidEnvironment(Map<String, String> propertyMap) {
+ try {
+ Map<String, String> map = getAndroidProperties();
+ String manufacturer = map.get("ro.product.manufacturer");
+ String device = map.get("ro.product.device");
+ propertyMap.put("android.device", manufacturer + " " + device); // "Motorola sholes"
+
+ String brand = map.get("ro.product.brand");
+ String model = map.get("ro.product.model");
+ propertyMap.put("android.model", brand + " " + model); // "verizon Droid"
+
+ String release = map.get("ro.build.version.release");
+ String id = map.get("ro.build.id");
+ propertyMap.put("android.release", release + " " + id); // "Gingerbread GRH07B"
+ } catch (IOException ignored) {
+ }
+ }
+
+ private static String describe(Multimap<String, String> cpuInfo, String s) {
+ Collection<String> strings = cpuInfo.get(s);
+ // TODO redo the ImmutableMultiset.toString() call so we don't get square brackets
+ return (strings.size() == 1)
+ ? strings.iterator().next()
+ : ImmutableMultiset.copyOf(strings).toString();
+ }
+
+ /**
+ * Returns the key/value pairs from the specified properties-file like
+ * reader. Unlike standard Java properties files, {@code reader} is allowed
+ * to list the same property multiple times. Comments etc. are unsupported.
+ */
+ private static Multimap<String, String> propertiesFileToMultimap(Reader reader)
+ throws IOException {
+ ImmutableMultimap.Builder<String, String> result = ImmutableMultimap.builder();
+ BufferedReader in = new BufferedReader(reader);
+
+ String line;
+ while((line = in.readLine()) != null) {
+ String[] parts = line.split("\\s*\\:\\s*", 2);
+ if (parts.length == 2) {
+ result.put(parts[0], parts[1]);
+ }
+ }
+ in.close();
+
+ return result.build();
+ }
+
+ private static Multimap<String, String> propertiesFromLinuxFile(String file) {
+ try {
+ Process process = Runtime.getRuntime().exec(new String[]{"/bin/cat", file});
+ return propertiesFileToMultimap(
+ new InputStreamReader(process.getInputStream(), "ISO-8859-1"));
+ } catch (IOException e) {
+ return ImmutableMultimap.of();
+ }
+ }
+
+ public static void main(String[] args) {
+ Environment snapshot = new EnvironmentGetter().getEnvironmentSnapshot();
+ for (Map.Entry<String, String> entry : snapshot.getProperties().entrySet()) {
+ System.out.println(entry.getKey() + " " + entry.getValue());
+ }
+ }
+
+ /**
+ * Android properties are available from adb shell /system/bin/getprop. That
+ * program prints Android system properties in this format:
+ * [ro.product.model]: [Droid]
+ * [ro.product.brand]: [verizon]
+ */
+ private static Map<String, String> getAndroidProperties() throws IOException {
+ Map<String, String> result = new HashMap<String, String>();
+
+ Process process = Runtime.getRuntime().exec(new String[] {"/system/bin/getprop"});
+ BufferedReader reader = new BufferedReader(
+ new InputStreamReader(process.getInputStream(), "ISO-8859-1"));
+
+ Pattern pattern = Pattern.compile("\\[([^\\]]*)\\]: \\[([^\\]]*)\\]");
+ String line;
+ while ((line = reader.readLine()) != null) {
+ Matcher matcher = pattern.matcher(line);
+ if (matcher.matches()) {
+ result.put(matcher.group(1), matcher.group(2));
+ }
+ }
+ return result;
+ }
+}
diff --git a/caliper/src/main/java/com/google/caliper/InProcessRunner.java b/caliper/src/main/java/com/google/caliper/InProcessRunner.java
new file mode 100644
index 0000000..1ef4788
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/InProcessRunner.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import com.google.caliper.UserException.ExceptionFromUserCodeException;
+import com.google.common.base.Supplier;
+
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.List;
+
+/**
+ * Executes a benchmark in the current VM.
+ */
+final class InProcessRunner {
+
+ public void run(String... args) {
+ Arguments arguments = Arguments.parse(args);
+
+ ScenarioSelection scenarioSelection = new ScenarioSelection(arguments);
+
+ try {
+ Measurer measurer = getMeasurer(arguments);
+ List<Scenario> scenarios = scenarioSelection.select();
+ // We only expect one scenario right now - if we have more, something has gone wrong.
+ // This matters for things like reading the measurements. This is only done once, so if
+ // multiple scenarios are executed, they will be ignored!
+ if (scenarios.size() != 1) {
+ throw new IllegalArgumentException("Invalid arguments to subprocess. Expected exactly one "
+ + "scenario but got " + scenarios.size());
+ }
+ Scenario scenario = scenarios.get(0);
+
+ System.out.println("starting " + scenario);
+ MeasurementSet measurementSet = run(scenarioSelection, scenario, measurer);
+ System.out.println(arguments.getMarker() + Json.measurementSetToJson(measurementSet));
+ } catch (UserException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ExceptionFromUserCodeException(e);
+ }
+ }
+
+ public MeasurementSet run(final ScenarioSelection scenarioSelection, final Scenario scenario,
+ Measurer measurer) throws Exception {
+ Supplier<ConfiguredBenchmark> supplier = new Supplier<ConfiguredBenchmark>() {
+ @Override public ConfiguredBenchmark get() {
+ return scenarioSelection.createBenchmark(scenario);
+ }
+ };
+
+ PrintStream out = System.out;
+ PrintStream err = System.err;
+ measurer.setLogStream(out);
+ CountingPrintStream countedOut = new CountingPrintStream(out);
+ CountingPrintStream countedErr = new CountingPrintStream(err);
+ System.setOut(countedOut);
+ System.setErr(countedErr);
+ try {
+ MeasurementSet measurementSet = measurer.run(supplier);
+ if (measurementSet != null) {
+ measurementSet = measurementSet.plusCharCounts(
+ countedOut.getCount(), countedErr.getCount());
+ }
+ return measurementSet;
+ } finally {
+ System.setOut(out);
+ System.setErr(err);
+ }
+ }
+
+ private Measurer getMeasurer(Arguments arguments) {
+ if (arguments.getMeasurementType() == MeasurementType.TIME) {
+ return new TimeMeasurer(arguments.getWarmupMillis(), arguments.getRunMillis());
+ } else if (arguments.getMeasurementType() == MeasurementType.INSTANCE) {
+ return new InstancesAllocationMeasurer();
+ } else if (arguments.getMeasurementType() == MeasurementType.MEMORY) {
+ return new MemoryAllocationMeasurer();
+ } else if (arguments.getMeasurementType() == MeasurementType.DEBUG) {
+ return new DebugMeasurer(arguments.getDebugReps());
+ } else {
+ throw new IllegalArgumentException("unrecognized measurement type: "
+ + arguments.getMeasurementType());
+ }
+ }
+
+ public static void main(String... args) throws Exception {
+ try {
+ new InProcessRunner().run(args);
+ System.exit(0); // user code may have leave non-daemon threads behind!
+ } catch (UserException e) {
+ e.display(); // TODO: send this to the host process
+ System.out.println(LogConstants.CALIPER_LOG_PREFIX + LogConstants.SCENARIOS_FINISHED);
+ System.exit(1);
+ }
+ }
+
+ public PrintStream nullPrintStream() {
+ return new PrintStream(new OutputStream() {
+ public void write(int b) {}
+ });
+ }
+}
diff --git a/caliper/src/main/java/com/google/caliper/InstancesAllocationMeasurer.java b/caliper/src/main/java/com/google/caliper/InstancesAllocationMeasurer.java
new file mode 100644
index 0000000..3fe89c7
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/InstancesAllocationMeasurer.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+public final class InstancesAllocationMeasurer extends AllocationMeasurer {
+
+ InstancesAllocationMeasurer() {
+ type = "instance";
+ }
+
+ @Override protected long incrementAllocationCount(long oldAllocationCount, int arrayCount,
+ long size) {
+ return oldAllocationCount + 1;
+ }
+
+ @Override protected Measurement getMeasurement(ConfiguredBenchmark benchmark, long allocations) {
+ return new Measurement(benchmark.instanceUnitNames(), allocations,
+ benchmark.instancesToUnits(allocations));
+ }
+}
diff --git a/caliper/src/main/java/com/google/caliper/Json.java b/caliper/src/main/java/com/google/caliper/Json.java
new file mode 100644
index 0000000..9e3f809
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/Json.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import com.google.gson.reflect.TypeToken;
+
+import java.lang.reflect.Type;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+
+/**
+ * Ordinarily serialization should be done within the class that is being serialized. However,
+ * many of these classes are used by GWT, which dies when it sees Gson.
+ */
+public final class Json {
+ /**
+ * This Gson instance must be used when serializing a class that includes a Run as a member
+ * or as a member of a member (etc.), otherwise the Map<Scenario, ScenarioResult> will not
+ * be correctly serialized.
+ */
+ private static final Gson GSON_INSTANCE =
+ new GsonBuilder()
+ .registerTypeAdapter(Date.class, new DateTypeAdapter())
+ .registerTypeAdapter(Run.class, new RunTypeAdapter())
+ .registerTypeAdapter(Measurement.class, new MeasurementDeserializer())
+ .create();
+
+ public static Gson getGsonInstance() {
+ return GSON_INSTANCE;
+ }
+
+ public static String measurementSetToJson(MeasurementSet measurementSet) {
+ return new Gson().toJson(measurementSet);
+ }
+
+ /**
+ * Attempts to extract a MeasurementSet from a string, assuming it is JSON. If this fails, it
+ * tries to extract it from the string assuming it is a space-separated list of double values.
+ */
+ public static MeasurementSet measurementSetFromJson(String measurementSetJson) {
+ try {
+ return getGsonInstance().fromJson(measurementSetJson, MeasurementSet.class);
+ } catch (JsonParseException e) {
+ // might be an old MeasurementSet, so fall back on failure to the old, space separated
+ // serialization method.
+ try {
+ String[] measurementStrings = measurementSetJson.split("\\s+");
+ List<Measurement> measurements = new ArrayList<Measurement>();
+ for (String s : measurementStrings) {
+ measurements.add(
+ new Measurement(ImmutableMap.of("ns", 1, "us", 1000, "ms", 1000000, "s", 1000000000),
+ Double.valueOf(s), Double.valueOf(s)));
+ }
+ // seconds and variations is the default unit
+ return new MeasurementSet(measurements.toArray(new Measurement[measurements.size()]));
+ } catch (NumberFormatException ignore) {
+ throw new IllegalArgumentException("Not a measurement set: " + measurementSetJson);
+ }
+ }
+ }
+
+ public static MeasurementSet measurementSetFromJson(JsonObject measurementSetJson) {
+ return getGsonInstance().fromJson(measurementSetJson, MeasurementSet.class);
+ }
+
+ /**
+ * Backwards compatibility!
+ */
+ private static class MeasurementDeserializer implements JsonDeserializer<Measurement> {
+ @Override public Measurement deserialize(JsonElement jsonElement, Type type,
+ JsonDeserializationContext context) throws JsonParseException {
+ JsonObject obj = jsonElement.getAsJsonObject();
+ if (obj.has("raw") && obj.has("processed")) {
+ return new Measurement(
+ context.<Map<String, Integer>>deserialize(obj.get("unitNames"),
+ new TypeToken<Map<String, Integer>>() {}.getType()),
+ context.<Double>deserialize(obj.get("raw"), Double.class),
+ context.<Double>deserialize(obj.get("processed"), Double.class));
+ }
+ if (obj.has("nanosPerRep") && obj.has("unitsPerRep") && obj.has("unitNames")) {
+ return new Measurement(
+ context.<Map<String, Integer>>deserialize(obj.get("unitNames"),
+ new TypeToken<Map<String, Integer>>() {}.getType()),
+ context.<Double>deserialize(obj.get("nanosPerRep"), Double.class),
+ context.<Double>deserialize(obj.get("unitsPerRep"), Double.class));
+ }
+ throw new JsonParseException(obj.toString());
+ }
+ }
+
+ /**
+ * This adapter is necessary because gson doesn't handle Maps more complex than Map<String, ...>
+ * in a useful way. For example, Map<Scenario, ScenarioResult>'s serialized version simply uses
+ * Scenario.toString() as the keys. This adapter stores this Map as lists of
+ * KeyValuePair<Scenario, ScenarioResult> instead, to preserve the Scenario objects on
+ * deserialization.
+ */
+ private static class RunTypeAdapter implements JsonSerializer<Run>, JsonDeserializer<Run> {
+
+ @Override public Run deserialize(JsonElement jsonElement, Type type,
+ JsonDeserializationContext context) throws JsonParseException {
+
+ List<KeyValuePair<Scenario, ScenarioResult>> mapList = context.deserialize(
+ jsonElement.getAsJsonObject().get("measurements"),
+ new TypeToken<List<KeyValuePair<Scenario, ScenarioResult>>>() {}.getType());
+ Map<Scenario, ScenarioResult> measurements = new LinkedHashMap<Scenario, ScenarioResult>();
+ for (KeyValuePair<Scenario, ScenarioResult> entry : mapList) {
+ measurements.put(entry.getKey(), entry.getValue());
+ }
+
+ String benchmarkName =
+ context.deserialize(jsonElement.getAsJsonObject().get("benchmarkName"), String.class);
+
+ Date executedTimestamp = context.deserialize(
+ jsonElement.getAsJsonObject().get("executedTimestamp"), Date.class);
+
+ return new Run(measurements, benchmarkName, executedTimestamp);
+ }
+
+ @Override public JsonElement serialize(Run run, Type type, JsonSerializationContext context) {
+ JsonObject result = new JsonObject();
+ result.add("benchmarkName", context.serialize(run.getBenchmarkName()));
+ result.add("executedTimestamp", context.serialize(run.getExecutedTimestamp()));
+
+ List<KeyValuePair<Scenario, ScenarioResult>> mapList =
+ new ArrayList<KeyValuePair<Scenario, ScenarioResult>>();
+ for (Map.Entry<Scenario, ScenarioResult> entry : run.getMeasurements().entrySet()) {
+ mapList.add(new KeyValuePair<Scenario, ScenarioResult>(entry.getKey(), entry.getValue()));
+ }
+ result.add("measurements", context.serialize(mapList,
+ new TypeToken<List<KeyValuePair<Scenario, ScenarioResult>>>() {}.getType()));
+
+ return result;
+ }
+ }
+
+ private static class DateTypeAdapter implements JsonSerializer<Date>, JsonDeserializer<Date> {
+ private final DateFormat dateFormat;
+
+ private DateTypeAdapter() {
+ dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz", Locale.US);
+ dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+ }
+
+ @Override public synchronized JsonElement serialize(Date date, Type type,
+ JsonSerializationContext jsonSerializationContext) {
+ return new JsonPrimitive(dateFormat.format(date));
+ }
+
+ @Override public synchronized Date deserialize(JsonElement jsonElement, Type type,
+ JsonDeserializationContext jsonDeserializationContext) {
+ String dateString = jsonElement.getAsString();
+ // first try to parse as an ISO 8601 date
+ try {
+ return dateFormat.parse(dateString);
+ } catch (ParseException ignored) {
+ }
+ // next, try a GSON-style locale-specific dates (for Caliper r282 and earlier)
+ try {
+ return DateFormat.getDateTimeInstance().parse(dateString);
+ } catch (ParseException ignored) {
+ }
+ throw new JsonParseException(dateString);
+ }
+ }
+
+ /**
+ * This is similar to the Map.Entry class, but is necessary since Entrys are not supported
+ * by gson.
+ */
+ private static class KeyValuePair<K, V> {
+ private K k;
+ private V v;
+
+ KeyValuePair(K k, V v) {
+ this.k = k;
+ this.v = v;
+ }
+
+ public K getKey() {
+ return k;
+ }
+
+ public V getValue() {
+ return v;
+ }
+
+ @SuppressWarnings("unused")
+ private KeyValuePair() {} // for gson
+ }
+
+ private Json() {} // static class
+}
diff --git a/caliper/src/main/java/com/google/caliper/LogConstants.java b/caliper/src/main/java/com/google/caliper/LogConstants.java
new file mode 100644
index 0000000..09acebe
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/LogConstants.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+public final class LogConstants {
+ /**
+ * Must be prepended to line of XML that represents normalized scenario.
+ */
+ public static final String SCENARIO_JSON_PREFIX = "[scenario] ";
+ public static final String MEASUREMENT_JSON_PREFIX = "[measurement] ";
+
+ /**
+ * Must be prepended to any logs that are to be included in the run event log.
+ */
+ public static final String CALIPER_LOG_PREFIX = "[caliper] ";
+ public static final String SCENARIOS_STARTING = "[starting scenarios]";
+ public static final String STARTING_SCENARIO_PREFIX = "[starting scenario] ";
+ public static final String SCENARIO_FINISHED = "[scenario finished]";
+ public static final String SCENARIOS_FINISHED = "[scenarios finished]";
+
+ /**
+ * All events will be logged from when {@code MEASURED_SECTION_STARTING} is logged until
+ * {@code MEASURED_SECTION_DONE} is logged.
+ */
+ public static final String MEASURED_SECTION_STARTING = "[starting measured section]";
+ public static final String MEASURED_SECTION_DONE = "[done measured section]";
+
+ private LogConstants() {}
+}
diff --git a/caliper/src/main/java/com/google/caliper/Measurement.java b/caliper/src/main/java/com/google/caliper/Measurement.java
new file mode 100644
index 0000000..35865b6
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/Measurement.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import com.google.common.annotations.GwtCompatible;
+
+import java.io.Serializable;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Represents a measurement of a single run of a benchmark.
+ */
+@SuppressWarnings("serial")
+@GwtCompatible
+public final class Measurement
+ implements Serializable /* for GWT */ {
+
+ public static final Comparator<Measurement> SORT_BY_NANOS = new Comparator<Measurement>() {
+ @Override public int compare(Measurement a, Measurement b) {
+ double aNanos = a.getRaw();
+ double bNanos = b.getRaw();
+ return Double.compare(aNanos, bNanos);
+ }
+ };
+
+ public static final Comparator<Measurement> SORT_BY_UNITS = new Comparator<Measurement>() {
+ @Override public int compare(Measurement a, Measurement b) {
+ double aNanos = a.getProcessed();
+ double bNanos = b.getProcessed();
+ return Double.compare(aNanos, bNanos);
+ }
+ };
+
+ private /*final*/ double raw;
+ private /*final*/ double processed;
+ private /*final*/ Map<String, Integer> unitNames;
+
+ public Measurement(Map<String, Integer> unitNames, double raw, double processed) {
+ this.unitNames = new HashMap<String, Integer>(unitNames);
+ this.raw = raw;
+ this.processed = processed;
+ }
+
+ public Map<String, Integer> getUnitNames() {
+ return new HashMap<String, Integer>(unitNames);
+ }
+
+ public double getRaw() {
+ return raw;
+ }
+
+ public double getProcessed() {
+ return processed;
+ }
+
+ @Override public boolean equals(Object o) {
+ return o instanceof Measurement
+ && ((Measurement) o).raw == raw
+ && ((Measurement) o).processed == processed
+ && ((Measurement) o).unitNames.equals(unitNames);
+ }
+
+ @Override public int hashCode() {
+ return (int) raw // Double.doubleToLongBits doesn't exist on GWT
+ + (int) processed * 37
+ + unitNames.hashCode() * 1373;
+ }
+
+ @Override public String toString() {
+ return (raw != processed
+ ? raw + "/" + processed
+ : Double.toString(raw));
+ }
+
+ @SuppressWarnings("unused")
+ private Measurement() {} /* for GWT */
+}
diff --git a/caliper/src/main/java/com/google/caliper/MeasurementSet.java b/caliper/src/main/java/com/google/caliper/MeasurementSet.java
new file mode 100644
index 0000000..eb0b42a
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/MeasurementSet.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import com.google.common.annotations.GwtCompatible;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A collection of measurements of the same scenario.
+ */
+@SuppressWarnings("serial")
+@GwtCompatible
+public final class MeasurementSet
+ implements Serializable /* for GWT Serialization */ {
+
+ private /*final*/ List<Measurement> measurements;
+ /**
+ * Mapping of user-defined units to relative sizes.
+ */
+ private /*final*/ Map<String, Integer> unitNames;
+
+ private /*final*/ int systemOutCharCount;
+ private /*final*/ int systemErrCharCount;
+
+ public MeasurementSet(Measurement... measurements) {
+ this(0, 0, getUnitNamesFromMeasurements(measurements), Arrays.asList(measurements));
+ }
+
+ private static Map<String, Integer> getUnitNamesFromMeasurements(Measurement... measurements) {
+ Map<String, Integer> unitNameToAssign = null;
+ for (Measurement measurement : measurements) {
+ if (unitNameToAssign == null) {
+ unitNameToAssign = new HashMap<String, Integer>(measurement.getUnitNames());
+ } else if (!unitNameToAssign.equals(measurement.getUnitNames())) {
+ throw new IllegalArgumentException("incompatible unit names: " + unitNameToAssign + " and "
+ + measurement.getUnitNames());
+ }
+ }
+ return unitNameToAssign;
+ }
+
+ /**
+ * Constructor to use directly from plusMeasurement. Skips some excessive checking and takes a
+ * list directly.
+ */
+ private MeasurementSet(int systemOutCharCount, int systemErrCharCount,
+ Map<String, Integer> unitNames, List<Measurement> measurements) {
+ this.systemOutCharCount = systemOutCharCount;
+ this.systemErrCharCount = systemErrCharCount;
+ this.unitNames = unitNames;
+ this.measurements = measurements;
+ }
+
+ /**
+ * This is the same as getUnitNames(), but is for backwards compatibility on the server
+ * when null pointer exceptions need to be avoided.
+ */
+ public Map<String, Integer> getUnitNames(Map<String, Integer> defaultValue) {
+ if (unitNames == null) {
+ return defaultValue;
+ }
+ return new HashMap<String, Integer>(unitNames);
+ }
+
+ public Map<String, Integer> getUnitNames() {
+ return new HashMap<String, Integer>(unitNames);
+ }
+
+ public List<Measurement> getMeasurements() {
+ return new ArrayList<Measurement>(measurements);
+ }
+
+ public int size() {
+ return measurements.size();
+ }
+
+ public int getSystemOutCharCount() {
+ return systemOutCharCount;
+ }
+
+ public int getSystemErrCharCount() {
+ return systemErrCharCount;
+ }
+
+ public List<Double> getMeasurementsRaw() {
+ List<Double> measurementRaw = new ArrayList<Double>();
+ for (Measurement measurement : measurements) {
+ measurementRaw.add(measurement.getRaw());
+ }
+ return measurementRaw;
+ }
+
+ public List<Double> getMeasurementUnits() {
+ List<Double> measurementUnits = new ArrayList<Double>();
+ for (Measurement measurement : measurements) {
+ measurementUnits.add(measurement.getProcessed());
+ }
+ return measurementUnits;
+ }
+
+ /**
+ * Returns the median measurement, with respect to raw units.
+ */
+ public double medianRaw() {
+ return median(getMeasurementsRaw());
+ }
+
+ /**
+ * Returns the median measurement, with respect to user-defined units.
+ */
+ public double medianUnits() {
+ return median(getMeasurementUnits());
+ }
+
+ private double median(List<Double> doubles) {
+ Collections.sort(doubles);
+ int n = doubles.size();
+ return (n % 2 == 0)
+ ? (doubles.get(n / 2 - 1) + doubles.get(n / 2)) / 2
+ : doubles.get(n / 2);
+ }
+
+ /**
+ * Returns the average measurement with respect to raw units.
+ */
+ public double meanRaw() {
+ return mean(getMeasurementsRaw());
+ }
+
+ /**
+ * Returns the average measurement with respect to user-defined units.
+ */
+ public double meanUnits() {
+ return mean(getMeasurementUnits());
+ }
+
+ private double mean(List<Double> doubles) {
+ double sum = 0;
+ for (double d : doubles) {
+ sum += d;
+ }
+ return sum / doubles.size();
+ }
+
+ public double standardDeviationRaw() {
+ return standardDeviation(getMeasurementsRaw());
+ }
+
+ public double standardDeviationUnits() {
+ return standardDeviation(getMeasurementUnits());
+ }
+
+ /**
+ * Returns the standard deviation of the measurements.
+ */
+ private double standardDeviation(List<Double> doubles) {
+ double mean = mean(doubles);
+ double sumOfSquares = 0;
+ for (double d : doubles) {
+ double delta = (d - mean);
+ sumOfSquares += (delta * delta);
+ }
+ return Math.sqrt(sumOfSquares / (doubles.size() - 1));
+ }
+
+ public double minRaw() {
+ return min(getMeasurementsRaw());
+ }
+
+ public double minUnits() {
+ return min(getMeasurementUnits());
+ }
+
+ /**
+ * Returns the minimum measurement.
+ */
+ private double min(List<Double> doubles) {
+ Collections.sort(doubles);
+ return doubles.get(0);
+ }
+
+ public double maxRaw() {
+ return max(getMeasurementsRaw());
+ }
+
+ public double maxUnits() {
+ return max(getMeasurementUnits());
+ }
+
+ /**
+ * Returns the maximum measurement.
+ */
+ private double max(List<Double> doubles) {
+ Collections.sort(doubles, Collections.reverseOrder());
+ return doubles.get(0);
+ }
+
+ /**
+ * Returns a new measurement set that contains the measurements in this set
+ * plus the given additional measurement.
+ */
+ public MeasurementSet plusMeasurement(Measurement measurement) {
+ // verify that this Measurement is compatible with this MeasurementSet
+ if (unitNames != null && !unitNames.equals(measurement.getUnitNames())) {
+ throw new IllegalArgumentException("new measurement incompatible with units of measurement "
+ + "set. Expected " + unitNames + " but got " + measurement.getUnitNames());
+ }
+
+ List<Measurement> resultMeasurements = new ArrayList<Measurement>(measurements);
+ resultMeasurements.add(measurement);
+ Map<String, Integer> newUnitNames = unitNames == null ? measurement.getUnitNames() : unitNames;
+ return new MeasurementSet(systemOutCharCount, systemErrCharCount,
+ newUnitNames, resultMeasurements);
+ }
+
+ public MeasurementSet plusCharCounts(int systemOutCharCount, int systemErrCharCount) {
+ return new MeasurementSet(this.systemOutCharCount + systemOutCharCount,
+ this.systemErrCharCount + systemErrCharCount, unitNames, measurements);
+ }
+
+ @Override public boolean equals(Object o) {
+ return o instanceof MeasurementSet
+ && ((MeasurementSet) o).measurements.equals(measurements)
+ && ((MeasurementSet) o).unitNames.equals(unitNames)
+ && ((MeasurementSet) o).systemOutCharCount == systemOutCharCount
+ && ((MeasurementSet) o).systemErrCharCount == systemErrCharCount;
+ }
+
+ @Override public int hashCode() {
+ return measurements.hashCode()
+ + unitNames.hashCode() * 37
+ + systemOutCharCount * 1373
+ + systemErrCharCount * 53549;
+ }
+
+ @Override public String toString() {
+ return measurements.toString() + " " + unitNames + " "
+ + systemOutCharCount + "/" + systemErrCharCount;
+ }
+
+ @SuppressWarnings("unused")
+ private MeasurementSet() {} // for GWT Serialization
+}
diff --git a/caliper/src/main/java/com/google/caliper/MeasurementType.java b/caliper/src/main/java/com/google/caliper/MeasurementType.java
new file mode 100644
index 0000000..30de69f
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/MeasurementType.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import com.google.common.annotations.GwtCompatible;
+
+@GwtCompatible
+public enum MeasurementType {
+ TIME, INSTANCE, MEMORY, DEBUG
+}
diff --git a/caliper/src/main/java/com/google/caliper/Measurer.java b/caliper/src/main/java/com/google/caliper/Measurer.java
new file mode 100644
index 0000000..93002e6
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/Measurer.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import com.google.common.base.Supplier;
+
+import java.io.PrintStream;
+
+abstract class Measurer {
+
+ private PrintStream logStream = System.out;
+
+ /**
+ * Sets the stream used to log caliper events.
+ */
+ void setLogStream(PrintStream logStream) {
+ this.logStream = logStream;
+ }
+
+ public abstract MeasurementSet run(Supplier<ConfiguredBenchmark> testSupplier) throws Exception;
+
+ protected void prepareForTest() {
+ System.gc();
+ System.gc();
+ }
+
+ protected final void log(String message) {
+ logStream.println(LogConstants.CALIPER_LOG_PREFIX + message);
+ }
+}
diff --git a/caliper/src/main/java/com/google/caliper/MemoryAllocationMeasurer.java b/caliper/src/main/java/com/google/caliper/MemoryAllocationMeasurer.java
new file mode 100644
index 0000000..a28aabf
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/MemoryAllocationMeasurer.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+public final class MemoryAllocationMeasurer extends AllocationMeasurer {
+
+ public MemoryAllocationMeasurer() {
+ type = "byte";
+ }
+
+ @Override protected long incrementAllocationCount(long oldAllocationCount, int arrayCount,
+ long size) {
+ return oldAllocationCount + size;
+ }
+
+ @Override protected Measurement getMeasurement(ConfiguredBenchmark benchmark, long allocations) {
+ return new Measurement(benchmark.memoryUnitNames(), allocations,
+ benchmark.bytesToUnits(allocations));
+ }
+}
diff --git a/caliper/src/main/java/com/google/caliper/Param.java b/caliper/src/main/java/com/google/caliper/Param.java
new file mode 100644
index 0000000..f4cdf45
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/Param.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * To make your benchmark depend on a parameterized value, create a field with the name you want
+ * this parameter to be known by, and add this annotation. Caliper will inject a value for this
+ * field to each instance it creates. These values come from
+ *
+ * <ul>
+ * <li>The command line, if specified using {@code -Dname=value1,value2,value3}
+ * <li>Otherwise, the {@link #value()} list given in the annotation
+ * <li>Otherwise, Caliper looks for a static method named {@code paramName + "Values"} (for
+ * example, if the parameter field is {@code size}, it looks for {@code sizeValues()}). The
+ * method can return any subtype of {@link Iterable}. The contents of that iterable are used as
+ * the parameter values.
+ * <li>Otherwise, Caliper repeats the previous check looking for a static <em>field</em> instead
+ * of a method.
+ * <li>Otherwise, if the parameter type is either {@code boolean} or an {@code enum} type, Caliper
+ * assumes you want all possible values.
+ * <li>Finally, if none of the above match, Caliper will display an error and exit.
+ * </ul>
+ *
+ * <p>Caliper parameters are always strings, but can be converted to other types at the point of
+ * injection. If the type of the field this annotation is applied to is not {@link String}, then the
+ * type class must contain a static {@code fromString(String)}, {@code decode(String)} or {@code
+ * valueOf(String)} method that returns that type, or a constructor accepting only a {@code String}.
+ *
+ * <p>Caliper will test every possible combination of parameter values for your benchmark. For
+ * example, if you have two parameters, {@code -Dletter=a,b,c -Dnumber=1,2}, Caliper will construct
+ * six independent "scenarios" and perform measurement for each one.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface Param {
+ /**
+ * One or more default values, as strings, that this parameter should be given if none are
+ * specified on the command line. If values are specified on the command line, the defaults given
+ * here are all ignored.
+ */
+ String[] value() default {};
+}
diff --git a/caliper/src/main/java/com/google/caliper/Parameter.java b/caliper/src/main/java/com/google/caliper/Parameter.java
new file mode 100644
index 0000000..c79bc86
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/Parameter.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+/**
+ * A parameter in a {@link SimpleBenchmark}.
+ *
+ * @param <T> the (possibly wrapped) type of the parameter field, such as {@link
+ * String} or {@link Integer}
+ */
+abstract class Parameter<T> {
+
+ private final Field field;
+
+ private Parameter(Field field) {
+ this.field = field;
+ }
+
+ /**
+ * Returns all parameters for the given class.
+ */
+ public static Map<String, Parameter<?>> forClass(Class<? extends Benchmark> suiteClass) {
+ Map<String, Parameter<?>> parameters = new TreeMap<String, Parameter<?>>();
+ for (Field field : suiteClass.getDeclaredFields()) {
+ if (field.isAnnotationPresent(Param.class)) {
+ field.setAccessible(true);
+ Parameter<?> parameter = forField(suiteClass, field);
+ parameters.put(parameter.getName(), parameter);
+ }
+ }
+ return parameters;
+ }
+
+ @VisibleForTesting
+ static Parameter<?> forField(
+ Class<? extends Benchmark> suiteClass, final Field field) {
+ // First check for String values on the annotation itself
+ final Object[] defaults = field.getAnnotation(Param.class).value();
+ if (defaults.length > 0) {
+ return new Parameter<Object>(field) {
+ @Override public Iterable<Object> values() throws Exception {
+ return Arrays.asList(defaults);
+ }
+ };
+ // TODO: or should we continue so we can give an error/warning if params are also give in a
+ // method or field?
+ }
+
+ Parameter<?> result = null;
+ Type returnType = null;
+ Member member = null;
+
+ // Now check for a fooValues() method
+ try {
+ final Method valuesMethod = suiteClass.getDeclaredMethod(field.getName() + "Values");
+ if (!Modifier.isStatic(valuesMethod.getModifiers())) {
+ throw new ConfigurationException("Values method must be static " + member);
+ }
+ valuesMethod.setAccessible(true);
+ member = valuesMethod;
+ returnType = valuesMethod.getGenericReturnType();
+ result = new Parameter<Object>(field) {
+ @SuppressWarnings("unchecked") // guarded below
+ @Override public Iterable<Object> values() throws Exception {
+ return (Iterable<Object>) valuesMethod.invoke(null);
+ }
+ };
+ } catch (NoSuchMethodException ignored) {
+ }
+
+ // Now check for a fooValues field
+ try {
+ final Field valuesField = suiteClass.getDeclaredField(field.getName() + "Values");
+ if (!Modifier.isStatic(valuesField.getModifiers())) {
+ throw new ConfigurationException("Values field must be static " + member);
+ }
+ valuesField.setAccessible(true);
+ member = valuesField;
+ if (result != null) {
+ throw new ConfigurationException("Two values members defined for " + field);
+ }
+ returnType = valuesField.getGenericType();
+ result = new Parameter<Object>(field) {
+ @SuppressWarnings("unchecked") // guarded below
+ @Override public Iterable<Object> values() throws Exception {
+ return (Iterable<Object>) valuesField.get(null);
+ }
+ };
+ } catch (NoSuchFieldException ignored) {
+ }
+
+ // If there isn't a values member but the parameter is an enum, we default
+ // to EnumSet.allOf.
+ if (member == null && field.getType().isEnum()) {
+ returnType = Collection.class;
+ result = new Parameter<Object>(field) {
+ // TODO: figure out the simplest way to make this compile and be green in IDEA too
+ @SuppressWarnings({"unchecked", "RawUseOfParameterizedType", "RedundantCast"})
+ // guarded above
+ @Override public Iterable<Object> values() throws Exception {
+ Set<Enum> set = EnumSet.allOf((Class<Enum>) field.getType());
+ return Collections.<Object>unmodifiableSet(set);
+ }
+ };
+ }
+
+ // If it's boolean, default to (true, false)
+ if (member == null && field.getType() == boolean.class) {
+ returnType = Collection.class;
+ result = new Parameter<Object>(field) {
+ @Override public Iterable<Object> values() throws Exception {
+ return Arrays.<Object>asList(Boolean.TRUE, Boolean.FALSE);
+ }
+ };
+ }
+
+ if (result == null) {
+ return new Parameter<Object>(field) {
+ @Override public Iterable<Object> values() {
+ // TODO: need tests to make sure this fails properly when no cmdline params given and
+ // works properly when they are given. Also, can we restructure the code so that we
+ // just throw here instead of later?
+ return Collections.emptySet();
+ }
+ };
+ } else if (!isValidReturnType(returnType)) {
+ throw new ConfigurationException("Invalid return type " + returnType
+ + " for values member " + member + "; must be Collection");
+ }
+ return result;
+ }
+
+ private static boolean isValidReturnType(Type type) {
+ if (type instanceof Class) {
+ return isIterableClass(type);
+ }
+ if (type instanceof ParameterizedType) {
+ return isIterableClass(((ParameterizedType) type).getRawType());
+ }
+ return false;
+ }
+
+ private static boolean isIterableClass(Type returnClass) {
+ return Iterable.class.isAssignableFrom((Class<?>) returnClass);
+ }
+
+ /**
+ * Sets the value of this property to the specified value for the given suite.
+ */
+ public void set(Benchmark suite, Object value) throws Exception {
+ field.set(suite, value);
+ }
+
+ /**
+ * Returns the available values of the property as specified by the suite.
+ */
+ public abstract Iterable<T> values() throws Exception;
+
+ /**
+ * Returns the parameter's type, such as double.class.
+ */
+ public Type getType() {
+ return field.getGenericType();
+ }
+
+ /**
+ * Returns the field's name.
+ */
+ String getName() {
+ return field.getName();
+ }
+}
diff --git a/caliper/src/main/java/com/google/caliper/Result.java b/caliper/src/main/java/com/google/caliper/Result.java
new file mode 100644
index 0000000..c57f5a3
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/Result.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+/**
+ * Represents an invocation of a benchmark, including the run itself, as well as the environment
+ * in which the run occurred.
+ */
+public final class Result {
+ private /*final*/ Run run;
+ private /*final*/ Environment environment;
+
+ public Result(Run run, Environment environment) {
+ this.run = run;
+ this.environment = environment;
+ }
+
+ public Run getRun() {
+ return run;
+ }
+
+ public Environment getEnvironment() {
+ return environment;
+ }
+
+ @Override public boolean equals(Object o) {
+ return o instanceof Result
+ && ((Result) o).run.equals(run)
+ && ((Result) o).environment.equals(environment);
+ }
+
+ @Override public int hashCode() {
+ return run.hashCode() * 37 + environment.hashCode();
+ }
+
+ @Override public String toString() {
+ return run + "@" + environment;
+ }
+
+ @SuppressWarnings("unused")
+ private Result() {} // for gson
+}
diff --git a/caliper/src/main/java/com/google/caliper/ResultsReader.java b/caliper/src/main/java/com/google/caliper/ResultsReader.java
new file mode 100644
index 0000000..2396dd3
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/ResultsReader.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import com.google.gson.JsonParseException;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+/**
+ * Helps with deserialization of results, given uncertainty about the format (xml or json) they
+ * are in.
+ */
+public final class ResultsReader {
+ public Result getResult(InputStream in) throws IOException {
+ // save input into a byte array since we may need to read it twice
+ byte[] postedData = readAllBytes(in);
+ Result result;
+ InputStreamReader baisJsonReader = new InputStreamReader(new ByteArrayInputStream(postedData));
+ try {
+ result = Json.getGsonInstance().fromJson(baisJsonReader, Result.class);
+ } catch (JsonParseException e) {
+ // probably an old client is trying to send data, so try to parse it as XML instead.
+ ByteArrayInputStream baisXml = new ByteArrayInputStream(postedData);
+ try {
+ result = Xml.resultFromXml(baisXml);
+ } catch (Exception e2) {
+ throw new RuntimeException(e);
+ } finally {
+ baisXml.close();
+ }
+ } finally {
+ baisJsonReader.close();
+ }
+ return result;
+ }
+
+ private byte[] readAllBytes(InputStream in) throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ byte[] buf = new byte[4096];
+ int read;
+ while ((read = in.read(buf)) != -1) {
+ baos.write(buf, 0, read);
+ }
+ return baos.toByteArray();
+ }
+}
diff --git a/caliper/src/main/java/com/google/caliper/Run.java b/caliper/src/main/java/com/google/caliper/Run.java
new file mode 100644
index 0000000..00f9350
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/Run.java
@@ -0,0 +1,93 @@
+/**
+ * Copyright (C) 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import com.google.common.annotations.GwtCompatible;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * The complete result of a benchmark suite run.
+ *
+ * WARNING: a JSON representation of this class is stored on the app engine server. If any changes
+ * are made to this class, a deserialization adapter must be written for this class to ensure
+ * backwards compatibility.
+ *
+ * <p>Gwt-safe.
+ */
+@SuppressWarnings("serial")
+@GwtCompatible
+public final class Run
+ implements Serializable /* for GWT Serialization */ {
+
+ private /*final*/ Map<Scenario, ScenarioResult> measurements;
+ private /*final*/ String benchmarkName;
+ private /*final*/ long executedTimestamp;
+
+ // TODO: add more run properties such as checksums of the executed code
+
+ public Run(Map<Scenario, ScenarioResult> measurements,
+ String benchmarkName, Date executedTimestamp) {
+ if (benchmarkName == null || executedTimestamp == null) {
+ throw new NullPointerException();
+ }
+
+ this.measurements = new LinkedHashMap<Scenario, ScenarioResult>(measurements);
+ this.benchmarkName = benchmarkName;
+ this.executedTimestamp = executedTimestamp.getTime();
+ }
+
+ public Map<Scenario, ScenarioResult> getMeasurements() {
+ return measurements;
+ }
+
+ public String getBenchmarkName() {
+ return benchmarkName;
+ }
+
+ public Date getExecutedTimestamp() {
+ return new Date(executedTimestamp);
+ }
+
+ @Override public boolean equals(Object o) {
+ if (o instanceof Run) {
+ Run that = (Run) o;
+ return measurements.equals(that.measurements)
+ && benchmarkName.equals(that.benchmarkName)
+ && executedTimestamp == that.executedTimestamp;
+ }
+
+ return false;
+ }
+
+ @Override public int hashCode() {
+ int result = measurements.hashCode();
+ result = result * 37 + benchmarkName.hashCode();
+ result = result * 37 + (int) ((executedTimestamp >> 32) ^ executedTimestamp);
+ return result;
+ }
+
+ @Override public String toString() {
+ return measurements.toString();
+ }
+
+ @SuppressWarnings("unused")
+ private Run() {} // for GWT Serialization
+}
diff --git a/caliper/src/main/java/com/google/caliper/Runner.java b/caliper/src/main/java/com/google/caliper/Runner.java
new file mode 100644
index 0000000..57715c9
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/Runner.java
@@ -0,0 +1,438 @@
+/*
+ * Copyright (C) 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import com.google.caliper.UserException.DisplayUsageException;
+import com.google.caliper.UserException.ExceptionFromUserCodeException;
+import com.google.caliper.util.InterleavedReader;
+import com.google.common.base.Joiner;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ObjectArrays;
+import com.google.common.io.Closeables;
+import com.google.gson.JsonObject;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.net.HttpURLConnection;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.net.URL;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.TimeZone;
+import java.util.regex.Pattern;
+
+/**
+ * Creates, executes and reports benchmark runs.
+ */
+public final class Runner {
+
+ private static final FileFilter UPLOAD_FILE_FILTER = new FileFilter() {
+ @Override public boolean accept(File file) {
+ return file.getName().endsWith(".xml") || file.getName().endsWith(".json");
+ }
+ };
+
+ private static final String FILE_NAME_DATE_FORMAT = "yyyy-MM-dd'T'HH-mm-ssZ";
+
+ private static final Splitter ARGUMENT_SPLITTER
+ = Splitter.on(Pattern.compile("\\s+")).omitEmptyStrings();
+
+ /** Command line arguments to the process */
+ private Arguments arguments;
+ private ScenarioSelection scenarioSelection;
+
+ private String createFileName(Result result) {
+ String timestamp = createTimestamp();
+ return String.format("%s.%s.json", result.getRun().getBenchmarkName(), timestamp);
+ }
+
+ private String createTimestamp() {
+ SimpleDateFormat dateFormat = new SimpleDateFormat(FILE_NAME_DATE_FORMAT, Locale.US);
+ dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+ dateFormat.setLenient(true);
+ return dateFormat.format(new Date());
+ }
+
+ public void run(String... args) {
+ this.arguments = Arguments.parse(args);
+ File resultsUploadFile = arguments.getUploadResultsFile();
+ if (resultsUploadFile != null) {
+ uploadResultsFileOrDir(resultsUploadFile);
+ return;
+ }
+ this.scenarioSelection = new ScenarioSelection(arguments);
+ if (arguments.getDebug()) {
+ debug();
+ return;
+ }
+ Result result = runOutOfProcess();
+ new ConsoleReport(result.getRun(), arguments).displayResults();
+ boolean saveResultsLocally = arguments.getSaveResultsFile() != null;
+ try {
+ postResults(result);
+ } catch (Exception e) {
+ System.out.println();
+ System.out.println(e);
+ saveResultsLocally = true;
+ }
+
+ if (saveResultsLocally) {
+ saveResults(result);
+ }
+ }
+
+ void uploadResultsFileOrDir(File resultsFileOrDir) {
+ try {
+ if (resultsFileOrDir.isDirectory()) {
+ for (File resultsFile : resultsFileOrDir.listFiles(UPLOAD_FILE_FILTER)) {
+ uploadResults(resultsFile);
+ }
+ } else {
+ uploadResults(resultsFileOrDir);
+ }
+ } catch (Exception e) {
+ throw new RuntimeException("uploading XML file failed", e);
+ }
+ }
+
+ private void uploadResults(File resultsUploadFile) throws IOException {
+ System.out.println();
+ System.out.println("Uploading " + resultsUploadFile.getCanonicalPath());
+ InputStream inputStream = new FileInputStream(resultsUploadFile);
+ try {
+ Result result = new ResultsReader().getResult(inputStream);
+ postResults(result);
+ } finally {
+ inputStream.close();
+ }
+ }
+
+ private void saveResults(Result result) {
+ File resultsFile = arguments.getSaveResultsFile();
+ File destinationFile;
+ if (resultsFile == null) {
+ File dir = new File("./caliper-results");
+ dir.mkdirs();
+ destinationFile = new File(dir, createFileName(result));
+ } else if (resultsFile.exists() && resultsFile.isDirectory()) {
+ destinationFile = new File(resultsFile, createFileName(result));
+ } else {
+ // assume this is a file
+ File parent = resultsFile.getParentFile();
+ if (parent != null) {
+ parent.mkdirs();
+ }
+ destinationFile = resultsFile;
+ }
+
+ PrintStream filePrintStream;
+ try {
+ filePrintStream = new PrintStream(new FileOutputStream(destinationFile));
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException("can't open " + destinationFile, e);
+ }
+ String resultJson = Json.getGsonInstance().toJson(result);
+ try {
+ System.out.println();
+ System.out.println("Writing results to " + destinationFile.getCanonicalPath());
+ filePrintStream.print(resultJson);
+ } catch (Exception e) {
+ System.out.println(e);
+ System.out.println("Failed to write results to file, writing to standard out instead:");
+ System.out.println(resultJson);
+ System.out.flush();
+ } finally {
+ filePrintStream.close();
+ }
+ }
+
+ private void postResults(Result result) {
+ CaliperRc caliperrc = CaliperRc.INSTANCE;
+ String postUrl = caliperrc.getPostUrl();
+ String apiKey = caliperrc.getApiKey();
+ if (postUrl == null || apiKey == null) {
+ // TODO: probably nicer to show a message if only one is null
+ return;
+ }
+
+ try {
+ URL url = new URL(postUrl + apiKey + "/" + result.getRun().getBenchmarkName());
+ HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(getProxy());
+ urlConnection.setDoOutput(true);
+ String resultJson = Json.getGsonInstance().toJson(result);
+ urlConnection.getOutputStream().write(resultJson.getBytes());
+ if (urlConnection.getResponseCode() == 200) {
+ System.out.println("");
+ System.out.println("View current and previous benchmark results online:");
+ BufferedReader in = new BufferedReader(
+ new InputStreamReader(urlConnection.getInputStream()));
+ System.out.println(" " + in.readLine());
+ in.close();
+ return;
+ }
+
+ System.out.println("Posting to " + postUrl + " failed: "
+ + urlConnection.getResponseMessage());
+ BufferedReader reader = new BufferedReader(
+ new InputStreamReader(urlConnection.getInputStream()));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ System.out.println(line);
+ }
+ reader.close();
+ } catch (IOException e) {
+ throw new RuntimeException("Posting to " + postUrl + " failed.", e);
+ }
+ }
+
+ private Proxy getProxy() {
+ String proxyAddress = CaliperRc.INSTANCE.getProxy();
+ if (proxyAddress == null) {
+ return Proxy.NO_PROXY;
+ }
+
+ String[] proxyHostAndPort = proxyAddress.trim().split(":");
+ return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(
+ proxyHostAndPort[0], Integer.parseInt(proxyHostAndPort[1])));
+ }
+
+ private ScenarioResult runScenario(Scenario scenario) {
+ MeasurementResult timeMeasurementResult = measure(scenario, MeasurementType.TIME);
+ MeasurementSet allocationMeasurements = null;
+ String allocationEventLog = null;
+ MeasurementSet memoryMeasurements = null;
+ String memoryEventLog = null;
+ if (arguments.getMeasureMemory()) {
+ MeasurementResult allocationsMeasurementResult =
+ measure(scenario, MeasurementType.INSTANCE);
+ allocationMeasurements = allocationsMeasurementResult.getMeasurements();
+ allocationEventLog = allocationsMeasurementResult.getEventLog();
+ MeasurementResult memoryMeasurementResult =
+ measure(scenario, MeasurementType.MEMORY);
+ memoryMeasurements = memoryMeasurementResult.getMeasurements();
+ memoryEventLog = memoryMeasurementResult.getEventLog();
+ }
+
+ return new ScenarioResult(timeMeasurementResult.getMeasurements(),
+ timeMeasurementResult.getEventLog(),
+ allocationMeasurements, allocationEventLog,
+ memoryMeasurements, memoryEventLog);
+ }
+
+ private static class MeasurementResult {
+ private final MeasurementSet measurements;
+ private final String eventLog;
+
+ MeasurementResult(MeasurementSet measurements, String eventLog) {
+ this.measurements = measurements;
+ this.eventLog = eventLog;
+ }
+
+ public MeasurementSet getMeasurements() {
+ return measurements;
+ }
+
+ public String getEventLog() {
+ return eventLog;
+ }
+ }
+
+ private MeasurementResult measure(Scenario scenario, MeasurementType type) {
+ Vm vm = new VmFactory().createVm(scenario);
+ // this must be done before starting the forked process on certain VMs
+ ProcessBuilder processBuilder = createCommand(scenario, vm, type)
+ .redirectErrorStream(true);
+ Process timeProcess;
+ try {
+ timeProcess = processBuilder.start();
+ } catch (IOException e) {
+ throw new RuntimeException("failed to start subprocess", e);
+ }
+
+ MeasurementSet measurementSet = null;
+ StringBuilder eventLog = new StringBuilder();
+ InterleavedReader reader = null;
+ try {
+ reader = new InterleavedReader(arguments.getMarker(),
+ new InputStreamReader(timeProcess.getInputStream()));
+ Object o;
+ while ((o = reader.read()) != null) {
+ if (o instanceof String) {
+ eventLog.append(o);
+ } else if (measurementSet == null) {
+ JsonObject jsonObject = (JsonObject) o;
+ measurementSet = Json.measurementSetFromJson(jsonObject);
+ } else {
+ throw new RuntimeException("Unexpected value: " + o);
+ }
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } finally {
+ Closeables.closeQuietly(reader);
+ timeProcess.destroy();
+ }
+
+ if (measurementSet == null) {
+ String message = "Failed to execute " + Joiner.on(" ").join(processBuilder.command());
+ System.err.println(" " + message);
+ System.err.println(eventLog.toString());
+ throw new ConfigurationException(message);
+ }
+
+ return new MeasurementResult(measurementSet, eventLog.toString());
+ }
+
+ private ProcessBuilder createCommand(Scenario scenario, Vm vm, MeasurementType type) {
+ File workingDirectory = new File(System.getProperty("user.dir"));
+
+ String classPath = System.getProperty("java.class.path");
+ if (classPath == null || classPath.length() == 0) {
+ throw new IllegalStateException("java.class.path is undefined in " + System.getProperties());
+ }
+
+ ImmutableList.Builder<String> vmArgs = ImmutableList.builder();
+ vmArgs.addAll(ARGUMENT_SPLITTER.split(scenario.getVariables().get(Scenario.VM_KEY)));
+ if (type == MeasurementType.INSTANCE || type == MeasurementType.MEMORY) {
+ String allocationJarFile = System.getenv("ALLOCATION_JAR");
+ vmArgs.add("-javaagent:" + allocationJarFile);
+ }
+ vmArgs.addAll(vm.getVmSpecificOptions(type, arguments));
+
+ Map<String, String> vmParameters = scenario.getVariables(
+ scenarioSelection.getVmParameterNames());
+ for (String vmParameter : vmParameters.values()) {
+ vmArgs.addAll(ARGUMENT_SPLITTER.split(vmParameter));
+ }
+
+ ImmutableList.Builder<String> caliperArgs = ImmutableList.builder();
+ caliperArgs.add("--warmupMillis").add(Long.toString(arguments.getWarmupMillis()));
+ caliperArgs.add("--runMillis").add(Long.toString(arguments.getRunMillis()));
+ caliperArgs.add("--measurementType").add(type.toString());
+ caliperArgs.add("--marker").add(arguments.getMarker());
+
+ Map<String,String> userParameters = scenario.getVariables(
+ scenarioSelection.getUserParameterNames());
+ for (Entry<String, String> entry : userParameters.entrySet()) {
+ caliperArgs.add("-D" + entry.getKey() + "=" + entry.getValue());
+ }
+ caliperArgs.add(arguments.getSuiteClassName());
+
+ return vm.newProcessBuilder(workingDirectory, classPath,
+ vmArgs.build(), InProcessRunner.class.getName(), caliperArgs.build());
+ }
+
+ private void debug() {
+ try {
+ int debugReps = arguments.getDebugReps();
+ InProcessRunner runner = new InProcessRunner();
+ DebugMeasurer measurer = new DebugMeasurer(debugReps);
+ for (Scenario scenario : scenarioSelection.select()) {
+ System.out.println("running " + debugReps + " debug reps of " + scenario);
+ runner.run(scenarioSelection, scenario, measurer);
+ }
+ } catch (Exception e) {
+ throw new ExceptionFromUserCodeException(e);
+ }
+ }
+
+ private Result runOutOfProcess() {
+ Date executedDate = new Date();
+ ImmutableMap.Builder<Scenario, ScenarioResult> resultsBuilder = ImmutableMap.builder();
+
+ try {
+ List<Scenario> scenarios = scenarioSelection.select();
+
+ int i = 0;
+ for (Scenario scenario : scenarios) {
+ beforeMeasurement(i++, scenarios.size(), scenario);
+ ScenarioResult scenarioResult = runScenario(scenario);
+ afterMeasurement(arguments.getMeasureMemory(), scenarioResult);
+ resultsBuilder.put(scenario, scenarioResult);
+ }
+ System.out.println();
+
+ Environment environment = new EnvironmentGetter().getEnvironmentSnapshot();
+ return new Result(
+ new Run(resultsBuilder.build(), arguments.getSuiteClassName(), executedDate),
+ environment);
+ } catch (Exception e) {
+ throw new ExceptionFromUserCodeException(e);
+ }
+ }
+
+ private void beforeMeasurement(int index, int total, Scenario scenario) {
+ double percentDone = (double) index / total;
+ System.out.printf("%2.0f%% %s", percentDone * 100, scenario);
+ }
+
+ private void afterMeasurement(boolean memoryMeasured, ScenarioResult scenarioResult) {
+ String memoryMeasurements = "";
+ if (memoryMeasured) {
+ MeasurementSet instanceMeasurementSet =
+ scenarioResult.getMeasurementSet(MeasurementType.INSTANCE);
+ String instanceUnit =
+ ConsoleReport.UNIT_ORDERING.min(instanceMeasurementSet.getUnitNames().entrySet()).getKey();
+ MeasurementSet memoryMeasurementSet = scenarioResult.getMeasurementSet(MeasurementType.MEMORY);
+ String memoryUnit =
+ ConsoleReport.UNIT_ORDERING.min(memoryMeasurementSet.getUnitNames().entrySet()).getKey();
+ memoryMeasurements = String.format(", allocated %s%s for a total of %s%s",
+ Math.round(instanceMeasurementSet.medianUnits()), instanceUnit,
+ Math.round(memoryMeasurementSet.medianUnits()), memoryUnit);
+ }
+
+ MeasurementSet timeMeasurementSet = scenarioResult.getMeasurementSet(MeasurementType.TIME);
+ String unit =
+ ConsoleReport.UNIT_ORDERING.min(timeMeasurementSet.getUnitNames().entrySet()).getKey();
+ System.out.printf(" %.2f %s; \u03C3=%.2f %s @ %d trials%s%n", timeMeasurementSet.medianUnits(),
+ unit, timeMeasurementSet.standardDeviationUnits(), unit,
+ timeMeasurementSet.getMeasurements().size(), memoryMeasurements);
+ }
+
+ public static void main(String[] args) {
+ try {
+ new Runner().run(args);
+ System.exit(0); // user code may have leave non-daemon threads behind!
+ } catch (DisplayUsageException e) {
+ e.display();
+ System.exit(0);
+ } catch (UserException e) {
+ e.display();
+ System.exit(1);
+ }
+ }
+
+ @SuppressWarnings("unchecked") // temporary fakery
+ public static void main(Class<? extends Benchmark> suite, String[] args) {
+ main(ObjectArrays.concat(args, suite.getName()));
+ }
+}
diff --git a/caliper/src/main/java/com/google/caliper/Scenario.java b/caliper/src/main/java/com/google/caliper/Scenario.java
new file mode 100644
index 0000000..bc026d8
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/Scenario.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import com.google.common.annotations.GwtCompatible;
+
+import java.io.Serializable;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A configured benchmark.
+ *
+ * WARNING: a JSON representation of this class is stored on the app engine server. If any changes
+ * are made to this class, a deserialization adapter must be written for this class to ensure
+ * backwards compatibility.
+ *
+ * <p>Gwt-safe.
+ */
+@SuppressWarnings("serial")
+@GwtCompatible
+public final class Scenario
+ implements Serializable /* for GWT */ {
+
+ static final String VM_KEY = "vm";
+ static final String TRIAL_KEY = "trial";
+
+ private /*final*/ Map<String, String> variables;
+
+ public Scenario(Map<String, String> variables) {
+ this.variables = new LinkedHashMap<String, String>(variables);
+ }
+
+ public Map<String, String> getVariables() {
+ return variables;
+ }
+
+ /**
+ * Returns the named set of variables.
+ */
+ public Map<String, String> getVariables(Set<String> names) {
+ Map<String, String> result = new LinkedHashMap<String, String>(variables);
+ result.keySet().retainAll(names);
+ if (!result.keySet().equals(names)) {
+ throw new IllegalArgumentException("Not all of " + names + " are in " + result.keySet());
+ }
+ return result;
+ }
+
+ @Override public boolean equals(Object o) {
+ return o instanceof Scenario
+ && ((Scenario) o).getVariables().equals(variables);
+ }
+
+ @Override public int hashCode() {
+ return variables.hashCode();
+ }
+
+ @Override public String toString() {
+ return "Scenario" + variables;
+ }
+
+ @SuppressWarnings("unused")
+ private Scenario() {} // for GWT
+}
diff --git a/caliper/src/main/java/com/google/caliper/ScenarioResult.java b/caliper/src/main/java/com/google/caliper/ScenarioResult.java
new file mode 100644
index 0000000..1fa687d
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/ScenarioResult.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import com.google.common.annotations.GwtCompatible;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Holds the results for a particular scenario, including timing measurements, memory use
+ * measurements, and event logs for both, recording significant events during measurement.
+ *
+ * WARNING: a JSON representation of this class is stored on the app engine server. If any changes
+ * are made to this class, a deserialization adapter must be written for this class to ensure
+ * backwards compatibility.
+ *
+ * <p>Gwt-safe.
+ */
+@SuppressWarnings({"serial", "FieldMayBeFinal"})
+@GwtCompatible
+public final class ScenarioResult
+ implements Serializable /* for GWT Serialization */ {
+
+ // want these to be EnumMaps, but that upsets GWT
+ private /*final*/ Map<String, MeasurementSet> measurementSetMap
+ = new HashMap<String, MeasurementSet>();
+ private /*final*/ Map<String, String> eventLogMap
+ = new HashMap<String, String>();
+
+ public ScenarioResult(MeasurementSet timeMeasurementSet,
+ String timeEventLog, MeasurementSet instanceMeasurementSet,
+ String instanceEventLog, MeasurementSet memoryMeasurementSet,
+ String memoryEventLog) {
+ if (timeMeasurementSet != null) {
+ measurementSetMap.put(MeasurementType.TIME.toString(), timeMeasurementSet);
+ eventLogMap.put(MeasurementType.TIME.toString(), timeEventLog);
+ }
+ if (instanceMeasurementSet != null) {
+ measurementSetMap.put(MeasurementType.INSTANCE.toString(), instanceMeasurementSet);
+ eventLogMap.put(MeasurementType.INSTANCE.toString(), instanceEventLog);
+ }
+ if (memoryMeasurementSet != null) {
+ measurementSetMap.put(MeasurementType.MEMORY.toString(), memoryMeasurementSet);
+ eventLogMap.put(MeasurementType.MEMORY.toString(), memoryEventLog);
+ }
+ }
+
+ public MeasurementSet getMeasurementSet(MeasurementType type) {
+ return measurementSetMap.get(type.toString());
+ }
+
+ public String getEventLog(MeasurementType type) {
+ return eventLogMap.get(type.toString());
+ }
+
+ @Override public boolean equals(Object o) {
+ return o instanceof ScenarioResult
+ && ((ScenarioResult) o).measurementSetMap.equals(measurementSetMap)
+ && ((ScenarioResult) o).eventLogMap.equals(eventLogMap);
+ }
+
+ @Override public int hashCode() {
+ return measurementSetMap.hashCode() * 37 + eventLogMap.hashCode();
+ }
+
+ @Override public String toString() {
+ return "measurementSetMap: " + measurementSetMap + ", eventLogMap: " + eventLogMap;
+ }
+
+ @SuppressWarnings("unused")
+ private ScenarioResult() {} // for GWT Serialization
+}
diff --git a/caliper/src/main/java/com/google/caliper/ScenarioSelection.java b/caliper/src/main/java/com/google/caliper/ScenarioSelection.java
new file mode 100644
index 0000000..fcfead0
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/ScenarioSelection.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import com.google.caliper.UserException.AbstractBenchmarkException;
+import com.google.caliper.UserException.DoesntImplementBenchmarkException;
+import com.google.caliper.UserException.ExceptionFromUserCodeException;
+import com.google.caliper.UserException.NoParameterlessConstructorException;
+import com.google.caliper.UserException.NoSuchClassException;
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Multimap;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * Figures out which scenarios to benchmark given a benchmark suite, set of user
+ * parameters, and set of user VMs.
+ */
+public final class ScenarioSelection {
+
+ private final Set<String> userVms;
+ private final Multimap<String, String> vmParameters;
+ private final String suiteClassName;
+
+ /**
+ * The user parameters specified on the command line. This may be a subset of
+ * the effective user parameters because parameters not specified here may get
+ * default values from the benchmark class.
+ */
+ private final Multimap<String, String> userParameterArguments;
+
+ /**
+ * The actual user parameters we'll use to run in the benchmark. This contains
+ * the userParameterArguments plus the default user parameters.
+ */
+ private Multimap<String, String> userParameters;
+
+ private final int trials;
+ private Benchmark suite;
+
+
+ public ScenarioSelection(Arguments arguments) {
+ this(arguments.getUserVms(), arguments.getVmParameters(), arguments.getSuiteClassName(),
+ arguments.getUserParameters(), arguments.getTrials());
+ }
+
+ public ScenarioSelection(Set<String> userVms, Multimap<String, String> vmParameters,
+ String suiteClassName, Multimap<String, String> userParameterArguments, int trials) {
+ this.userVms = userVms;
+ this.vmParameters = vmParameters;
+ this.suiteClassName = suiteClassName;
+ this.userParameterArguments = userParameterArguments;
+ this.trials = trials;
+ }
+
+ /**
+ * Returns the selected scenarios for this benchmark.
+ */
+ public List<Scenario> select() {
+ prepareSuite();
+ userParameters = computeUserParameters();
+ return createScenarios();
+ }
+
+ /**
+ * Returns a normalized version of {@code scenario}, with information from {@code suite}
+ * assisting in correcting problems.
+ */
+ public Scenario normalizeScenario(Scenario scenario) {
+ // This only applies to SimpleBenchmarks since they accept the special "benchmark"
+ // parameter. This is a special case because SimpleBenchmark is the most commonly
+ // used benchmark class. Have to do this horrible stuff since Benchmark API
+ // doesn't provide scenario-normalization (and it shouldn't), which SimpleBenchmark
+ // requires.
+ if (suite instanceof SimpleBenchmark) {
+ return ((SimpleBenchmark) suite).normalizeScenario(scenario);
+ }
+
+ return scenario;
+ }
+
+ public Set<String> getUserParameterNames() {
+ if (userParameters == null) {
+ throw new IllegalStateException();
+ }
+ return userParameters.keySet();
+ }
+
+ public Set<String> getVmParameterNames() {
+ return vmParameters.keySet();
+ }
+
+ public ConfiguredBenchmark createBenchmark(Scenario scenario) {
+ return suite.createBenchmark(scenario.getVariables(getUserParameterNames()));
+ }
+
+ private void prepareSuite() {
+ Class<?> benchmarkClass;
+ try {
+ benchmarkClass = getClassByName(suiteClassName);
+ } catch (ExceptionInInitializerError e) {
+ throw new ExceptionFromUserCodeException(e.getCause());
+ } catch (ClassNotFoundException ignored) {
+ throw new NoSuchClassException(suiteClassName);
+ }
+
+ Object s;
+ try {
+ Constructor<?> constructor = benchmarkClass.getDeclaredConstructor();
+ constructor.setAccessible(true);
+ s = constructor.newInstance();
+ } catch (InstantiationException ignore) {
+ throw new AbstractBenchmarkException(benchmarkClass);
+ } catch (NoSuchMethodException ignore) {
+ throw new NoParameterlessConstructorException(benchmarkClass);
+ } catch (IllegalAccessException impossible) {
+ throw new AssertionError(impossible); // shouldn't happen since we setAccessible(true)
+ } catch (InvocationTargetException e) {
+ throw new ExceptionFromUserCodeException(e.getCause());
+ }
+
+ if (s instanceof Benchmark) {
+ this.suite = (Benchmark) s;
+ } else {
+ throw new DoesntImplementBenchmarkException(benchmarkClass);
+ }
+ }
+
+ private static Class<?> getClassByName(String className) throws ClassNotFoundException {
+ try {
+ return Class.forName(className);
+ } catch (ClassNotFoundException ignored) {
+ // try replacing the last dot with a $, in case that helps
+ // example: tutorial.Tutorial.Benchmark1 becomes tutorial.Tutorial$Benchmark1
+ // amusingly, the $ character means three different things in this one line alone
+ String newName = className.replaceFirst("\\.([^.]+)$", "\\$$1");
+ return Class.forName(newName);
+ }
+ }
+
+ private Multimap<String, String> computeUserParameters() {
+ Multimap<String, String> result = LinkedHashMultimap.create();
+ for (String key : suite.parameterNames()) {
+ // first check if the user has specified values
+ Collection<String> userValues = userParameterArguments.get(key);
+ if (!userValues.isEmpty()) {
+ result.putAll(key, userValues);
+ // TODO: type convert 'em to validate?
+
+ } else { // otherwise use the default values from the suite
+ Set<String> values = suite.parameterValues(key);
+ if (values.isEmpty()) {
+ throw new ConfigurationException(key + " has no values. "
+ + "Did you forget a -D" + key + "=<value> command line argument?");
+ }
+ result.putAll(key, values);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns a complete set of scenarios with every combination of variables.
+ */
+ private List<Scenario> createScenarios() {
+ List<ScenarioBuilder> builders = new ArrayList<ScenarioBuilder>();
+ builders.add(new ScenarioBuilder());
+
+ Map<String, Collection<String>> variables = new LinkedHashMap<String, Collection<String>>();
+ variables.put(Scenario.VM_KEY, userVms.isEmpty() ? VmFactory.defaultVms() : userVms);
+ variables.put(Scenario.TRIAL_KEY, newListOfSize(trials));
+ variables.putAll(userParameters.asMap());
+ variables.putAll(vmParameters.asMap());
+
+ for (Entry<String, Collection<String>> entry : variables.entrySet()) {
+ Iterator<String> values = entry.getValue().iterator();
+ if (!values.hasNext()) {
+ throw new ConfigurationException("Not enough values for " + entry);
+ }
+
+ String firstValue = values.next();
+ for (ScenarioBuilder builder : builders) {
+ builder.variables.put(entry.getKey(), firstValue);
+ }
+
+ // multiply the size of the specs by the number of alternate values
+ int size = builders.size();
+ while (values.hasNext()) {
+ String alternate = values.next();
+ for (int s = 0; s < size; s++) {
+ ScenarioBuilder copy = builders.get(s).copy();
+ copy.variables.put(entry.getKey(), alternate);
+ builders.add(copy);
+ }
+ }
+ }
+
+ List<Scenario> result = new ArrayList<Scenario>();
+ for (ScenarioBuilder builder : builders) {
+ result.add(normalizeScenario(builder.build()));
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns a list containing {@code count} distinct elements.
+ */
+ private Collection<String> newListOfSize(int count) {
+ List<String> result = new ArrayList<String>();
+ for (int i = 0; i < count; i++) {
+ result.add(Integer.toString(i));
+ }
+ return result;
+ }
+
+ private static class ScenarioBuilder {
+ final Map<String, String> variables = new LinkedHashMap<String, String>();
+
+ ScenarioBuilder copy() {
+ ScenarioBuilder result = new ScenarioBuilder();
+ result.variables.putAll(variables);
+ return result;
+ }
+
+ public Scenario build() {
+ return new Scenario(variables);
+ }
+ }
+}
diff --git a/caliper/src/main/java/com/google/caliper/SimpleBenchmark.java b/caliper/src/main/java/com/google/caliper/SimpleBenchmark.java
new file mode 100644
index 0000000..01fc3a8
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/SimpleBenchmark.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import com.google.caliper.UserException.ExceptionFromUserCodeException;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A convenience class for implementing benchmarks in plain code.
+ * Implementing classes must have a no-arguments constructor.
+ *
+ * <h3>Benchmarks</h3>
+ * The benchmarks of a suite are defined by . They may be
+ * static. They are not permitted to take parameters . . ..
+ *
+ * <h3>Parameters</h3>
+ * See the {@link Param} documentation to learn about parameters.
+ */
+public abstract class SimpleBenchmark
+ implements Benchmark {
+ private static final Class<?>[] ARGUMENT_TYPES = { int.class };
+
+ private final Map<String, Parameter<?>> parameters;
+ private final Map<String, Method> methods;
+
+ protected SimpleBenchmark() {
+ parameters = Parameter.forClass(getClass());
+ methods = createTimedMethods();
+
+ if (methods.isEmpty()) {
+ throw new ConfigurationException(
+ "No benchmarks defined in " + getClass().getName());
+ }
+ }
+
+ protected void setUp() throws Exception {}
+
+ protected void tearDown() throws Exception {}
+
+ @Override public Set<String> parameterNames() {
+ return ImmutableSet.<String>builder()
+ .add("benchmark")
+ .addAll(parameters.keySet())
+ .build();
+ }
+
+ @Override public Set<String> parameterValues(String parameterName) {
+ if ("benchmark".equals(parameterName)) {
+ return methods.keySet();
+ }
+
+ Parameter<?> parameter = parameters.get(parameterName);
+ if (parameter == null) {
+ throw new IllegalArgumentException();
+ }
+ try {
+ Iterable<?> values = parameter.values();
+
+ ImmutableSet.Builder<String> result = ImmutableSet.builder();
+ for (Object value : values) {
+ result.add(String.valueOf(value));
+ }
+ return result.build();
+ } catch (Exception e) {
+ throw new ExceptionFromUserCodeException(e);
+ }
+ }
+
+ @Override public ConfiguredBenchmark createBenchmark(Map<String, String> parameterValues) {
+ if (!parameterNames().equals(parameterValues.keySet())) {
+ throw new IllegalArgumentException("Invalid parameters specified. Expected "
+ + parameterNames() + " but was " + parameterValues.keySet());
+ }
+
+ String methodName = parameterValues.get("benchmark");
+ final Method method = methods.get(methodName);
+ if (method == null) {
+ throw new IllegalArgumentException("Invalid parameters specified. \"time" + methodName + "\" "
+ + "is not a method of this benchmark.");
+ }
+
+ try {
+ @SuppressWarnings({"ClassNewInstance"}) // can throw any Exception, so we catch all Exceptions
+ final SimpleBenchmark copyOfSelf = getClass().newInstance();
+
+ for (Map.Entry<String, String> entry : parameterValues.entrySet()) {
+ String parameterName = entry.getKey();
+ if ("benchmark".equals(parameterName)) {
+ continue;
+ }
+
+ Parameter<?> parameter = parameters.get(parameterName);
+ Object value = TypeConverter.fromString(entry.getValue(), parameter.getType());
+ parameter.set(copyOfSelf, value);
+ }
+ copyOfSelf.setUp();
+
+ return new ConfiguredBenchmark(copyOfSelf) {
+ @Override public Object run(int reps) throws Exception {
+ try {
+ return method.invoke(copyOfSelf, reps);
+ } catch (InvocationTargetException e) {
+ Throwable cause = e.getCause();
+ if (cause instanceof Exception) {
+ throw (Exception) cause;
+ } else if (cause instanceof Error) {
+ throw (Error) cause;
+ } else {
+ throw e;
+ }
+ }
+ }
+
+ @Override public void close() throws Exception {
+ copyOfSelf.tearDown();
+ }
+ };
+ } catch (Exception e) {
+ throw new ExceptionFromUserCodeException(e);
+ }
+ }
+
+ public Scenario normalizeScenario(Scenario scenario) {
+ Map<String, String> variables =
+ new LinkedHashMap<String, String>(scenario.getVariables());
+ // Make sure the scenario contains method names without the prefixed "time". If
+ // it has "time" prefixed, then remove it. Also check whether the user has
+ // accidentally put a lower cased letter first, and fix it if necessary.
+ String benchmark = variables.get("benchmark");
+ Map<String, Method> timedMethods = createTimedMethods();
+ if (timedMethods.get(benchmark) == null) {
+ // try to upper case first character
+ char[] benchmarkChars = benchmark.toCharArray();
+ benchmarkChars[0] = Character.toUpperCase(benchmarkChars[0]);
+ String upperCasedBenchmark = String.valueOf(benchmarkChars);
+ if (timedMethods.get(upperCasedBenchmark) != null) {
+ variables.put("benchmark", upperCasedBenchmark);
+ } else if (benchmark.startsWith("time")) {
+ variables.put("benchmark", benchmark.substring(4));
+ }
+ }
+ return new Scenario(variables);
+ }
+
+ /**
+ * Returns a spec for each benchmark defined in the specified class. The
+ * returned specs have no parameter values; those must be added separately.
+ */
+ private Map<String, Method> createTimedMethods() {
+ ImmutableMap.Builder<String, Method> result = ImmutableMap.builder();
+ for (Method method : getClass().getDeclaredMethods()) {
+ int modifiers = method.getModifiers();
+ if (!method.getName().startsWith("time")) {
+ continue;
+ }
+
+ if (!Modifier.isPublic(modifiers)
+ || Modifier.isStatic(modifiers)
+ || Modifier.isAbstract(modifiers)
+ || !Arrays.equals(method.getParameterTypes(), ARGUMENT_TYPES)) {
+ throw new ConfigurationException("Timed methods must be public, "
+ + "non-static, non-abstract and take a single int parameter. "
+ + "But " + method + " violates these requirements.");
+ }
+
+ result.put(method.getName().substring(4), method);
+ }
+
+ return result.build();
+ }
+
+ @Override public Map<String, Integer> getTimeUnitNames() {
+ return ImmutableMap.of("ns", 1,
+ "us", 1000,
+ "ms", 1000000,
+ "s", 1000000000);
+ }
+
+ @Override public double nanosToUnits(double nanos) {
+ return nanos;
+ }
+
+ @Override public Map<String, Integer> getInstanceUnitNames() {
+ return ImmutableMap.of(" instances", 1,
+ "K instances", 1000,
+ "M instances", 1000000,
+ "B instances", 1000000000);
+ }
+
+ @Override public double instancesToUnits(long instances) {
+ return instances;
+ }
+
+ @Override public Map<String, Integer> getMemoryUnitNames() {
+ return ImmutableMap.of("B", 1,
+ "KiB", 1024,
+ "MiB", 1048576,
+ "GiB", 1073741824);
+ }
+
+ @Override public double bytesToUnits(long bytes) {
+ return bytes;
+ }
+}
diff --git a/caliper/src/main/java/com/google/caliper/StandardVm.java b/caliper/src/main/java/com/google/caliper/StandardVm.java
new file mode 100644
index 0000000..3366eb1
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/StandardVm.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+import java.util.ArrayList;
+import java.util.List;
+
+final class StandardVm extends Vm {
+
+ @Override public List<String> getVmSpecificOptions(MeasurementType type, Arguments arguments) {
+ if (!arguments.getCaptureVmLog()) {
+ return ImmutableList.of();
+ }
+
+ List<String> result = new ArrayList<String>();
+ result.add("-verbose:gc");
+ result.add("-Xbatch");
+ result.add("-XX:+UseSerialGC");
+ if (type == MeasurementType.TIME) {
+ return Lists.newArrayList("-XX:+PrintCompilation");
+ }
+
+ return result;
+ }
+
+ public static String defaultVmName() {
+ return "java";
+ }
+}
diff --git a/caliper/src/main/java/com/google/caliper/TimeMeasurer.java b/caliper/src/main/java/com/google/caliper/TimeMeasurer.java
new file mode 100644
index 0000000..2925d2d
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/TimeMeasurer.java
@@ -0,0 +1,180 @@
+/**
+ * Copyright (C) 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.caliper.UserException.DoesNotScaleLinearlyException;
+import com.google.caliper.UserException.RuntimeOutOfRangeException;
+import com.google.common.base.Supplier;
+
+/**
+ * Measure's the benchmark's per-trial execution time.
+ */
+class TimeMeasurer extends Measurer {
+
+ private final long warmupNanos;
+ private final long runNanos;
+
+ /**
+ * If the standard deviation of our measurements is within this tolerance, we
+ * won't bother to perform additional measurements.
+ */
+ private static final double SHORT_CIRCUIT_TOLERANCE = 0.01;
+
+ private static final int MAX_TRIALS = 10;
+
+ TimeMeasurer(long warmupMillis, long runMillis) {
+ checkArgument(warmupMillis > 50);
+ checkArgument(runMillis > 50);
+
+ this.warmupNanos = warmupMillis * 1000000;
+ this.runNanos = runMillis * 1000000;
+ }
+
+ private double warmUp(Supplier<ConfiguredBenchmark> testSupplier) throws Exception {
+ long elapsedNanos = 0;
+ long netReps = 0;
+ int reps = 1;
+ boolean definitelyScalesLinearly = false;
+
+ /*
+ * Run progressively more reps at a time until we cross our warmup
+ * threshold. This way any just-in-time compiler will be comfortable running
+ * multiple iterations of our measurement method.
+ */
+ log("[starting warmup]");
+ while (elapsedNanos < warmupNanos) {
+ long nanos = measureReps(testSupplier.get(), reps);
+ elapsedNanos += nanos;
+
+ netReps += reps;
+ reps *= 2;
+
+ // if reps overflowed, that's suspicious! Check that it time scales with reps
+ if (reps <= 0) {
+ if (!definitelyScalesLinearly) {
+ checkScalesLinearly(testSupplier);
+ definitelyScalesLinearly = true;
+ }
+ reps = Integer.MAX_VALUE;
+ }
+ }
+ log("[ending warmup]");
+
+ double nanosPerExecution = (double) elapsedNanos / netReps;
+ double lowerBound = 0.1;
+ double upperBound = 10000000000.0;
+ if (!(lowerBound <= nanosPerExecution && nanosPerExecution <= upperBound)) {
+ throw new RuntimeOutOfRangeException(nanosPerExecution, lowerBound, upperBound);
+ }
+
+ return nanosPerExecution;
+ }
+
+ /**
+ * Doing half as much work shouldn't take much more than half as much time. If
+ * it does we have a broken benchmark!
+ */
+ private void checkScalesLinearly(Supplier<ConfiguredBenchmark> testSupplier) throws Exception {
+ double half = measureReps(testSupplier.get(), Integer.MAX_VALUE / 2);
+ double one = measureReps(testSupplier.get(), Integer.MAX_VALUE);
+ if (half / one > 0.75) {
+ throw new DoesNotScaleLinearlyException();
+ }
+ }
+
+ /**
+ * Measure the nanos per rep for the given test. This code uses an interesting
+ * strategy to measure the runtime to minimize execution time when execution
+ * time is consistent.
+ * <ol>
+ * <li>1.0x {@code runMillis} trial is run.
+ * <li>0.5x {@code runMillis} trial is run.
+ * <li>1.5x {@code runMillis} trial is run.
+ * <li>At this point, the standard deviation of these trials is computed. If
+ * it is within the threshold, the result is returned.
+ * <li>Otherwise trials continue to be executed until either the threshold
+ * is satisfied or the maximum number of runs have been executed.
+ * </ol>
+ *
+ * @param testSupplier provides instances of the code under test. A new test
+ * is created for each iteration because some benchmarks' performance
+ * depends on which memory was allocated. See SetContainsBenchmark for an
+ * example.
+ */
+ @Override public MeasurementSet run(Supplier<ConfiguredBenchmark> testSupplier)
+ throws Exception {
+ double estimatedNanosPerRep = warmUp(testSupplier);
+
+ log("[measuring nanos per rep with scale 1.00]");
+ Measurement measurement100 = measure(testSupplier, 1.00, estimatedNanosPerRep);
+ log("[measuring nanos per rep with scale 0.50]");
+ Measurement measurement050 = measure(testSupplier, 0.50, measurement100.getRaw());
+ log("[measuring nanos per rep with scale 1.50]");
+ Measurement measurement150 = measure(testSupplier, 1.50, measurement100.getRaw());
+ MeasurementSet measurementSet =
+ new MeasurementSet(measurement100, measurement050, measurement150);
+
+ for (int i = 3; i < MAX_TRIALS; i++) {
+ double threshold = SHORT_CIRCUIT_TOLERANCE * measurementSet.meanRaw();
+ if (measurementSet.standardDeviationRaw() < threshold) {
+ return measurementSet;
+ }
+
+ log("[performing additional measurement with scale 1.00]");
+ Measurement measurement = measure(testSupplier, 1.00, measurement100.getRaw());
+ measurementSet = measurementSet.plusMeasurement(measurement);
+ }
+
+ return measurementSet;
+ }
+
+ /**
+ * Runs the test method for approximately {@code runNanos * durationScale}
+ * nanos and returns a Measurement of the nanos per rep and units per rep.
+ */
+ private Measurement measure(Supplier<ConfiguredBenchmark> testSupplier,
+ double durationScale, double estimatedNanosPerRep) throws Exception {
+ int reps = (int) (durationScale * runNanos / estimatedNanosPerRep);
+ if (reps == 0) {
+ reps = 1;
+ }
+
+ log("[running trial with " + reps + " reps]");
+ ConfiguredBenchmark benchmark = testSupplier.get();
+ long elapsedTime = measureReps(benchmark, reps);
+ double nanosPerRep = elapsedTime / (double) reps;
+ log(String.format("[took %.2f nanoseconds per rep]", nanosPerRep));
+ return new Measurement(benchmark.timeUnitNames(), nanosPerRep,
+ benchmark.nanosToUnits(nanosPerRep));
+ }
+
+ /**
+ * Returns the total nanos to run {@code reps}.
+ */
+ private long measureReps(ConfiguredBenchmark benchmark, int reps) throws Exception {
+ prepareForTest();
+ log(LogConstants.MEASURED_SECTION_STARTING);
+ long startNanos = System.nanoTime();
+ benchmark.run(reps);
+ long endNanos = System.nanoTime();
+ log(LogConstants.MEASURED_SECTION_DONE);
+ benchmark.close();
+ return endNanos - startNanos;
+ }
+}
diff --git a/caliper/src/main/java/com/google/caliper/TypeConverter.java b/caliper/src/main/java/com/google/caliper/TypeConverter.java
new file mode 100644
index 0000000..c3268e0
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/TypeConverter.java
@@ -0,0 +1,65 @@
+/**
+ * Copyright (C) 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import com.google.common.collect.ImmutableMap;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.Map;
+
+/**
+ * Convert objects to and from Strings.
+ */
+final class TypeConverter {
+ private TypeConverter() {}
+
+ public static Object fromString(String value, Type type) {
+ if (type == String.class) {
+ return value;
+ }
+
+ Class<?> c = wrap((Class<?>) type);
+ try {
+ Method m = c.getMethod("valueOf", String.class);
+ m.setAccessible(true); // to permit inner enums, etc.
+ return m.invoke(null, value);
+ } catch (Exception e) {
+ throw new UnsupportedOperationException(
+ "Cannot convert " + value + " of type " + type, e);
+ }
+ }
+
+ // safe because both Long.class and long.class are of type Class<Long>
+ @SuppressWarnings("unchecked")
+ private static <T> Class<T> wrap(Class<T> c) {
+ return c.isPrimitive() ? (Class<T>) PRIMITIVES_TO_WRAPPERS.get(c) : c;
+ }
+
+ private static final Map<Class<?>, Class<?>> PRIMITIVES_TO_WRAPPERS
+ = new ImmutableMap.Builder<Class<?>, Class<?>>()
+ .put(boolean.class, Boolean.class)
+ .put(byte.class, Byte.class)
+ .put(char.class, Character.class)
+ .put(double.class, Double.class)
+ .put(float.class, Float.class)
+ .put(int.class, Integer.class)
+ .put(long.class, Long.class)
+ .put(short.class, Short.class)
+ .put(void.class, Void.class)
+ .build();
+}
diff --git a/caliper/src/main/java/com/google/caliper/UploadResults.java b/caliper/src/main/java/com/google/caliper/UploadResults.java
new file mode 100644
index 0000000..7dae7af
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/UploadResults.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import java.io.File;
+
+/**
+ * Usage: UploadResults <file_or_dir>
+ */
+public class UploadResults {
+ public static void main(String[] args) {
+ new Runner().uploadResultsFileOrDir(new File(args[0]));
+ }
+}
diff --git a/caliper/src/main/java/com/google/caliper/UserException.java b/caliper/src/main/java/com/google/caliper/UserException.java
new file mode 100644
index 0000000..a4535a7
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/UserException.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import java.util.Arrays;
+import java.util.Set;
+
+/**
+ * Signifies a problem that should be explained in user-friendly terms on the command line, without
+ * a confusing stack trace, and optionally followed by a usage summary.
+ */
+@SuppressWarnings("serial") // never going to serialize these... right?
+public abstract class UserException extends RuntimeException {
+ protected UserException(String error) {
+ super(error);
+ }
+
+ public abstract void display();
+
+ // - - - -
+
+ public abstract static class ErrorInUsageException extends UserException {
+ protected ErrorInUsageException(String error) {
+ super(error);
+ }
+
+ @Override public void display() {
+ String message = getMessage();
+ if (message != null) {
+ System.err.println("Error: " + message);
+ }
+ Arguments.printUsage();
+ }
+ }
+
+ public abstract static class ErrorInUserCodeException extends UserException {
+ private final String remedy;
+
+ protected ErrorInUserCodeException(String error, String remedy) {
+ super(error);
+ this.remedy = remedy;
+ }
+
+ @Override public void display() {
+ System.err.println("Error: " + getMessage());
+ System.err.println("Typical Remedy: " + remedy);
+ }
+ }
+
+ // - - - -
+
+ // Not technically an error, but works nicely this way anyway
+ public static class DisplayUsageException extends ErrorInUsageException {
+ public DisplayUsageException() {
+ super(null);
+ }
+ }
+
+ public static class IncompatibleArgumentsException extends ErrorInUsageException {
+ public IncompatibleArgumentsException(String arg) {
+ super("Some arguments passed in are incompatible with: " + arg);
+ }
+ }
+
+ public static class UnrecognizedOptionException extends ErrorInUsageException {
+ public UnrecognizedOptionException(String arg) {
+ super("Argument not recognized: " + arg);
+ }
+ }
+
+ public static class NoBenchmarkClassException extends ErrorInUsageException {
+ public NoBenchmarkClassException() {
+ super("No benchmark class specified.");
+ }
+ }
+
+ public static class MultipleBenchmarkClassesException extends ErrorInUsageException {
+ public MultipleBenchmarkClassesException(String a, String b) {
+ super("Multiple benchmark classes specified: " + Arrays.asList(a, b));
+ }
+ }
+
+ public static class MalformedParameterException extends ErrorInUsageException {
+ public MalformedParameterException(String arg) {
+ super("Malformed parameter: " + arg);
+ }
+ }
+
+ public static class DuplicateParameterException extends ErrorInUsageException {
+ public DuplicateParameterException(String arg) {
+ super("Duplicate parameter: " + arg);
+ }
+ public DuplicateParameterException(Set<String> arg) {
+ super("Duplicate parameters: " + arg);
+ }
+ }
+
+ public static class InvalidParameterValueException extends ErrorInUsageException {
+ public InvalidParameterValueException(String arg, String value) {
+ super("Invalid value \"" + value + "\" for parameter: " + arg);
+ }
+ }
+
+ public static class InvalidTrialsException extends ErrorInUsageException {
+ public InvalidTrialsException(String arg) {
+ super("Invalid trials: " + arg);
+ }
+ }
+
+ public static class CantCustomizeInProcessVmException extends ErrorInUsageException {
+ public CantCustomizeInProcessVmException() {
+ super("Can't customize VM when running in process.");
+ }
+ }
+
+ public static class NoSuchClassException extends ErrorInUsageException {
+ public NoSuchClassException(String name) {
+ super("No class named [" + name + "] was found (check CLASSPATH).");
+ }
+ }
+
+ public static class RuntimeOutOfRangeException extends ErrorInUsageException {
+ public RuntimeOutOfRangeException(
+ double nanosPerExecution, double lowerBound, double upperBound) {
+ super("Runtime " + nanosPerExecution + "ns/rep out of range "
+ + lowerBound + "-" + upperBound);
+ }
+ }
+
+ public static class DoesNotScaleLinearlyException extends ErrorInUsageException {
+ public DoesNotScaleLinearlyException() {
+ super("Doing 2x as much work didn't take 2x as much time! "
+ + "Is the JIT optimizing away the body of your benchmark?");
+ }
+ }
+
+ public static class NonConstantMemoryUsage extends ErrorInUsageException {
+ public NonConstantMemoryUsage() {
+ super("Not all reps of the inner loop allocate the same number of times! "
+ + "The reps loop should use a constant number of allocations. "
+ + "Are you using the value of reps inside the loop?");
+ }
+ }
+
+ public static class AbstractBenchmarkException extends ErrorInUserCodeException {
+ public AbstractBenchmarkException(Class<?> specifiedClass) {
+ super("Class [" + specifiedClass.getName() + "] is abstract.", "Specify a concrete class.");
+ }
+ }
+
+ public static class NoParameterlessConstructorException extends ErrorInUserCodeException {
+ public NoParameterlessConstructorException(Class<?> specifiedClass) {
+ super("Class [" + specifiedClass.getName() + "] has no parameterless constructor.",
+ "Remove all constructors or add a parameterless constructor.");
+ }
+ }
+
+ public static class DoesntImplementBenchmarkException extends ErrorInUserCodeException {
+ public DoesntImplementBenchmarkException(Class<?> specifiedClass) {
+ super("Class [" + specifiedClass + "] does not implement the " + Benchmark.class.getName()
+ + " interface.", "Add 'extends " + SimpleBenchmark.class + "' to the class declaration.");
+ }
+ }
+
+ public static class InvalidDebugRepsException extends ErrorInUsageException {
+ public InvalidDebugRepsException(String arg) {
+ super("Invalid debug reps: " + arg);
+ }
+ }
+
+ // TODO: should remove the caliper stack frames....
+ public static class ExceptionFromUserCodeException extends UserException {
+ public ExceptionFromUserCodeException(Throwable t) {
+ super("An exception was thrown from the benchmark code.");
+ initCause(t);
+ }
+ @Override public void display() {
+ System.err.println(getMessage());
+ getCause().printStackTrace(System.err);
+ }
+ }
+}
diff --git a/caliper/src/main/java/com/google/caliper/Vm.java b/caliper/src/main/java/com/google/caliper/Vm.java
new file mode 100644
index 0000000..1cc45a9
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/Vm.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import com.google.common.collect.ImmutableList;
+
+import java.io.File;
+import java.util.List;
+
+class Vm {
+ public List<String> getVmSpecificOptions(MeasurementType type, Arguments arguments) {
+ return ImmutableList.of();
+ }
+
+ /**
+ * Returns a process builder to run this VM.
+ *
+ * @param vmArgs the path to the VM followed by VM arguments.
+ * @param applicationArgs arguments to the target process
+ */
+ public ProcessBuilder newProcessBuilder(File workingDirectory, String classPath,
+ ImmutableList<String> vmArgs, String className, ImmutableList<String> applicationArgs) {
+ ProcessBuilder result = new ProcessBuilder();
+ result.directory(workingDirectory);
+ result.command().addAll(vmArgs);
+ result.command().add("-cp");
+ result.command().add(classPath);
+ result.command().add(className);
+ result.command().addAll(applicationArgs);
+ return result;
+ }
+}
diff --git a/caliper/src/main/java/com/google/caliper/VmFactory.java b/caliper/src/main/java/com/google/caliper/VmFactory.java
new file mode 100644
index 0000000..408e34c
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/VmFactory.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import com.google.common.collect.ImmutableSet;
+
+import java.util.Arrays;
+import java.util.List;
+
+public final class VmFactory {
+ public static ImmutableSet<String> defaultVms() {
+ String vmName = DalvikVm.isDalvikVm()
+ ? DalvikVm.vmName()
+ : StandardVm.defaultVmName();
+ return ImmutableSet.of(vmName);
+ }
+
+ public Vm createVm(Scenario scenario) {
+ List<String> vmList = Arrays.asList(scenario.getVariables().get(Scenario.VM_KEY).split("\\s+"));
+ Vm vm = null;
+ if (!vmList.isEmpty()) {
+ if (vmList.get(0).endsWith("app_process")) {
+ vm = new DalvikVm();
+ } else if (vmList.get(0).endsWith("java")) {
+ vm = new StandardVm();
+ }
+ }
+ if (vm == null) {
+ vm = new Vm();
+ }
+ return vm;
+ }
+}
diff --git a/caliper/src/main/java/com/google/caliper/Xml.java b/caliper/src/main/java/com/google/caliper/Xml.java
new file mode 100644
index 0000000..2aeebda
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/Xml.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import com.google.common.collect.ImmutableMap;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import java.io.InputStream;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+
+/**
+ * This exists for backwards compatibility with old data, which is stored in XML format.
+ * All new data is stored in JSON.
+ */
+public final class Xml {
+ private static final String DATE_FORMAT_STRING = "yyyy-MM-dd'T'HH:mm:ssz";
+ private static final String ENVIRONMENT_ELEMENT_NAME = "environment";
+ private static final String RESULT_ELEMENT_NAME = "result";
+ private static final String RUN_ELEMENT_NAME = "run";
+ private static final String BENCHMARK_ATTRIBUTE = "benchmark";
+ private static final String EXECUTED_TIMESTAMP_ATTRIBUTE = "executedTimestamp";
+ private static final String OLD_SCENARIO_ELEMENT_NAME = "scenario";
+ // for backwards compatibility, use a different name
+ private static final String SCENARIO_ELEMENT_NAME = "newScenario";
+ private static final String MEASUREMENTS_ELEMENT_NAME = "measurements";
+ private static final String TIME_EVENT_LOG_ELEMENT_NAME = "eventLog";
+
+ private static Result readResultElement(Element element) throws Exception {
+ Environment environment = null;
+ Run run = null;
+ for (Node topLevelNode : XmlUtils.childrenOf(element)) {
+ if (topLevelNode.getNodeName().equals(ENVIRONMENT_ELEMENT_NAME)) {
+ Element environmentElement = (Element) topLevelNode;
+ environment = readEnvironmentElement(environmentElement);
+ } else if (topLevelNode.getNodeName().equals(RUN_ELEMENT_NAME)) {
+ run = readRunElement((Element) topLevelNode);
+ } else {
+ throw new RuntimeException("illegal node name: " + topLevelNode.getNodeName());
+ }
+ }
+
+ if (environment == null || run == null) {
+ throw new RuntimeException("missing environment or run elements");
+ }
+
+ return new Result(run, environment);
+ }
+
+ private static Environment readEnvironmentElement(Element element) {
+ return new Environment(XmlUtils.attributesOf(element));
+ }
+
+ private static Run readRunElement(Element element) throws Exception {
+ String benchmarkName = element.getAttribute(BENCHMARK_ATTRIBUTE);
+ String executedDateString = element.getAttribute(EXECUTED_TIMESTAMP_ATTRIBUTE);
+ Date executedDate = new SimpleDateFormat(DATE_FORMAT_STRING, Locale.US)
+ .parse(executedDateString);
+
+ ImmutableMap.Builder<Scenario, ScenarioResult> measurementsBuilder = ImmutableMap.builder();
+ for (Node scenarioNode : XmlUtils.childrenOf(element)) {
+ Element scenarioElement = (Element) scenarioNode;
+ Scenario scenario = new Scenario(XmlUtils.attributesOf(scenarioElement));
+ ScenarioResult scenarioResult;
+
+ // for backwards compatibility with older runs
+ if (scenarioNode.getNodeName().equals(OLD_SCENARIO_ELEMENT_NAME)) {
+ MeasurementSet measurement =
+ Json.measurementSetFromJson(scenarioElement.getTextContent());
+ scenarioResult = new ScenarioResult(measurement, "",
+ null, null, null, null);
+ } else if (scenarioNode.getNodeName().equals(SCENARIO_ELEMENT_NAME)) {
+ MeasurementSet timeMeasurementSet = null;
+ String eventLog = null;
+ for (Node node : XmlUtils.childrenOf(scenarioElement)) {
+ if (node.getNodeName().equals(MEASUREMENTS_ELEMENT_NAME)) {
+ timeMeasurementSet = Json.measurementSetFromJson(node.getTextContent());
+ } else if (node.getNodeName().equals(TIME_EVENT_LOG_ELEMENT_NAME)) {
+ eventLog = node.getTextContent();
+ } else {
+ throw new RuntimeException("illegal node name: " + node.getNodeName());
+ }
+ }
+ if (timeMeasurementSet == null || eventLog == null) {
+ throw new RuntimeException("missing node \"" + MEASUREMENTS_ELEMENT_NAME + "\" or \""
+ + TIME_EVENT_LOG_ELEMENT_NAME + "\"");
+ }
+ // "new Measurement[0]" used instead of empty varargs argument since MeasurementSet has
+ // an empty private constructor.
+ scenarioResult = new ScenarioResult(timeMeasurementSet, eventLog,
+ null, null, null, null);
+ } else {
+ throw new RuntimeException("illegal node name: " + scenarioNode.getNodeName());
+ }
+
+ measurementsBuilder.put(scenario, scenarioResult);
+ }
+
+ return new Run(measurementsBuilder.build(), benchmarkName, executedDate);
+ }
+
+ /**
+ * Creates a result by decoding XML from the specified stream. The XML should
+ * be consistent with the format emitted by the now deleted runToXml(Run, OutputStream).
+ */
+ public static Run runFromXml(InputStream in) {
+ try {
+ Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(in);
+ return readRunElement(document.getDocumentElement());
+ } catch (Exception e) {
+ throw new IllegalStateException("Malformed XML document", e);
+ }
+ }
+
+ /**
+ * Creates an environment by decoding XML from the specified stream. The XML should
+ * be consistent with the format emitted by the now deleted
+ * environmentToXml(Environment, OutputStream).
+ */
+ public static Environment environmentFromXml(InputStream in) {
+ try {
+ Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(in);
+ Element environmentElement = document.getDocumentElement();
+ return readEnvironmentElement(environmentElement);
+ } catch (Exception e) {
+ throw new IllegalStateException("Malformed XML document", e);
+ }
+ }
+
+ public static Result resultFromXml(InputStream in) {
+ try {
+ Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(in);
+ return readResultElement(document.getDocumentElement());
+ } catch (Exception e) {
+ throw new IllegalStateException("Malformed XML document", e);
+ }
+ }
+
+ private Xml() {}
+}
diff --git a/caliper/src/main/java/com/google/caliper/XmlUtils.java b/caliper/src/main/java/com/google/caliper/XmlUtils.java
new file mode 100644
index 0000000..458e6fa
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/XmlUtils.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+public final class XmlUtils {
+ public static ImmutableList<Node> childrenOf(Node node) {
+ NodeList children = node.getChildNodes();
+ ImmutableList.Builder<Node> result = ImmutableList.builder();
+ for (int i = 0, size = children.getLength(); i < size; i++) {
+ result.add(children.item(i));
+ }
+ return result.build();
+ }
+
+ public static ImmutableMap<String, String> attributesOf(Element element) {
+ NamedNodeMap map = element.getAttributes();
+ ImmutableMap.Builder<String, String> result = ImmutableMap.builder();
+ for (int i = 0, size = map.getLength(); i < size; i++) {
+ Attr attr = (Attr) map.item(i);
+ result.put(attr.getName(), attr.getValue());
+ }
+ return result.build();
+ }
+
+ private XmlUtils() {}
+}
diff --git a/caliper/src/main/java/com/google/caliper/util/InterleavedReader.java b/caliper/src/main/java/com/google/caliper/util/InterleavedReader.java
new file mode 100644
index 0000000..40293bf
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/util/InterleavedReader.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper.util;
+
+import com.google.gson.JsonParser;
+
+import java.io.BufferedReader;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.Reader;
+
+/**
+ * Reads a stream containing inline JSON objects. Each JSON object is prefixed
+ * by a marker string and suffixed by a newline character.
+ */
+public final class InterleavedReader implements Closeable {
+
+ /**
+ * The length of the scratch buffer to search for markers in. Also acts as an
+ * upper bound on the length of returned strings. Not used as an I/O buffer.
+ */
+ private static final int BUFFER_LENGTH = 80;
+
+ private final String marker;
+ private final BufferedReader reader;
+ private final JsonParser jsonParser = new JsonParser();
+
+ public static final String DEFAULT_MARKER = "//ZxJ/";
+
+ public InterleavedReader(Reader reader) {
+ this(DEFAULT_MARKER, reader);
+ }
+
+ public InterleavedReader(String marker, Reader reader) {
+ if (marker.length() > BUFFER_LENGTH) {
+ throw new IllegalArgumentException("marker.length() > BUFFER_LENGTH");
+ }
+ this.marker = marker;
+ this.reader = reader instanceof BufferedReader
+ ? (BufferedReader) reader
+ : new BufferedReader(reader);
+ }
+
+ /**
+ * Returns the next value in the stream: either a String, a JsonElement, or
+ * null to indicate the end of the stream. Callers should use instanceof to
+ * inspect the return type.
+ */
+ public Object read() throws IOException {
+ char[] buffer = new char[BUFFER_LENGTH];
+ reader.mark(BUFFER_LENGTH);
+ int count = 0;
+ int textEnd;
+
+ while (true) {
+ int r = reader.read(buffer, count, buffer.length - count);
+
+ if (r == -1) {
+ // the input is exhausted; return the remaining characters
+ textEnd = count;
+ break;
+ }
+
+ count += r;
+ int possibleMarker = findPossibleMarker(buffer, count);
+
+ if (possibleMarker != 0) {
+ // return the characters that precede the marker
+ textEnd = possibleMarker;
+ break;
+ }
+
+ if (count < marker.length()) {
+ // the buffer contains only the prefix of a marker so we must read more
+ continue;
+ }
+
+ // we've read a marker so return the value that follows
+ reader.reset();
+ String json = reader.readLine().substring(marker.length());
+ return jsonParser.parse(json);
+ }
+
+ if (count == 0) {
+ return null;
+ }
+
+ // return characters
+ reader.reset();
+ count = reader.read(buffer, 0, textEnd);
+ return new String(buffer, 0, count);
+ }
+
+ @Override public void close() throws IOException {
+ reader.close();
+ }
+
+ /**
+ * Returns the index of marker in {@code chars}, stopping at {@code limit}.
+ * Should the chars end with a prefix of marker, the offset of that prefix
+ * is returned.
+ */
+ int findPossibleMarker(char[] chars, int limit) {
+ search:
+ for (int i = 0; true; i++) {
+ for (int m = 0; m < marker.length() && i + m < limit; m++) {
+ if (chars[i + m] != marker.charAt(m)) {
+ continue search;
+ }
+ }
+ return i;
+ }
+ }
+}
diff --git a/caliper/src/main/java/com/google/caliper/util/LinearTranslation.java b/caliper/src/main/java/com/google/caliper/util/LinearTranslation.java
new file mode 100644
index 0000000..2e52b06
--- /dev/null
+++ b/caliper/src/main/java/com/google/caliper/util/LinearTranslation.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper.util;
+
+import com.google.common.annotations.GwtCompatible;
+
+@GwtCompatible
+public class LinearTranslation {
+ // y = mx + b
+ private final double m;
+ private final double b;
+
+ // TODO(kevinb): why so high? why even check this at all?
+ private static final double EQUALITY_TOLERANCE = 1.0E-6;
+
+ /**
+ * Constructs a linear translation for which {@code translate(in1) == out1}
+ * and {@code translate(in2) == out2}.
+ *
+ * @throws IllegalArgumentException if {@code in1 == in2}
+ */
+ public LinearTranslation(double in1, double out1, double in2, double out2) {
+ if (Math.abs(in1 - in2) < EQUALITY_TOLERANCE) {
+ throw new IllegalArgumentException("in1 and in2 are approximately equal");
+ }
+ double divisor = in1 - in2;
+ this.m = (out1 - out2) / divisor;
+ this.b = (in1 * out2 - in2 * out1) / divisor;
+ }
+
+ public double translate(double in) {
+ return m * in + b;
+ }
+}
diff --git a/caliper/src/main/resources/CaliperCore.gwt.xml b/caliper/src/main/resources/CaliperCore.gwt.xml
new file mode 100644
index 0000000..fcfe2fe
--- /dev/null
+++ b/caliper/src/main/resources/CaliperCore.gwt.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module>
+ <inherits name='com.google.gwt.user.User'/>
+ <source path="com/google/caliper">
+ <!-- Adding a file? Don't forget to update build.xml! -->
+ <include name="**/LinearTranslation.java"/>
+ <include name="**/Environment.java"/>
+ <include name="**/Run.java"/>
+ <include name="**/Measurement.java"/>
+ <include name="**/MeasurementSet.java"/>
+ <include name="**/ScenarioResult.java"/>
+ <include name="**/MeasurementType.java"/>
+ <include name="**/Scenario.java"/>
+ </source>
+</module> \ No newline at end of file
diff --git a/caliper/src/test/java/com/google/caliper/AllTests.java b/caliper/src/test/java/com/google/caliper/AllTests.java
new file mode 100644
index 0000000..0e3d1cc
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/AllTests.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+public class AllTests {
+ public static Test suite() {
+ TestSuite suite = new TestSuite();
+ suite.addTestSuite(CaliperTest.class);
+ suite.addTestSuite(JsonTest.class);
+ suite.addTestSuite(MeasurementSetTest.class);
+ suite.addTestSuite(ParameterTest.class);
+ suite.addTestSuite(WarmupOverflowTest.class);
+
+ return suite;
+ }
+}
diff --git a/caliper/src/test/java/com/google/caliper/CaliperTest.java b/caliper/src/test/java/com/google/caliper/CaliperTest.java
new file mode 100644
index 0000000..18857ba
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/CaliperTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import com.google.common.base.Supplier;
+import java.util.Map;
+import java.util.Set;
+import junit.framework.TestCase;
+
+public final class CaliperTest extends TestCase {
+
+ /**
+ * Test we detect and fail when benchmarks don't scale properly.
+ * @throws Exception
+ */
+ public void testBenchmarkScalesNonLinearly() throws Exception {
+ TimeMeasurer timeMeasurer = new TimeMeasurer(1000, 1000);
+ try {
+ timeMeasurer.run(new NonLinearTimedRunnable());
+ fail();
+ } catch (UserException.DoesNotScaleLinearlyException e) {
+ }
+ }
+
+ private static class NonLinearTimedRunnable extends ConfiguredBenchmark
+ implements Supplier<ConfiguredBenchmark> {
+ private NonLinearTimedRunnable() {
+ super(new NoOpBenchmark());
+ }
+
+ @Override public ConfiguredBenchmark get() {
+ return this;
+ }
+
+ @Override public Object run(int reps) throws Exception {
+ return null; // broken! doesn't loop reps times.
+ }
+
+ @Override public void close() throws Exception {}
+ }
+
+ private static class NoOpBenchmark implements Benchmark {
+ @Override public Set<String> parameterNames() {
+ return null;
+ }
+
+ @Override public Set<String> parameterValues(String parameterName) {
+ return null;
+ }
+
+ @Override public ConfiguredBenchmark createBenchmark(Map<String, String> parameterValues) {
+ return null;
+ }
+
+ @Override public Map<String, Integer> getTimeUnitNames() {
+ return null;
+ }
+
+ @Override public Map<String, Integer> getInstanceUnitNames() {
+ return null;
+ }
+
+ @Override public Map<String, Integer> getMemoryUnitNames() {
+ return null;
+ }
+
+ @Override public double nanosToUnits(double nanos) {
+ return 0;
+ }
+
+ @Override public double instancesToUnits(long instances) {
+ return 0;
+ }
+
+ @Override public double bytesToUnits(long bytes) {
+ return 0;
+ }
+ }
+}
diff --git a/caliper/src/test/java/com/google/caliper/JsonTest.java b/caliper/src/test/java/com/google/caliper/JsonTest.java
new file mode 100644
index 0000000..cee02b0
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/JsonTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Map;
+import junit.framework.TestCase;
+
+public final class JsonTest extends TestCase {
+
+ public void testJsonSerialization() {
+ Result original = newSampleResult();
+ String json = Json.getGsonInstance().toJson(original, Result.class);
+ Result reserialized = Json.getGsonInstance().fromJson(json, Result.class);
+ assertEquals(original, reserialized);
+ }
+
+ /**
+ * Caliper's JSON files used to include dates specific to the host machine's
+ * locale. http://code.google.com/p/caliper/issues/detail?id=113
+ */
+ public void testJsonSerializationWithFancyLocale() {
+ Result original = newSampleResult();
+
+ // serialize in one locale...
+ Locale defaultLocale = Locale.getDefault();
+ Locale.setDefault(Locale.ITALY);
+ String json;
+ try {
+ json = Json.getGsonInstance().toJson(original, Result.class);
+ } finally {
+ Locale.setDefault(defaultLocale);
+ }
+
+ // deserialize in another
+ Result reserialized = Json.getGsonInstance().fromJson(json, Result.class);
+ assertEquals(original, reserialized);
+ }
+
+ private Result newSampleResult() {
+ Map<String,Integer> units = ImmutableMap.of("ns", 1);
+ MeasurementSet timeMeasurements = new MeasurementSet(new Measurement(units, 2.0, 2.0));
+ Date executedDate = new Date(0);
+ Scenario scenario = new Scenario(ImmutableMap.of("benchmark", "Foo"));
+ ScenarioResult scenarioResult = new ScenarioResult(
+ timeMeasurements, "log", null, null, null, null);
+ Run run = new Run(ImmutableMap.of(scenario, scenarioResult), "foo.FooBenchmark", executedDate);
+ Environment environment = new Environment(ImmutableMap.of("os.name", "Linux"));
+ return new Result(run, environment);
+ }
+}
diff --git a/caliper/src/test/java/com/google/caliper/MeasurementSetTest.java b/caliper/src/test/java/com/google/caliper/MeasurementSetTest.java
new file mode 100644
index 0000000..f6f7cbb
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/MeasurementSetTest.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Ordering;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import junit.framework.TestCase;
+
+public class MeasurementSetTest extends TestCase {
+
+ Ordering<Measurement> MEASUREMENT_BY_NANOS = new Ordering<Measurement>() {
+ @Override public int compare(Measurement a, Measurement b) {
+ return Double.compare(a.getRaw(), b.getRaw());
+ }
+ };
+
+ public void testIncompatibleMeasurements() {
+ Measurement[] measurements = new Measurement[2];
+ measurements[0] = new Measurement(ImmutableMap.of("doublens", 1), 1.1, 2.2);
+ measurements[1] = new Measurement(ImmutableMap.of("triplens", 1), 3.8, 7.6);
+ try {
+ new MeasurementSet(measurements);
+ fail("illegal argument exception not thrown");
+ } catch (IllegalArgumentException e) {
+ // success
+ }
+ }
+
+ public void testIncompatibleAddedMeasurements() {
+ Measurement[] measurements = new Measurement[1];
+ measurements[0] = new Measurement(ImmutableMap.of("doublens", 1), 1.1, 2.2);
+ MeasurementSet measurementSet = new MeasurementSet(measurements);
+ try {
+ measurementSet.plusMeasurement(new Measurement(ImmutableMap.of("triplens", 1), 3.8, 7.6));
+ fail("illegal argument exception not thrown");
+ } catch (IllegalArgumentException e) {
+ // success
+ }
+ }
+
+ public void testSize() {
+ Measurement[] measurements = new Measurement[3];
+ measurements[0] = new Measurement(ImmutableMap.of("doublens", 1), 1.1, 2.2);
+ measurements[1] = new Measurement(ImmutableMap.of("doublens", 1), 3.8, 7.6);
+ measurements[2] = new Measurement(ImmutableMap.of("doublens", 1), 2.3, 4.6);
+ MeasurementSet measurementSet = new MeasurementSet(measurements);
+ assertEquals(3, measurementSet.size());
+
+ Measurement[] measurements2 = new Measurement[4];
+ measurements2[0] = new Measurement(ImmutableMap.of("doublens", 1), 1.1, 2.2);
+ measurements2[1] = new Measurement(ImmutableMap.of("doublens", 1), 3.8, 7.6);
+ measurements2[2] = new Measurement(ImmutableMap.of("doublens", 1), 2.3, 4.6);
+ measurements2[3] = new Measurement(ImmutableMap.of("doublens", 1), 7.2, 14.4);
+ MeasurementSet measurementSet2 =
+ new MeasurementSet(measurements2);
+ assertEquals(4, measurementSet2.size());
+ }
+
+ public void testPlusMeasurement() {
+ Measurement[] measurements = new Measurement[3];
+ measurements[0] = new Measurement(ImmutableMap.of("doublens", 1), 1.1, 2.2);
+ measurements[1] = new Measurement(ImmutableMap.of("doublens", 1), 3.8, 7.6);
+ measurements[2] = new Measurement(ImmutableMap.of("doublens", 1), 2.3, 4.6);
+ MeasurementSet measurementSet = new MeasurementSet(measurements);
+
+ Measurement[] measurements2 = new Measurement[4];
+ measurements2[0] = new Measurement(ImmutableMap.of("doublens", 1), 1.1, 2.2);
+ measurements2[1] = new Measurement(ImmutableMap.of("doublens", 1), 3.8, 7.6);
+ measurements2[2] = new Measurement(ImmutableMap.of("doublens", 1), 2.3, 4.6);
+ measurements2[3] = new Measurement(ImmutableMap.of("doublens", 1), 7.2, 14.4);
+ MeasurementSet measurementSet2 =
+ new MeasurementSet(measurements2);
+
+ MeasurementSet measurementSet3 = measurementSet.plusMeasurement(measurements2[3]);
+
+ assertDoubleListsEquals(measurementSet2.getMeasurementsRaw(),
+ measurementSet3.getMeasurementsRaw(), 0.0000001);
+ assertDoubleListsEquals(measurementSet2.getMeasurementUnits(),
+ measurementSet3.getMeasurementUnits(), 0.0000001);
+ assertEquals(measurementSet2.getUnitNames(), measurementSet3.getUnitNames());
+
+ List<Measurement> measurementList1 =
+ MEASUREMENT_BY_NANOS.sortedCopy(measurementSet2.getMeasurements());
+ List<Measurement> measurementList2 =
+ MEASUREMENT_BY_NANOS.sortedCopy(measurementSet3.getMeasurements());
+ assertEquals(measurementList1.size(), measurementList2.size());
+ for (int i = 0; i < measurementList1.size(); i++) {
+ assertEquals(measurementList1.get(i).getRaw(),
+ measurementList2.get(i).getRaw());
+ assertEquals(measurementList1.get(i).getProcessed(),
+ measurementList2.get(i).getProcessed());
+ }
+ }
+
+ public void testMedian() {
+ Measurement[] measurements = new Measurement[3];
+ measurements[0] = new Measurement(ImmutableMap.of("doublens", 1), 1.1, 2.2);
+ measurements[1] = new Measurement(ImmutableMap.of("doublens", 1), 3.8, 7.6);
+ measurements[2] = new Measurement(ImmutableMap.of("doublens", 1), 2.3, 4.6);
+ MeasurementSet measurementSet = new MeasurementSet(measurements);
+ assertEquals(2.3, measurementSet.medianRaw(), 0.00000001);
+ assertEquals(4.6, measurementSet.medianUnits(), 0.00000001);
+
+ Measurement[] measurements2 = new Measurement[4];
+ measurements2[0] = new Measurement(ImmutableMap.of("doublens", 1), 1.1, 2.2);
+ measurements2[1] = new Measurement(ImmutableMap.of("doublens", 1), 3.8, 7.6);
+ measurements2[2] = new Measurement(ImmutableMap.of("doublens", 1), 2.3, 4.6);
+ measurements2[3] = new Measurement(ImmutableMap.of("doublens", 1), 7.2, 14.4);
+ MeasurementSet measurementSet2 =
+ new MeasurementSet(measurements2);
+ assertEquals((2.3 + 3.8) / 2, measurementSet2.medianRaw(), 0.00000001);
+ assertEquals((4.6 + 7.6) / 2, measurementSet2.medianUnits(), 0.00000001);
+ }
+
+ public void testMean() {
+ Measurement[] measurements = new Measurement[3];
+ measurements[0] = new Measurement(ImmutableMap.of("doublens", 1), 1.1, 2.2);
+ measurements[1] = new Measurement(ImmutableMap.of("doublens", 1), 3.8, 7.6);
+ measurements[2] = new Measurement(ImmutableMap.of("doublens", 1), 2.3, 4.6);
+ MeasurementSet measurementSet = new MeasurementSet(measurements);
+ assertEquals((1.1 + 3.8 + 2.3) / 3, measurementSet.meanRaw(), 0.00000001);
+ assertEquals((2.2 + 7.6 + 4.6) / 3, measurementSet.meanUnits(), 0.00000001);
+
+ Measurement[] measurements2 = new Measurement[4];
+ measurements2[0] = new Measurement(ImmutableMap.of("doublens", 1), 1.1, 2.2);
+ measurements2[1] = new Measurement(ImmutableMap.of("doublens", 1), 3.8, 7.6);
+ measurements2[2] = new Measurement(ImmutableMap.of("doublens", 1), 2.3, 4.6);
+ measurements2[3] = new Measurement(ImmutableMap.of("doublens", 1), 7.2, 14.4);
+ MeasurementSet measurementSet2 =
+ new MeasurementSet(measurements2);
+ assertEquals((1.1 + 2.3 + 3.8 + 7.2) / 4, measurementSet2.meanRaw(), 0.00000001);
+ assertEquals((2.2 + 4.6 + 7.6 + 14.4) / 4, measurementSet2.meanUnits(), 0.00000001);
+ }
+
+ public void testStandardDeviation() {
+ Measurement[] measurements = new Measurement[3];
+ measurements[0] = new Measurement(ImmutableMap.of("doublens", 1), 1.1, 2.2);
+ measurements[1] = new Measurement(ImmutableMap.of("doublens", 1), 3.8, 7.6);
+ measurements[2] = new Measurement(ImmutableMap.of("doublens", 1), 2.3, 4.6);
+ MeasurementSet measurementSet = new MeasurementSet(measurements);
+ assertEquals(1.35277, measurementSet.standardDeviationRaw(), 0.00001);
+ assertEquals(2.70555, measurementSet.standardDeviationUnits(), 0.00001);
+ }
+
+ public void testMax() {
+ Measurement[] measurements = new Measurement[3];
+ measurements[0] = new Measurement(ImmutableMap.of("doublens", 1), 1.1, 2.2);
+ measurements[1] = new Measurement(ImmutableMap.of("doublens", 1), 3.8, 7.6);
+ measurements[2] = new Measurement(ImmutableMap.of("doublens", 1), 2.3, 4.6);
+ MeasurementSet measurementSet = new MeasurementSet(measurements);
+ assertEquals(3.8, measurementSet.maxRaw(), 0.00000001);
+ assertEquals(7.6, measurementSet.maxUnits(), 0.00000001);
+ }
+
+ public void testMin() {
+ Measurement[] measurements = new Measurement[3];
+ measurements[0] = new Measurement(ImmutableMap.of("doublens", 1), 1.1, 2.2);
+ measurements[1] = new Measurement(ImmutableMap.of("doublens", 1), 3.8, 7.6);
+ measurements[2] = new Measurement(ImmutableMap.of("doublens", 1), 2.3, 4.6);
+ MeasurementSet measurementSet = new MeasurementSet(measurements);
+ assertEquals(1.1, measurementSet.minRaw(), 0.00000001);
+ assertEquals(2.2, measurementSet.minUnits(), 0.00000001);
+ }
+
+ public void testJsonRoundtrip() {
+ Measurement[] measurements = new Measurement[3];
+ measurements[0] = new Measurement(ImmutableMap.of("doublens", 1), 1.1, 2.2);
+ measurements[1] = new Measurement(ImmutableMap.of("doublens", 1), 3.8, 7.6);
+ measurements[2] = new Measurement(ImmutableMap.of("doublens", 1), 2.3, 4.6);
+ MeasurementSet measurementSet = new MeasurementSet(measurements);
+ MeasurementSet roundTripped =
+ Json.measurementSetFromJson(Json.measurementSetToJson(measurementSet));
+ assertDoubleListsEquals(measurementSet.getMeasurementsRaw(),
+ roundTripped.getMeasurementsRaw(), 0.00000001);
+ assertDoubleListsEquals(measurementSet.getMeasurementUnits(),
+ roundTripped.getMeasurementUnits(), 0.00000001);
+ assertEquals(measurementSet.getUnitNames(), roundTripped.getUnitNames());
+ }
+
+ @SuppressWarnings({"AssertEqualsBetweenInconvertibleTypes"})
+ public void testFromLegacyString() {
+ MeasurementSet measurementSet = Json.measurementSetFromJson("122.0 133.0 144.0");
+ assertDoubleListsEquals(Arrays.asList(122.0, 133.0, 144.0),
+ measurementSet.getMeasurementsRaw(), 0.00000001);
+ assertDoubleListsEquals(Arrays.asList(122.0, 133.0, 144.0),
+ measurementSet.getMeasurementUnits(), 0.00000001);
+ assertEquals(ImmutableMap.of("ns", 1, "us", 1000, "ms", 1000000, "s", 1000000000),
+ measurementSet.getUnitNames());
+ }
+
+ private void assertDoubleListsEquals(List<Double> expected, List<Double> actual, double epsilon) {
+ assertEquals(expected.size(), actual.size());
+ Collections.sort(expected);
+ Collections.sort(actual);
+ for (int i = 0; i < expected.size(); i++) {
+ assertEquals(expected.get(i), actual.get(i), epsilon);
+ }
+ }
+}
diff --git a/caliper/src/test/java/com/google/caliper/ParameterTest.java b/caliper/src/test/java/com/google/caliper/ParameterTest.java
new file mode 100644
index 0000000..9063261
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/ParameterTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+
+public class ParameterTest extends TestCase {
+
+ public static class A extends SimpleBenchmark {
+ @Param({"value1", "value2"}) String param;
+ }
+
+ public void testFromAnnotation() throws Exception {
+ Map<String,Parameter<?>> map = Parameter.forClass(A.class);
+ Parameter<?> p = map.get("param");
+ assertEquals("param", p.getName());
+ assertEquals(String.class, p.getType());
+
+ checkParameterValues(A.class, "value1", "value2");
+ }
+
+ public enum Foo { VALUE1, VALUE2 }
+
+ public static class H extends SimpleBenchmark {
+ @Param Foo param;
+ }
+
+ public void testAllEnums() throws Exception {
+ checkParameterValues(H.class, Foo.VALUE1, Foo.VALUE2);
+ }
+
+ public static class I extends SimpleBenchmark {
+ @Param boolean param;
+ }
+
+ public void testBoolean() throws Exception {
+ checkParameterValues(I.class, true, false);
+ }
+
+ private static void checkParameterValues(Class<? extends SimpleBenchmark> bClass,
+ Object... expected) throws Exception {
+ Map<String,Parameter<?>> map = Parameter.forClass(bClass);
+ assertEquals(1, map.size());
+ Parameter<?> p = map.get("param");
+ List<Object> values = ImmutableList.copyOf(p.values());
+ assertEquals(Arrays.asList(expected), values);
+ }
+}
diff --git a/caliper/src/test/java/com/google/caliper/WarmupOverflowTest.java b/caliper/src/test/java/com/google/caliper/WarmupOverflowTest.java
new file mode 100644
index 0000000..85e59af
--- /dev/null
+++ b/caliper/src/test/java/com/google/caliper/WarmupOverflowTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.caliper;
+
+import com.google.caliper.UserException.DoesNotScaleLinearlyException;
+import com.google.common.util.concurrent.SimpleTimeLimiter;
+import com.google.common.util.concurrent.TimeLimiter;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import junit.framework.TestCase;
+
+/**
+ * Test exposing an issue where any warmup that completes enough executions to reach
+ * Integer.MAX_VALUE reps (either because the benchmark code is optimized away or because the
+ * warmupMillis are long enough compared to the benchmark execution time).
+ */
+public class WarmupOverflowTest extends TestCase {
+ private TimeLimiter timeLimiter;
+
+ @Override public void setUp() {
+ timeLimiter = new SimpleTimeLimiter(Executors.newSingleThreadExecutor());
+ }
+
+ public void testOptimizedAwayBenchmarkDoesNotTakeTooLongToRun() throws Exception {
+ try {
+ timeLimiter.callWithTimeout(new Callable<Void>() {
+ @Override public Void call() throws Exception {
+ InProcessRunner runner = new InProcessRunner();
+ runner.run(OptimizedAwayBenchmark.class.getName(), "--warmupMillis", "3000",
+ "--measurementType", "TIME");
+ return null;
+ }
+ }, 90, TimeUnit.SECONDS, false);
+ } catch (DoesNotScaleLinearlyException expected) {
+ }
+ }
+
+ public void testLongWarmupMillisDoesNotTakeTooLongToRun() throws Exception {
+ timeLimiter.callWithTimeout(new Callable<Void>() {
+ @Override public Void call() throws Exception {
+ InProcessRunner runner = new InProcessRunner();
+ runner.run(RelativelyFastBenchmark.class.getName(), "--warmupMillis", "8000",
+ "--runMillis", "51", "--measurementType", "TIME");
+ return null;
+ }
+ }, 90, TimeUnit.SECONDS, false);
+ }
+
+ public static class OptimizedAwayBenchmark extends SimpleBenchmark {
+ public void timeIsNullOrEmpty(int reps) {
+ for (int i = 0; i < reps; i++) {
+ // do nothing!
+ }
+ }
+ }
+
+ public static class RelativelyFastBenchmark extends SimpleBenchmark {
+ public long timeSqrt(int reps) {
+ long result = 0;
+ for(int i = 0; i < reps; i++) {
+ result += Math.sqrt(81);
+ }
+ return result;
+ }
+ }
+}
diff --git a/examples/pom.xml b/examples/pom.xml
new file mode 100644
index 0000000..7352d7b
--- /dev/null
+++ b/examples/pom.xml
@@ -0,0 +1,88 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>com.google.caliper</groupId>
+ <artifactId>caliper-examples</artifactId>
+ <packaging>jar</packaging>
+ <version>0.5-rc1</version>
+ <inceptionYear>2009</inceptionYear>
+ <name>Caliper Examples</name>
+ <parent>
+ <groupId>org.sonatype.oss</groupId>
+ <artifactId>oss-parent</artifactId>
+ <version>5</version>
+ </parent>
+ <url>http://code.google.com/p/caliper/</url>
+ <description>Caliper Examples</description>
+ <licenses>
+ <license>
+ <name>The Apache Software License, Version 2.0</name>
+ <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+ <distribution>repo</distribution>
+ </license>
+ </licenses>
+ <scm>
+ <connection>scm:git:http://code.google.com/p/caliper/examples</connection>
+ <developerConnection>scm:git:git:http://code.google.com/p/caliper/examples</developerConnection>
+ <url>http://caliper.codegoogle.com/svn/trunk/examples</url>
+ </scm>
+ <issueManagement>
+ <system>Google Code Issue Tracking</system>
+ <url>http://code.google.com/p/caliper/issues/list</url>
+ </issueManagement>
+ <organization>
+ <name>Google, Inc.</name>
+ <url>http://www.google.com</url>
+ </organization>
+ <dependencies>
+ <dependency>
+ <groupId>com.google.caliper</groupId>
+ <artifactId>caliper</artifactId>
+ <version>0.5-rc1</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>3.8.2</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <defaultGoal>package</defaultGoal>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>2.3.2</version>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-eclipse-plugin</artifactId>
+ <version>2.8</version>
+ <configuration>
+ <downloadSources>true</downloadSources>
+ <downloadJavadocs>true</downloadJavadocs>
+ <workspace>../eclipse-ws/</workspace>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-release-plugin</artifactId>
+ <version>2.1</version>
+ <configuration>
+ <arguments>-DenableCiProfile=true</arguments>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <developers>
+ <developer>
+ <name>Jesse Wilson</name>
+ <organization>Google Inc.</organization>
+ </developer>
+ </developers>
+</project>
diff --git a/examples/src/main/java/examples/ArraySortBenchmark.java b/examples/src/main/java/examples/ArraySortBenchmark.java
new file mode 100644
index 0000000..f42390f
--- /dev/null
+++ b/examples/src/main/java/examples/ArraySortBenchmark.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package examples;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+import java.util.Arrays;
+import java.util.Random;
+
+/**
+ * Measures sorting on different distributions of integers.
+ */
+public class ArraySortBenchmark extends SimpleBenchmark {
+
+ @Param({"10", "100", "1000", "10000"}) private int length;
+
+ @Param private Distribution distribution;
+
+ private int[] values;
+ private int[] copy;
+
+ @Override protected void setUp() throws Exception {
+ values = distribution.create(length);
+ copy = new int[length];
+ }
+
+ public void timeSort(int reps) {
+ for (int i = 0; i < reps; i++) {
+ System.arraycopy(values, 0, copy, 0, values.length);
+ Arrays.sort(copy);
+ }
+ }
+
+ public enum Distribution {
+ SAWTOOTH {
+ @Override
+ int[] create(int length) {
+ int[] result = new int[length];
+ for (int i = 0; i < length; i += 5) {
+ result[i] = 0;
+ result[i + 1] = 1;
+ result[i + 2] = 2;
+ result[i + 3] = 3;
+ result[i + 4] = 4;
+ }
+ return result;
+ }
+ },
+ INCREASING {
+ @Override
+ int[] create(int length) {
+ int[] result = new int[length];
+ for (int i = 0; i < length; i++) {
+ result[i] = i;
+ }
+ return result;
+ }
+ },
+ DECREASING {
+ @Override
+ int[] create(int length) {
+ int[] result = new int[length];
+ for (int i = 0; i < length; i++) {
+ result[i] = length - i;
+ }
+ return result;
+ }
+ },
+ RANDOM {
+ @Override
+ int[] create(int length) {
+ Random random = new Random();
+ int[] result = new int[length];
+ for (int i = 0; i < length; i++) {
+ result[i] = random.nextInt();
+ }
+ return result;
+ }
+ };
+
+ abstract int[] create(int length);
+ }
+
+ public static void main(String[] args) throws Exception {
+ Runner.main(ArraySortBenchmark.class, args);
+ }
+}
diff --git a/examples/src/main/java/examples/BitSetBenchmark.java b/examples/src/main/java/examples/BitSetBenchmark.java
new file mode 100644
index 0000000..3154b01
--- /dev/null
+++ b/examples/src/main/java/examples/BitSetBenchmark.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package examples;
+
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+import java.util.BitSet;
+import java.util.Random;
+
+/**
+ * A simple example of a benchmark for BitSet showing some of the issues with
+ * micro-benchmarking.
+ *
+ * <p>The following is a discussion of how the benchmarks evolved and what they
+ * may (or may not) tell us. This discussion is based on the following set of
+ * results:
+ *
+ * <p><pre>
+ * 0% Scenario{vm=java, benchmark=SetBitSetX64} 233.45ns; σ=0.31ns @ 3 trials
+ * 20% Scenario{vm=java, benchmark=SetMaskX64} 116.62ns; σ=0.09ns @ 3 trials
+ * 40% Scenario{vm=java, benchmark=CharsToBitSet} 748.40ns; σ=23.52ns @ 10 trials
+ * 60% Scenario{vm=java, benchmark=CharsToMask} 198.55ns; σ=9.46ns @ 10 trials
+ * 80% Scenario{vm=java, benchmark=BaselineIteration} 67.85ns; σ=0.44ns @ 3 trials
+ *
+ * benchmark ns logarithmic runtime
+ * SetBitSetX64 233 XXXXXXXXX|||||||||||||||
+ * SetMaskX64 117 XXXX|||||||||||||||||
+ * CharsToBitSet 748 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+ * CharsToMask 199 XXXXXXX||||||||||||||||
+ * BaselineIteration 68 XX|||||||||||||||||
+ * </pre>
+ *
+ * <p>Initially things look simple. The {@link #timeSetBitSetX64(int)} benchmark
+ * takes approximately twice as long as {@link #timeSetMaskX64(int)}. However
+ * the inner loops in these benchmarks have almost no content, so a more
+ * 'real world' benchmark was devised in an attempt to back up these results.
+ *
+ * <p>The {@link #timeCharsToMask(int)} and {@link #timeCharsToBitSet(int)}
+ * benchmarks convert a simple char[] of '1's and '0's to a corresponding BitSet
+ * or bit mask. These also processes 64 bits per iteration and so appears to be
+ * doing the same amount of work as the first benchmarks.
+ *
+ * <p>Additionally the {@link BitSetBenchmark#timeBaselineIteration(int)}
+ * benchmark attempts to measure the raw cost of looping through and reading the
+ * source data.
+ *
+ * <p>When comparing the benchmarks that use bit masking, we see that the
+ * measured time of the SetMaskX64 benchmark (117ns) is roughly the same
+ * as the CharsToMask benchmark (199ns) with the BaselineIteration time (68ms)
+ * subtracted from it. This gives us some confidence that both benchmarks are
+ * resulting in the same underlying work on the CPU.
+ *
+ * <p>However the CharsToBitSet and the SetBitSetX64 benchmarks differ very
+ * significantly (approximately 3x) even when accounting for the
+ * BaselineIteration result. This suggests that the performance of
+ * {@link BitSet#set} is quite dependent on the surrounding code and how
+ * it is optimized by the JVM.
+ *
+ * <p>The conclusions we can draw from this are:
+ *
+ * <p><b>1:</b> Using BitSet is slower than using bit masks directly. At best it
+ * seems about 2x slower than a bit mask, but could easily be 5x slower in real
+ * applications.
+ *
+ * <p>While these are only estimates, we can conclude that when performance is
+ * important and where bit set operations occur in tight loops, bit masks
+ * should be used in favor of BitSets.
+ *
+ * <p><b>2:</b>Overly simplistic benchmarks can give a very false impression of
+ * performance.
+ */
+public class BitSetBenchmark extends SimpleBenchmark {
+ private BitSet bitSet;
+ private char[] bitString;
+
+ @Override protected void setUp() throws Exception {
+ bitSet = new BitSet(64);
+ bitString = new char[64];
+ Random r = new Random();
+ for (int n = 0; n < 64; n++) {
+ bitString[n] = r.nextBoolean() ? '1' : '0';
+ }
+ }
+
+ /**
+ * This benchmark attempts to measure performance of {@link BitSet#set}.
+ */
+ public int timeSetBitSetX64(int reps) {
+ long count = 64L * reps;
+ for (int i = 0; i < count; i++) {
+ bitSet.set(i & 0x3F, true);
+ }
+ return bitSet.hashCode();
+ }
+
+ /**
+ * This benchmark attempts to measure performance of direct bit-manipulation.
+ */
+ public long timeSetMaskX64(int reps) {
+ long count = 64L * reps;
+ long bitMask = 0L;
+ for (int i = 0; i < count; i++) {
+ bitMask |= 1 << (i & 0x3F);
+ }
+ return bitMask;
+ }
+
+ /**
+ * This benchmark parses a char[] of 1's and 0's into a BitSet. Results from
+ * this benchmark should be comparable with those from
+ * {@link #timeCharsToMask(int)}.
+ */
+ public String timeCharsToBitSet(int reps) {
+ /*
+ * This benchmark now measures the complete parsing of a char[] rather than
+ * a single invocation of {@link BitSet#set}. However this fine because
+ * it is intended to be a comparative benchmark.
+ */
+ for (int i = 0; i < reps; i++) {
+ for (int n = 0; n < bitString.length; n++) {
+ bitSet.set(n, bitString[n] == '1');
+ }
+ }
+ return bitSet.toString();
+ }
+
+ /**
+ * This benchmark parses a char[] of 1's and 0's into a bit mask. Results from
+ * this benchmark should be comparable with those from
+ * {@link #timeCharsToBitSet(int)}.
+ */
+ public long timeCharsToMask(int reps) {
+ /*
+ * Comparing results we see a far more realistic sounding result whereby
+ * using a bit mask is a little over 4x faster than using BitSet.
+ */
+ long bitMask = 0;
+ for (int i = 0; i < reps; i++) {
+ for (int n = 0; n < bitString.length; n++) {
+ long m = 1 << n;
+ if (bitString[n] == '1') {
+ bitMask |= m;
+ } else {
+ bitMask &= ~m;
+ }
+ }
+ }
+ return bitMask;
+ }
+
+ /**
+ * This benchmark attempts to measure the baseline cost of both
+ * {@link #timeCharsToBitSet(int)} and {@link #timeCharsToMask(int)}.
+ * It does this by unconditionally summing the character values of the char[].
+ * This is as close to a no-op case as we can expect to get without unwanted
+ * over-optimization.
+ */
+ public long timeBaselineIteration(int reps) {
+ int badHash = 0;
+ for (int i = 0; i < reps; i++) {
+ for (int n = 0; n < bitString.length; n++) {
+ badHash += bitString[n];
+ }
+ }
+ return badHash;
+ }
+
+ // TODO: remove this from all examples when IDE plugins are ready
+ public static void main(String[] args) throws Exception {
+ Runner.main(BitSetBenchmark.class, args);
+ }
+}
diff --git a/examples/src/main/java/examples/CharacterBenchmark.java b/examples/src/main/java/examples/CharacterBenchmark.java
new file mode 100644
index 0000000..1e013af
--- /dev/null
+++ b/examples/src/main/java/examples/CharacterBenchmark.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package examples;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+/**
+ * Tests various Character methods, intended for testing multiple
+ * implementations against each other.
+ */
+public class CharacterBenchmark extends SimpleBenchmark {
+
+ @Param private CharacterSet characterSet;
+
+ @Param private Overload overload;
+
+ private char[] chars;
+
+ @Override protected void setUp() throws Exception {
+ this.chars = characterSet.chars;
+ }
+
+ public enum Overload { CHAR, INT }
+
+ public enum CharacterSet {
+ ASCII(128),
+ UNICODE(65536);
+ final char[] chars;
+ CharacterSet(int size) {
+ this.chars = new char[65536];
+ for (int i = 0; i < 65536; ++i) {
+ chars[i] = (char) (i % size);
+ }
+ }
+ }
+
+ // A fake benchmark to give us a baseline.
+ public boolean timeIsSpace(int reps) {
+ boolean dummy = false;
+ if (overload == Overload.CHAR) {
+ for (int i = 0; i < reps; ++i) {
+ for (int ch = 0; ch < 65536; ++ch) {
+ dummy ^= ((char) ch == ' ');
+ }
+ }
+ } else {
+ for (int i = 0; i < reps; ++i) {
+ for (int ch = 0; ch < 65536; ++ch) {
+ dummy ^= (ch == ' ');
+ }
+ }
+ }
+ return dummy;
+ }
+
+ public void timeDigit(int reps) {
+ if (overload == Overload.CHAR) {
+ for (int i = 0; i < reps; ++i) {
+ for (int ch = 0; ch < 65536; ++ch) {
+ Character.digit(chars[ch], 10);
+ }
+ }
+ } else {
+ for (int i = 0; i < reps; ++i) {
+ for (int ch = 0; ch < 65536; ++ch) {
+ Character.digit((int) chars[ch], 10);
+ }
+ }
+ }
+ }
+
+ public void timeGetNumericValue(int reps) {
+ if (overload == Overload.CHAR) {
+ for (int i = 0; i < reps; ++i) {
+ for (int ch = 0; ch < 65536; ++ch) {
+ Character.getNumericValue(chars[ch]);
+ }
+ }
+ } else {
+ for (int i = 0; i < reps; ++i) {
+ for (int ch = 0; ch < 65536; ++ch) {
+ Character.getNumericValue((int) chars[ch]);
+ }
+ }
+ }
+ }
+
+ public void timeIsDigit(int reps) {
+ if (overload == Overload.CHAR) {
+ for (int i = 0; i < reps; ++i) {
+ for (int ch = 0; ch < 65536; ++ch) {
+ Character.isDigit(chars[ch]);
+ }
+ }
+ } else {
+ for (int i = 0; i < reps; ++i) {
+ for (int ch = 0; ch < 65536; ++ch) {
+ Character.isDigit((int) chars[ch]);
+ }
+ }
+ }
+ }
+
+ public void timeIsIdentifierIgnorable(int reps) {
+ if (overload == Overload.CHAR) {
+ for (int i = 0; i < reps; ++i) {
+ for (int ch = 0; ch < 65536; ++ch) {
+ Character.isIdentifierIgnorable(chars[ch]);
+ }
+ }
+ } else {
+ for (int i = 0; i < reps; ++i) {
+ for (int ch = 0; ch < 65536; ++ch) {
+ Character.isIdentifierIgnorable((int) chars[ch]);
+ }
+ }
+ }
+ }
+
+ public void timeIsJavaIdentifierPart(int reps) {
+ if (overload == Overload.CHAR) {
+ for (int i = 0; i < reps; ++i) {
+ for (int ch = 0; ch < 65536; ++ch) {
+ Character.isJavaIdentifierPart(chars[ch]);
+ }
+ }
+ } else {
+ for (int i = 0; i < reps; ++i) {
+ for (int ch = 0; ch < 65536; ++ch) {
+ Character.isJavaIdentifierPart((int) chars[ch]);
+ }
+ }
+ }
+ }
+
+ public void timeIsJavaIdentifierStart(int reps) {
+ if (overload == Overload.CHAR) {
+ for (int i = 0; i < reps; ++i) {
+ for (int ch = 0; ch < 65536; ++ch) {
+ Character.isJavaIdentifierStart(chars[ch]);
+ }
+ }
+ } else {
+ for (int i = 0; i < reps; ++i) {
+ for (int ch = 0; ch < 65536; ++ch) {
+ Character.isJavaIdentifierStart((int) chars[ch]);
+ }
+ }
+ }
+ }
+
+ public void timeIsLetter(int reps) {
+ if (overload == Overload.CHAR) {
+ for (int i = 0; i < reps; ++i) {
+ for (int ch = 0; ch < 65536; ++ch) {
+ Character.isLetter(chars[ch]);
+ }
+ }
+ } else {
+ for (int i = 0; i < reps; ++i) {
+ for (int ch = 0; ch < 65536; ++ch) {
+ Character.isLetter((int) chars[ch]);
+ }
+ }
+ }
+ }
+
+ public void timeIsLetterOrDigit(int reps) {
+ if (overload == Overload.CHAR) {
+ for (int i = 0; i < reps; ++i) {
+ for (int ch = 0; ch < 65536; ++ch) {
+ Character.isLetterOrDigit(chars[ch]);
+ }
+ }
+ } else {
+ for (int i = 0; i < reps; ++i) {
+ for (int ch = 0; ch < 65536; ++ch) {
+ Character.isLetterOrDigit((int) chars[ch]);
+ }
+ }
+ }
+ }
+
+ public void timeIsLowerCase(int reps) {
+ if (overload == Overload.CHAR) {
+ for (int i = 0; i < reps; ++i) {
+ for (int ch = 0; ch < 65536; ++ch) {
+ Character.isLowerCase(chars[ch]);
+ }
+ }
+ } else {
+ for (int i = 0; i < reps; ++i) {
+ for (int ch = 0; ch < 65536; ++ch) {
+ Character.isLowerCase((int) chars[ch]);
+ }
+ }
+ }
+ }
+
+ public void timeIsSpaceChar(int reps) {
+ if (overload == Overload.CHAR) {
+ for (int i = 0; i < reps; ++i) {
+ for (int ch = 0; ch < 65536; ++ch) {
+ Character.isSpaceChar(chars[ch]);
+ }
+ }
+ } else {
+ for (int i = 0; i < reps; ++i) {
+ for (int ch = 0; ch < 65536; ++ch) {
+ Character.isSpaceChar((int) chars[ch]);
+ }
+ }
+ }
+ }
+
+ public void timeIsUpperCase(int reps) {
+ if (overload == Overload.CHAR) {
+ for (int i = 0; i < reps; ++i) {
+ for (int ch = 0; ch < 65536; ++ch) {
+ Character.isUpperCase(chars[ch]);
+ }
+ }
+ } else {
+ for (int i = 0; i < reps; ++i) {
+ for (int ch = 0; ch < 65536; ++ch) {
+ Character.isUpperCase((int) chars[ch]);
+ }
+ }
+ }
+ }
+
+ public void timeIsWhitespace(int reps) {
+ if (overload == Overload.CHAR) {
+ for (int i = 0; i < reps; ++i) {
+ for (int ch = 0; ch < 65536; ++ch) {
+ Character.isWhitespace(chars[ch]);
+ }
+ }
+ } else {
+ for (int i = 0; i < reps; ++i) {
+ for (int ch = 0; ch < 65536; ++ch) {
+ Character.isWhitespace((int) chars[ch]);
+ }
+ }
+ }
+ }
+
+ public void timeToLowerCase(int reps) {
+ if (overload == Overload.CHAR) {
+ for (int i = 0; i < reps; ++i) {
+ for (int ch = 0; ch < 65536; ++ch) {
+ Character.toLowerCase(chars[ch]);
+ }
+ }
+ } else {
+ for (int i = 0; i < reps; ++i) {
+ for (int ch = 0; ch < 65536; ++ch) {
+ Character.toLowerCase((int) chars[ch]);
+ }
+ }
+ }
+ }
+
+ public void timeToUpperCase(int reps) {
+ if (overload == Overload.CHAR) {
+ for (int i = 0; i < reps; ++i) {
+ for (int ch = 0; ch < 65536; ++ch) {
+ Character.toUpperCase(chars[ch]);
+ }
+ }
+ } else {
+ for (int i = 0; i < reps; ++i) {
+ for (int ch = 0; ch < 65536; ++ch) {
+ Character.toUpperCase((int) chars[ch]);
+ }
+ }
+ }
+ }
+
+ // TODO: remove this from all examples when IDE plugins are ready
+ public static void main(String[] args) throws Exception {
+ Runner.main(CharacterBenchmark.class, args);
+ }
+}
diff --git a/examples/src/main/java/examples/CompressionSizeBenchmark.java b/examples/src/main/java/examples/CompressionSizeBenchmark.java
new file mode 100644
index 0000000..90ddd39
--- /dev/null
+++ b/examples/src/main/java/examples/CompressionSizeBenchmark.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package examples;
+
+import com.google.caliper.Param;
+import com.google.caliper.api.Benchmark;
+import com.google.caliper.model.ArbitraryMeasurement;
+import com.google.caliper.runner.CaliperMain;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.zip.Deflater;
+
+/**
+ * Example "arbitrary measurement" benchmark.
+ */
+public class CompressionSizeBenchmark extends Benchmark {
+
+ @Param({
+ "this string will compress badly",
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ "asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdf"})
+ private String toCompress;
+ @Param({"bestCompression", "bestSpeed", "noCompression", "huffmanOnly"})
+ private String compressionLevel;
+
+ private double compressionRatio;
+
+ public static final Map<String, Integer> compressionLevelMap = new HashMap<String, Integer>();
+ static {
+ compressionLevelMap.put("bestCompression", Deflater.BEST_COMPRESSION);
+ compressionLevelMap.put("bestSpeed", Deflater.BEST_SPEED);
+ compressionLevelMap.put("noCompression", Deflater.NO_COMPRESSION);
+ compressionLevelMap.put("huffmanOnly", Deflater.HUFFMAN_ONLY);
+ }
+
+ public long timeSimpleCompression(int reps) {
+ long dummy = 0;
+ for (int i = 0; i < reps; i++) {
+ dummy += compress(toCompress.getBytes()).length;
+ }
+ return dummy;
+ }
+
+ @ArbitraryMeasurement(units = ":1", description = "ratio of uncompressed to compressed")
+ public double compressionSize() {
+ byte[] initialBytes = toCompress.getBytes();
+ byte[] finalBytes = compress(initialBytes);
+ compressionRatio = (double) initialBytes.length / (double) finalBytes.length;
+ return compressionRatio;
+ }
+
+ private byte[] compress(byte[] bytes) {
+ Deflater compressor = new Deflater();
+ compressor.setLevel(compressionLevelMap.get(compressionLevel));
+ compressor.setInput(bytes);
+ compressor.finish();
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ byte[] buf = new byte[1024];
+ while (!compressor.finished()) {
+ int count = compressor.deflate(buf);
+ bos.write(buf, 0, count);
+ }
+ try {
+ bos.close();
+ } catch (IOException e) {
+ }
+ return bos.toByteArray();
+ }
+
+ public static void main(String[] args) {
+ CaliperMain.main(CompressionSizeBenchmark.class, args);
+ }
+}
diff --git a/examples/src/main/java/examples/ContainsBenchmark.java b/examples/src/main/java/examples/ContainsBenchmark.java
new file mode 100644
index 0000000..01bb8c6
--- /dev/null
+++ b/examples/src/main/java/examples/ContainsBenchmark.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package examples;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+
+public class ContainsBenchmark extends SimpleBenchmark {
+ @Param({"0", "25", "50", "75", "100"}) private int percentNulls;
+ @Param({"100", "1000", "10000"}) private int containsPerRep;
+
+ /** the set under test */
+ private final Set<String> set = new HashSet<String>();
+
+ /** twenty-five percent nulls */
+ private final List<Object> queries = new ArrayList<Object>();
+
+ @Override protected void setUp() {
+ set.addAll(Arrays.asList("str1", "str2", "str3", "str4"));
+ int nullThreshold = percentNulls * containsPerRep / 100;
+ for (int i = 0; i < nullThreshold; i++) {
+ queries.add(null);
+ }
+ for (int i = nullThreshold; i < containsPerRep; i++) {
+ queries.add(new Object());
+ }
+ Collections.shuffle(queries, new Random(0));
+ }
+
+ @Override public Map<String, Integer> getTimeUnitNames() {
+ Map<String, Integer> unitNames = new HashMap<String, Integer>();
+ unitNames.put("ns/contains", 1);
+ unitNames.put("us/contains", 1000);
+ unitNames.put("ms/contains", 1000000);
+ unitNames.put("s/contains", 1000000000);
+ return unitNames;
+ }
+
+ @Override public double nanosToUnits(double nanos) {
+ return nanos / containsPerRep;
+ }
+
+ public void timeContains(int reps) {
+ for (int i = 0; i < reps; i++) {
+ for (Object query : queries) {
+ set.contains(query);
+ }
+ }
+ }
+
+ public static void main(String[] args) {
+ Runner.main(ContainsBenchmark.class, args);
+ }
+}
diff --git a/examples/src/main/java/examples/CopyArrayBenchmark.java b/examples/src/main/java/examples/CopyArrayBenchmark.java
new file mode 100644
index 0000000..fed0950
--- /dev/null
+++ b/examples/src/main/java/examples/CopyArrayBenchmark.java
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package examples;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+import java.util.Arrays;
+import java.util.Random;
+
+/**
+ * Tests each of four ways to copy an array, for all nine array types.
+ *
+ * <p>Once upon a time, {@code clone} was much slower than the other array copy techniques, but
+ * that was fixed in Sun bug:
+ *
+ * <a href="http://bugs.sun.com/view_bug.do?bug_id=6428387">
+ * array clone() much slower than Arrays.copyOf</a>
+ *
+ * at which time all copy methods were equally efficient.
+ *
+ * <p>Recent (2011) measurements with OpenJDK 7 on Linux are less clear. Results suggests that:
+ *
+ * <ul>
+ * <li>The different methods of copying have indistinguishable performance with hotspot server for
+ * all nine types, except that the naive LOOP is slower.
+ * With the "client" compiler, LOOP beats CLONE, which is the slowest.
+ * <li>As array sizes get large, the runtime is indeed proportional to the size of the array in
+ * memory (boolean arrays count as byte arrays!).
+ * </ul>
+ */
+public class CopyArrayBenchmark extends SimpleBenchmark {
+ public enum Strategy {
+ CLONE {
+ @Override Object[] copy(Object[] array) {
+ return array.clone();
+ }
+ @Override boolean[] copy(boolean[] array) {
+ return array.clone();
+ }
+ @Override byte[] copy(byte[] array) {
+ return array.clone();
+ }
+ @Override char[] copy(char[] array) {
+ return array.clone();
+ }
+ @Override double[] copy(double[] array) {
+ return array.clone();
+ }
+ @Override float[] copy(float[] array) {
+ return array.clone();
+ }
+ @Override int[] copy(int[] array) {
+ return array.clone();
+ }
+ @Override long[] copy(long[] array) {
+ return array.clone();
+ }
+ @Override short[] copy(short[] array) {
+ return array.clone();
+ }
+ },
+ ARRAYS_COPYOF {
+ @Override Object[] copy(Object[] array) {
+ return Arrays.copyOf(array, array.length);
+ }
+ @Override boolean[] copy(boolean[] array) {
+ return Arrays.copyOf(array, array.length);
+ }
+ @Override byte[] copy(byte[] array) {
+ return Arrays.copyOf(array, array.length);
+ }
+ @Override char[] copy(char[] array) {
+ return Arrays.copyOf(array, array.length);
+ }
+ @Override double[] copy(double[] array) {
+ return Arrays.copyOf(array, array.length);
+ }
+ @Override float[] copy(float[] array) {
+ return Arrays.copyOf(array, array.length);
+ }
+ @Override int[] copy(int[] array) {
+ return Arrays.copyOf(array, array.length);
+ }
+ @Override long[] copy(long[] array) {
+ return Arrays.copyOf(array, array.length);
+ }
+ @Override short[] copy(short[] array) {
+ return Arrays.copyOf(array, array.length);
+ }
+ },
+ SYSTEM_ARRAYCOPY {
+ @Override Object[] copy(Object[] array) {
+ Object[] copy = new Object[array.length];
+ System.arraycopy(array, 0, copy, 0, array.length);
+ return copy;
+ }
+ @Override boolean[] copy(boolean[] array) {
+ boolean[] copy = new boolean[array.length];
+ System.arraycopy(array, 0, copy, 0, array.length);
+ return copy;
+ }
+ @Override byte[] copy(byte[] array) {
+ byte[] copy = new byte[array.length];
+ System.arraycopy(array, 0, copy, 0, array.length);
+ return copy;
+ }
+ @Override char[] copy(char[] array) {
+ char[] copy = new char[array.length];
+ System.arraycopy(array, 0, copy, 0, array.length);
+ return copy;
+ }
+ @Override double[] copy(double[] array) {
+ double[] copy = new double[array.length];
+ System.arraycopy(array, 0, copy, 0, array.length);
+ return copy;
+ }
+ @Override float[] copy(float[] array) {
+ float[] copy = new float[array.length];
+ System.arraycopy(array, 0, copy, 0, array.length);
+ return copy;
+ }
+ @Override int[] copy(int[] array) {
+ int[] copy = new int[array.length];
+ System.arraycopy(array, 0, copy, 0, array.length);
+ return copy;
+ }
+ @Override long[] copy(long[] array) {
+ long[] copy = new long[array.length];
+ System.arraycopy(array, 0, copy, 0, array.length);
+ return copy;
+ }
+ @Override short[] copy(short[] array) {
+ short[] copy = new short[array.length];
+ System.arraycopy(array, 0, copy, 0, array.length);
+ return copy;
+ }
+ },
+ LOOP {
+ @Override Object[] copy(Object[] array) {
+ int len = array.length;
+ Object[] copy = new Object[len];
+ for (int i = 0; i < len; i++) {
+ copy[i] = array[i];
+ }
+ return copy;
+ }
+ @Override boolean[] copy(boolean[] array) {
+ int len = array.length;
+ boolean[] copy = new boolean[array.length];
+ for (int i = 0; i < len; i++) {
+ copy[i] = array[i];
+ }
+ return copy;
+ }
+ @Override byte[] copy(byte[] array) {
+ int len = array.length;
+ byte[] copy = new byte[array.length];
+ for (int i = 0; i < len; i++) {
+ copy[i] = array[i];
+ }
+ return copy;
+ }
+ @Override char[] copy(char[] array) {
+ int len = array.length;
+ char[] copy = new char[array.length];
+ for (int i = 0; i < len; i++) {
+ copy[i] = array[i];
+ }
+ return copy;
+ }
+ @Override double[] copy(double[] array) {
+ int len = array.length;
+ double[] copy = new double[array.length];
+ for (int i = 0; i < len; i++) {
+ copy[i] = array[i];
+ }
+ return copy;
+ }
+ @Override float[] copy(float[] array) {
+ int len = array.length;
+ float[] copy = new float[array.length];
+ for (int i = 0; i < len; i++) {
+ copy[i] = array[i];
+ }
+ return copy;
+ }
+ @Override int[] copy(int[] array) {
+ int len = array.length;
+ int[] copy = new int[array.length];
+ for (int i = 0; i < len; i++) {
+ copy[i] = array[i];
+ }
+ return copy;
+ }
+ @Override long[] copy(long[] array) {
+ int len = array.length;
+ long[] copy = new long[array.length];
+ for (int i = 0; i < len; i++) {
+ copy[i] = array[i];
+ }
+ return copy;
+ }
+ @Override short[] copy(short[] array) {
+ int len = array.length;
+ short[] copy = new short[array.length];
+ for (int i = 0; i < len; i++) {
+ copy[i] = array[i];
+ }
+ return copy;
+ }
+ },
+ ;
+
+ abstract Object[] copy(Object[] array);
+ abstract boolean[] copy(boolean[] array);
+ abstract byte[] copy(byte[] array);
+ abstract char[] copy(char[] array);
+ abstract double[] copy(double[] array);
+ abstract float[] copy(float[] array);
+ abstract int[] copy(int[] array);
+ abstract long[] copy(long[] array);
+ abstract short[] copy(short[] array);
+ }
+
+ @Param Strategy strategy;
+
+ @Param({"5", "500", "50000"}) int size;
+
+ Object[] objectArray;
+ boolean[] booleanArray;
+ byte[] byteArray;
+ char[] charArray;
+ double[] doubleArray;
+ float[] floatArray;
+ int[] intArray;
+ long[] longArray;
+ short[] shortArray;
+
+ @Override protected void setUp() {
+ objectArray = new Object[size];
+ booleanArray = new boolean[size];
+ byteArray = new byte[size];
+ charArray = new char[size];
+ doubleArray = new double[size];
+ floatArray = new float[size];
+ intArray = new int[size];
+ longArray = new long[size];
+ shortArray = new short[size];
+
+ Random random = new Random();
+ for (int i = 0; i < size; i++) {
+ int num = random.nextInt();
+ objectArray[i] = new Object();
+ booleanArray[i] = num % 2 == 0;
+ byteArray[i] = (byte) num;
+ charArray[i] = (char) num;
+ doubleArray[i] = num;
+ floatArray[i] = (float) num;
+ intArray[i] = num;
+ longArray[i] = num;
+ shortArray[i] = (short) num;
+ }
+ }
+
+ public int timeObjects(int reps) {
+ int dummy = 0;
+ for (int i = 0; i < reps; i++) {
+ dummy += strategy.copy(objectArray).hashCode();
+ }
+ return dummy;
+ }
+
+ public int timeBooleans(int reps) {
+ int dummy = 0;
+ for (int i = 0; i < reps; i++) {
+ dummy += strategy.copy(booleanArray).hashCode();
+ }
+ return dummy;
+ }
+
+ public int timeBytes(int reps) {
+ int dummy = 0;
+ for (int i = 0; i < reps; i++) {
+ dummy += strategy.copy(byteArray).hashCode();
+ }
+ return dummy;
+ }
+
+ public int timeChars(int reps) {
+ int dummy = 0;
+ for (int i = 0; i < reps; i++) {
+ dummy += strategy.copy(charArray).hashCode();
+ }
+ return dummy;
+ }
+
+ public int timeDoubles(int reps) {
+ int dummy = 0;
+ for (int i = 0; i < reps; i++) {
+ dummy += strategy.copy(doubleArray).hashCode();
+ }
+ return dummy;
+ }
+
+ public int timeFloats(int reps) {
+ int dummy = 0;
+ for (int i = 0; i < reps; i++) {
+ dummy += strategy.copy(floatArray).hashCode();
+ }
+ return dummy;
+ }
+
+ public int timeInts(int reps) {
+ int dummy = 0;
+ for (int i = 0; i < reps; i++) {
+ dummy += strategy.copy(intArray).hashCode();
+ }
+ return dummy;
+ }
+
+ public int timeLongs(int reps) {
+ int dummy = 0;
+ for (int i = 0; i < reps; i++) {
+ dummy += strategy.copy(longArray).hashCode();
+ }
+ return dummy;
+ }
+
+ public int timeShorts(int reps) {
+ int dummy = 0;
+ for (int i = 0; i < reps; i++) {
+ dummy += strategy.copy(shortArray).hashCode();
+ }
+ return dummy;
+ }
+
+ public static void main(String[] args) {
+ Runner.main(CopyArrayBenchmark.class, args);
+ }
+}
diff --git a/examples/src/main/java/examples/DemoBenchmark.java b/examples/src/main/java/examples/DemoBenchmark.java
new file mode 100644
index 0000000..3b7e2dd
--- /dev/null
+++ b/examples/src/main/java/examples/DemoBenchmark.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package examples;
+
+import com.google.caliper.Param;
+import com.google.caliper.api.Benchmark;
+import com.google.caliper.api.SkipThisScenarioException;
+import com.google.caliper.api.VmParam;
+import com.google.caliper.runner.CaliperMain;
+import com.google.caliper.util.ShortDuration;
+
+import java.math.BigDecimal;
+
+public class DemoBenchmark extends Benchmark {
+ @Param({"abc", "def", "xyz"}) String string;
+ @Param({"1", "2"}) int number;
+ @Param Foo foo;
+
+ @Param({"0.00", "123.45"}) BigDecimal money;
+ @Param({"1ns", "2 minutes"}) ShortDuration duration;
+ @VmParam({"-Xmx32m", "-Xmx1g"}) String memoryMax;
+
+ enum Foo {
+ FOO, BAR, BAZ, QUX;
+ }
+
+ DemoBenchmark() {
+// System.out.println("I should not do this.");
+ }
+
+ @Override protected void setUp() throws Exception {
+ if (string.equals("abc") && number == 1) {
+ throw new SkipThisScenarioException();
+ }
+ }
+
+ public int timeSomething(int reps) {
+ int dummy = 0;
+ for (int i = 0; i < reps; i++) {
+ dummy += i;
+ }
+ return dummy;
+ }
+
+ public int timeSomethingElse(int reps) {
+ int dummy = 0;
+ for (int i = 0; i < reps; i++) {
+ dummy -= i;
+ }
+ return dummy;
+ }
+
+ @Override protected void tearDown() throws Exception {
+// System.out.println("Hey, I'm tearing up the joint.");
+ }
+
+ public static void main(String[] args) {
+ CaliperMain.main(DemoBenchmark.class, args);
+ }
+}
diff --git a/examples/src/main/java/examples/DoubleToStringBenchmark.java b/examples/src/main/java/examples/DoubleToStringBenchmark.java
new file mode 100644
index 0000000..291ec84
--- /dev/null
+++ b/examples/src/main/java/examples/DoubleToStringBenchmark.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package examples;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Measures the various ways the JDK converts doubles to strings.
+ */
+public class DoubleToStringBenchmark extends SimpleBenchmark {
+ @Param Method method;
+
+ public enum Method {
+ TO_STRING {
+ @Override String convert(double d) {
+ return ((Double) d).toString();
+ }
+ @Override String convert(Double d) {
+ return d.toString();
+ }
+ },
+ STRING_VALUE_OF {
+ @Override String convert(double d) {
+ return String.valueOf(d);
+ }
+ @Override String convert(Double d) {
+ return String.valueOf(d);
+ }
+ },
+ STRING_FORMAT {
+ @Override String convert(double d) {
+ return String.format("%f", d);
+ }
+ @Override String convert(Double d) {
+ return String.format("%f", d);
+ }
+ },
+ QUOTE_TRICK {
+ @Override String convert(double d) {
+ return "" + d;
+ }
+ @Override String convert(Double d) {
+ return "" + d;
+ }
+ },
+ ;
+
+ abstract String convert(double d);
+ abstract String convert(Double d);
+ }
+
+ enum Value {
+ Pi(Math.PI),
+ NegativeZero(-0.0),
+ NegativeInfinity(Double.NEGATIVE_INFINITY),
+ NaN(Double.NaN);
+
+ final double value;
+
+ Value(double value) {
+ this.value = value;
+ }
+ }
+
+ @Param Value value;
+
+ public int timePrimitive(int reps) {
+ double d = value.value;
+ int dummy = 0;
+ for (int i = 0; i < reps; i++) {
+ dummy += method.convert(d).length();
+ }
+ return dummy;
+ }
+
+ public int timeWrapper(int reps) {
+ Double d = value.value;
+ int dummy = 0;
+ for (int i = 0; i < reps; i++) {
+ dummy += method.convert(d).length();
+ }
+ return dummy;
+ }
+
+ public static void main(String[] args) throws Exception {
+ Runner.main(DoubleToStringBenchmark.class, args);
+ }
+}
diff --git a/examples/src/main/java/examples/DoubleToStringBenchmark2.java b/examples/src/main/java/examples/DoubleToStringBenchmark2.java
new file mode 100644
index 0000000..8e6c69a
--- /dev/null
+++ b/examples/src/main/java/examples/DoubleToStringBenchmark2.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package examples;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+/**
+ * Measures the various ways the JDK converts doubles to strings.
+ */
+public class DoubleToStringBenchmark2 extends SimpleBenchmark {
+ @Param boolean useWrapper;
+
+ enum Value {
+ Pi(Math.PI),
+ NegativeZero(-0.0),
+ NegativeInfinity(Double.NEGATIVE_INFINITY),
+ NaN(Double.NaN);
+
+ final double d;
+
+ Value(double d) {
+ this.d = d;
+ }
+ }
+
+ @Param Value value;
+
+ public int timeToString(int reps) {
+ int dummy = 0;
+ if (useWrapper) {
+ Double d = value.d;
+ for (int i = 0; i < reps; i++) {
+ dummy += d.toString().length();
+ }
+ } else {
+ double d = value.d;
+ for (int i = 0; i < reps; i++) {
+ dummy += ((Double) d).toString().length();
+ }
+ }
+ return dummy;
+ }
+
+ public int timeStringValueOf(int reps) {
+ int dummy = 0;
+ if (useWrapper) {
+ Double d = value.d;
+ for (int i = 0; i < reps; i++) {
+ dummy += String.valueOf(d).length();
+ }
+ } else {
+ double d = value.d;
+ for (int i = 0; i < reps; i++) {
+ dummy += String.valueOf(d).length();
+ }
+ }
+ return dummy;
+ }
+
+ public int timeStringFormat(int reps) {
+ int dummy = 0;
+ if (useWrapper) {
+ Double d = value.d;
+ for (int i = 0; i < reps; i++) {
+ dummy += String.format("%f", d).length();
+ }
+ } else {
+ double d = value.d;
+ for (int i = 0; i < reps; i++) {
+ dummy += String.format("%f", d).length();
+ }
+ }
+ return dummy;
+ }
+
+ public int timeQuoteTrick(int reps) {
+ int dummy = 0;
+ if (useWrapper) {
+ Double d = value.d;
+ for (int i = 0; i < reps; i++) {
+ dummy += ("" + d).length();
+ }
+ } else {
+ double d = value.d;
+ for (int i = 0; i < reps; i++) {
+ dummy += ("" + d).length();
+ }
+ }
+ return dummy;
+ }
+
+ public static void main(String[] args) throws Exception {
+ Runner.main(DoubleToStringBenchmark2.class, args);
+ }
+}
diff --git a/examples/src/main/java/examples/EnumSetContainsBenchmark.java b/examples/src/main/java/examples/EnumSetContainsBenchmark.java
new file mode 100644
index 0000000..b232514
--- /dev/null
+++ b/examples/src/main/java/examples/EnumSetContainsBenchmark.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package examples;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+import java.util.EnumSet;
+import java.util.Set;
+
+/**
+ * Measures EnumSet#contains().
+ */
+public class EnumSetContainsBenchmark extends SimpleBenchmark {
+
+ @Param private SetMaker setMaker;
+
+ public enum SetMaker {
+ ENUM_SET {
+ @Override Set<?> newSet() {
+ return EnumSet.allOf(RegularSize.class);
+ }
+ @Override Object[] testValues() {
+ return new Object[] { RegularSize.E1, RegularSize.E2, RegularSize.E20,
+ RegularSize.E39, RegularSize.E40, "A", LargeSize.E40, null };
+ }
+ },
+ LARGE_ENUM_SET {
+ @Override Set<?> newSet() {
+ return EnumSet.allOf(LargeSize.class);
+ }
+ @Override Object[] testValues() {
+ return new Object[] { LargeSize.E1, LargeSize.E63, LargeSize.E64,
+ LargeSize.E65, LargeSize.E140, "A", RegularSize.E40, null };
+ }
+ };
+
+ abstract Set<?> newSet();
+ abstract Object[] testValues();
+ }
+
+ private enum RegularSize {
+ E1, E2, E3, E4, E5, E6, E7, E8, E9, E10, E11, E12, E13, E14, E15, E16, E17,
+ E18, E19, E20, E21, E22, E23, E24, E25, E26, E27, E28, E29, E30, E31, E32,
+ E33, E34, E35, E36, E37, E38, E39, E40,
+ }
+
+ private enum LargeSize {
+ E1, E2, E3, E4, E5, E6, E7, E8, E9, E10, E11, E12, E13, E14, E15, E16, E17,
+ E18, E19, E20, E21, E22, E23, E24, E25, E26, E27, E28, E29, E30, E31, E32,
+ E33, E34, E35, E36, E37, E38, E39, E40, E41, E42, E43, E44, E45, E46, E47,
+ E48, E49, E50, E51, E52, E53, E54, E55, E56, E57, E58, E59, E60, E61, E62,
+ E63, E64, E65, E66, E67, E68, E69, E70, E71, E72, E73, E74, E75, E76, E77,
+ E78, E79, E80, E81, E82, E83, E84, E85, E86, E87, E88, E89, E90, E91, E92,
+ E93, E94, E95, E96, E97, E98, E99, E100, E101, E102, E103, E104, E105, E106,
+ E107, E108, E109, E110, E111, E112, E113, E114, E115, E116, E117, E118,
+ E119, E120, E121, E122, E123, E124, E125, E126, E127, E128, E129, E130,
+ E131, E132, E133, E134, E135, E136, E137, E138, E139, E140,
+ }
+
+ private Set<?> set;
+ private Object[] testValues;
+
+ @Override protected void setUp() {
+ this.set = setMaker.newSet();
+ this.testValues = setMaker.testValues();
+ }
+
+ public void timeContains(int reps) {
+ for (int i = 0; i < reps; i++) {
+ set.contains(testValues[i % testValues.length]);
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ Runner.main(EnumSetContainsBenchmark.class, args);
+ }
+}
diff --git a/examples/src/main/java/examples/ExpensiveObjectsBenchmark.java b/examples/src/main/java/examples/ExpensiveObjectsBenchmark.java
new file mode 100644
index 0000000..a11b1bd
--- /dev/null
+++ b/examples/src/main/java/examples/ExpensiveObjectsBenchmark.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package examples;
+
+import com.google.caliper.SimpleBenchmark;
+import com.google.caliper.runner.CaliperMain;
+
+import java.text.DecimalFormatSymbols;
+import java.text.NumberFormat;
+import java.text.SimpleDateFormat;
+import java.util.Locale;
+
+/**
+ * Benchmarks creation and cloning various expensive objects.
+ */
+@SuppressWarnings({"ResultOfObjectAllocationIgnored"}) // TODO: should fix!
+public class ExpensiveObjectsBenchmark extends SimpleBenchmark {
+ public void timeNewDecimalFormatSymbols(int reps) {
+ for (int i = 0; i < reps; ++i) {
+ new DecimalFormatSymbols(Locale.US);
+ }
+ }
+
+ public void timeClonedDecimalFormatSymbols(int reps) {
+ DecimalFormatSymbols dfs = new DecimalFormatSymbols(Locale.US);
+ for (int i = 0; i < reps; ++i) {
+ dfs.clone();
+ }
+ }
+
+ public void timeNewNumberFormat(int reps) {
+ for (int i = 0; i < reps; ++i) {
+ NumberFormat.getInstance(Locale.US);
+ }
+ }
+
+ public void timeClonedNumberFormat(int reps) {
+ NumberFormat nf = NumberFormat.getInstance(Locale.US);
+ for (int i = 0; i < reps; ++i) {
+ nf.clone();
+ }
+ }
+
+ public void timeNewSimpleDateFormat(int reps) {
+ for (int i = 0; i < reps; ++i) {
+ new SimpleDateFormat();
+ }
+ }
+
+ public void timeClonedSimpleDateFormat(int reps) {
+ SimpleDateFormat sdf = new SimpleDateFormat();
+ for (int i = 0; i < reps; ++i) {
+ sdf.clone();
+ }
+ }
+
+ // TODO: remove this from all examples when IDE plugins are ready
+ public static void main(String[] args) throws Exception {
+ CaliperMain.main(ExpensiveObjectsBenchmark.class, args);
+// Runner.main(ExpensiveObjectsBenchmark.class, args);
+ }
+}
diff --git a/examples/src/main/java/examples/FormatterBenchmark.java b/examples/src/main/java/examples/FormatterBenchmark.java
new file mode 100644
index 0000000..b4a0541
--- /dev/null
+++ b/examples/src/main/java/examples/FormatterBenchmark.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package examples;
+
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+import java.util.Formatter;
+
+/**
+ * Compares Formatter against hand-written StringBuilder code.
+ */
+public class FormatterBenchmark extends SimpleBenchmark {
+ public void timeFormatter_NoFormatting(int reps) {
+ for (int i = 0; i < reps; i++) {
+ Formatter f = new Formatter();
+ f.format("this is a reasonably short string that doesn't actually need any formatting");
+ }
+ }
+
+ public void timeStringBuilder_NoFormatting(int reps) {
+ for (int i = 0; i < reps; i++) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("this is a reasonably short string that doesn't actually need any formatting");
+ }
+ }
+
+ public void timeFormatter_OneInt(int reps) {
+ for (int i = 0; i < reps; i++) {
+ Formatter f = new Formatter();
+ f.format("this is a reasonably short string that has an int %d in it", i);
+ }
+ }
+
+ public void timeStringBuilder_OneInt(int reps) {
+ for (int i = 0; i < reps; i++) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("this is a reasonably short string that has an int ");
+ sb.append(i);
+ sb.append(" in it");
+ }
+ }
+
+ public void timeFormatter_OneString(int reps) {
+ for (int i = 0; i < reps; i++) {
+ Formatter f = new Formatter();
+ f.format("this is a reasonably short string that has a string %s in it", "hello");
+ }
+ }
+
+ public void timeStringBuilder_OneString(int reps) {
+ for (int i = 0; i < reps; i++) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("this is a reasonably short string that has a string ");
+ sb.append("hello");
+ sb.append(" in it");
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ Runner.main(FormatterBenchmark.class, args);
+ }
+}
diff --git a/examples/src/main/java/examples/IntModBenchmark.java b/examples/src/main/java/examples/IntModBenchmark.java
new file mode 100644
index 0000000..55a119c
--- /dev/null
+++ b/examples/src/main/java/examples/IntModBenchmark.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package examples;
+
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+/**
+ * Measures several candidate implementations for mod().
+ */
+@SuppressWarnings("SameParameterValue")
+public class IntModBenchmark extends SimpleBenchmark {
+ private static final int M = (1 << 16) - 1;
+
+ public int timeConditional(int reps) {
+ int dummy = 5;
+ for (int i = 0; i < reps; i++) {
+ dummy += Integer.MAX_VALUE + conditionalMod(dummy, M);
+ }
+ return dummy;
+ }
+
+ private static int conditionalMod(int a, int m) {
+ int r = a % m;
+ return r < 0 ? r + m : r;
+ }
+
+ public int timeDoubleRemainder(int reps) {
+ int dummy = 5;
+ for (int i = 0; i < reps; i++) {
+ dummy += Integer.MAX_VALUE + doubleRemainderMod(dummy, M);
+ }
+ return dummy;
+ }
+
+ @SuppressWarnings("NumericCastThatLosesPrecision") // result of % by an int must be in int range
+ private static int doubleRemainderMod(int a, int m) {
+ return (int) ((a % m + (long) m) % m);
+ }
+
+ public int timeRightShiftingMod(int reps) {
+ int dummy = 5;
+ for (int i = 0; i < reps; i++) {
+ dummy += Integer.MAX_VALUE + rightShiftingMod(dummy, M);
+ }
+ return dummy;
+ }
+
+ @SuppressWarnings("NumericCastThatLosesPrecision") // must be in int range
+ private static int rightShiftingMod(int a, int m) {
+ long r = a % m;
+ return (int) (r + (r >> 63 & m));
+ }
+
+ public int timeLeftShiftingMod(int reps) {
+ int dummy = 5;
+ for (int i = 0; i < reps; i++) {
+ dummy += Integer.MAX_VALUE + leftShiftingMod(dummy, M);
+ }
+ return dummy;
+ }
+
+ @SuppressWarnings("NumericCastThatLosesPrecision") // result of % by an int must be in int range
+ private static int leftShiftingMod(int a, int m) {
+ return (int) ((a + ((long) m << 32)) % m);
+ }
+
+ public int timeWrongMod(int reps) {
+ int dummy = 5;
+ for (int i = 0; i < reps; i++) {
+ dummy += Integer.MAX_VALUE + dummy % M;
+ }
+ return dummy;
+ }
+
+ // TODO: remove this from all examples when IDE plugins are ready
+ public static void main(String[] args) throws Exception {
+ Runner.main(IntModBenchmark.class, args);
+ }
+} \ No newline at end of file
diff --git a/examples/src/main/java/examples/ListIterationBenchmark.java b/examples/src/main/java/examples/ListIterationBenchmark.java
new file mode 100644
index 0000000..a8cfb05
--- /dev/null
+++ b/examples/src/main/java/examples/ListIterationBenchmark.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package examples;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+import java.util.AbstractList;
+import java.util.List;
+
+/**
+ * Measures iterating through list elements.
+ */
+public class ListIterationBenchmark extends SimpleBenchmark {
+
+ @Param({"0", "10", "100", "1000"})
+ private int length;
+
+ private List<Object> list;
+ private Object[] array;
+
+ @Override protected void setUp() {
+ array = new Object[length];
+ for (int i = 0; i < length; i++) {
+ array[i] = new Object();
+ }
+
+ list = new AbstractList<Object>() {
+ @Override public int size() {
+ return length;
+ }
+
+ @Override public Object get(int i) {
+ return array[i];
+ }
+ };
+ }
+
+ @SuppressWarnings({"UnusedDeclaration"}) // TODO: fix
+ public void timeListIteration(int reps) {
+ for (int i = 0; i < reps; i++) {
+ for (Object value : list) {
+ }
+ }
+ }
+
+ @SuppressWarnings({"UnusedDeclaration"}) // TODO: fix
+ public void timeArrayIteration(int reps) {
+ for (int i = 0; i < reps; i++) {
+ for (Object value : array) {
+ }
+ }
+ }
+
+ // TODO: remove this from all examples when IDE plugins are ready
+ public static void main(String[] args) throws Exception {
+ Runner.main(ListIterationBenchmark.class, args);
+ }
+} \ No newline at end of file
diff --git a/examples/src/main/java/examples/LoopingBackwardsBenchmark.java b/examples/src/main/java/examples/LoopingBackwardsBenchmark.java
new file mode 100644
index 0000000..1e3d1ad
--- /dev/null
+++ b/examples/src/main/java/examples/LoopingBackwardsBenchmark.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package examples;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+/**
+ * Testing the old canard that looping backwards is faster.
+ */
+public class LoopingBackwardsBenchmark extends SimpleBenchmark {
+ @Param({"2", "20", "2000", "20000000"}) int max;
+
+ public int timeForwards(int reps) {
+ int dummy = 0;
+ for (int i = 0; i < reps; i++) {
+ for (int j = 0; j < max; j++) {
+ dummy += j;
+ }
+ }
+ return dummy;
+ }
+
+ public int timeBackwards(int reps) {
+ int dummy = 0;
+ for (int i = 0; i < reps; i++) {
+ for (int j = max - 1; j >= 0; j--) {
+ dummy += j;
+ }
+ }
+ return dummy;
+ }
+
+ public static void main(String[] args) throws Exception {
+ Runner.main(LoopingBackwardsBenchmark.class, args);
+ }
+}
diff --git a/examples/src/main/java/examples/MessageDigestCreationBenchmark.java b/examples/src/main/java/examples/MessageDigestCreationBenchmark.java
new file mode 100644
index 0000000..c9437bd
--- /dev/null
+++ b/examples/src/main/java/examples/MessageDigestCreationBenchmark.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package examples;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+import java.security.MessageDigest;
+
+/**
+ * Times creating new MessageDigest instances.
+ */
+public class MessageDigestCreationBenchmark extends SimpleBenchmark {
+ // By default, just the "interesting ones". Also consider Adler32 and CRC32,
+ // but these are not guaranteed to be supported in all runtime environments.
+ @Param({"MD5", "SHA-1", "SHA-256", "SHA-512"})
+ String algorithm;
+
+ public void time(int reps) throws Exception {
+ // Change this to use a dummy if the results look suspicious.
+ for (int i = 0; i < reps; i++) {
+ MessageDigest.getInstance(algorithm);
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ Runner.main(MessageDigestCreationBenchmark.class, args);
+ }
+}
diff --git a/examples/src/main/java/examples/StringBuilderBenchmark.java b/examples/src/main/java/examples/StringBuilderBenchmark.java
new file mode 100644
index 0000000..3c839d3
--- /dev/null
+++ b/examples/src/main/java/examples/StringBuilderBenchmark.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package examples;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+/**
+ * Tests the performance of various StringBuilder methods.
+ */
+public class StringBuilderBenchmark extends SimpleBenchmark {
+
+ @Param({"1", "10", "100"}) private int length;
+
+ public void timeAppendBoolean(int reps) {
+ for (int i = 0; i < reps; ++i) {
+ StringBuilder sb = new StringBuilder();
+ for (int j = 0; j < length; ++j) {
+ sb.append(true);
+ }
+ }
+ }
+
+ public void timeAppendChar(int reps) {
+ for (int i = 0; i < reps; ++i) {
+ StringBuilder sb = new StringBuilder();
+ for (int j = 0; j < length; ++j) {
+ sb.append('c');
+ }
+ }
+ }
+
+ public void timeAppendCharArray(int reps) {
+ char[] chars = "chars".toCharArray();
+ for (int i = 0; i < reps; ++i) {
+ StringBuilder sb = new StringBuilder();
+ for (int j = 0; j < length; ++j) {
+ sb.append(chars);
+ }
+ }
+ }
+
+ public void timeAppendCharSequence(int reps) {
+ CharSequence cs = "chars";
+ for (int i = 0; i < reps; ++i) {
+ StringBuilder sb = new StringBuilder();
+ for (int j = 0; j < length; ++j) {
+ sb.append(cs);
+ }
+ }
+ }
+
+ public void timeAppendDouble(int reps) {
+ double d = 1.2;
+ for (int i = 0; i < reps; ++i) {
+ StringBuilder sb = new StringBuilder();
+ for (int j = 0; j < length; ++j) {
+ sb.append(d);
+ }
+ }
+ }
+
+ public void timeAppendFloat(int reps) {
+ float f = 1.2f;
+ for (int i = 0; i < reps; ++i) {
+ StringBuilder sb = new StringBuilder();
+ for (int j = 0; j < length; ++j) {
+ sb.append(f);
+ }
+ }
+ }
+
+ public void timeAppendInt(int reps) {
+ int n = 123;
+ for (int i = 0; i < reps; ++i) {
+ StringBuilder sb = new StringBuilder();
+ for (int j = 0; j < length; ++j) {
+ sb.append(n);
+ }
+ }
+ }
+
+ public void timeAppendLong(int reps) {
+ long l = 123;
+ for (int i = 0; i < reps; ++i) {
+ StringBuilder sb = new StringBuilder();
+ for (int j = 0; j < length; ++j) {
+ sb.append(l);
+ }
+ }
+ }
+
+ public void timeAppendObject(int reps) {
+ Object o = new Object();
+ for (int i = 0; i < reps; ++i) {
+ StringBuilder sb = new StringBuilder();
+ for (int j = 0; j < length; ++j) {
+ sb.append(o);
+ }
+ }
+ }
+
+ public void timeAppendString(int reps) {
+ String s = "chars";
+ for (int i = 0; i < reps; ++i) {
+ StringBuilder sb = new StringBuilder();
+ for (int j = 0; j < length; ++j) {
+ sb.append(s);
+ }
+ }
+ }
+
+ // TODO: remove this from all examples when IDE plugins are ready
+ public static void main(String[] args) throws Exception {
+ Runner.main(StringBuilderBenchmark.class, args);
+ }
+}
diff --git a/lib/gson-1.7.1.jar b/lib/gson-1.7.1.jar
new file mode 100644
index 0000000..eb54274
--- /dev/null
+++ b/lib/gson-1.7.1.jar
Binary files differ
diff --git a/lib/java-allocation-instrumenter-2.0.jar b/lib/java-allocation-instrumenter-2.0.jar
new file mode 100644
index 0000000..8ea4193
--- /dev/null
+++ b/lib/java-allocation-instrumenter-2.0.jar
Binary files differ
diff --git a/scripts/caliper b/scripts/caliper
new file mode 100644
index 0000000..a25caf1
--- /dev/null
+++ b/scripts/caliper
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+# rough
+
+export PATH=$PATH:$JAVA_HOME/bin
+base=`dirname $0`
+export ALLOCATION_JAR=$base/lib/allocation.jar
+exec java -cp $base/lib/caliper-@VERSION@.jar:$ALLOCATION_JAR:$CLASSPATH com.google.caliper.Runner $*
+
diff --git a/tutorial/Tutorial.java b/tutorial/Tutorial.java
new file mode 100644
index 0000000..625d387
--- /dev/null
+++ b/tutorial/Tutorial.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package tutorial;
+
+import com.google.caliper.Param;
+import com.google.caliper.SimpleBenchmark;
+
+/**
+ * Caliper tutorial. To run the example benchmarks in this file:
+ * {@code CLASSPATH=... [caliper_home]/caliper tutorial.Tutorial.Benchmark1}
+ */
+public class Tutorial {
+
+ /*
+ * We begin the Caliper tutorial with the simplest benchmark you can write.
+ * We'd like to know how efficient the method System.nanoTime() is.
+ *
+ * Notice:
+ *
+ * - We write a class that extends com.google.caliper.SimpleBenchmark.
+ * - It contains a public instance method whose name begins with 'time' and
+ * which accepts a single 'int reps' parameter.
+ * - The body of the method simply executes the code we wish to measure,
+ * 'reps' times.
+ *
+ * Example run:
+ *
+ * $ CLASSPATH=build/classes/test caliper tutorial.Tutorial.Benchmark1
+ * [real-time results appear on this line]
+ *
+ * Summary report for tutorial.Tutorial$Benchmark1:
+ *
+ * Benchmark ns
+ * --------- ---
+ * NanoTime 233
+ */
+ public static class Benchmark1 extends SimpleBenchmark {
+ public void timeNanoTime(int reps) {
+ for (int i = 0; i < reps; i++) {
+ System.nanoTime();
+ }
+ }
+ }
+
+ /*
+ * Now let's compare two things: nanoTime() versus currentTimeMillis().
+ * Notice:
+ *
+ * - We simply add another method, following the same rules as the first.
+ *
+ * Example run output:
+ *
+ * Benchmark ns
+ * ----------------- ---
+ * NanoTime 248
+ * CurrentTimeMillis 118
+ */
+ public static class Benchmark2 extends SimpleBenchmark {
+ public void timeNanoTime(int reps) {
+ for (int i = 0; i < reps; i++) {
+ System.nanoTime();
+ }
+ }
+ public void timeCurrentTimeMillis(int reps) {
+ for (int i = 0; i < reps; i++) {
+ System.currentTimeMillis();
+ }
+ }
+ }
+
+ /*
+ * Let's try iterating over a large array. This seems simple enough, but
+ * there is a problem!
+ */
+ public static class Benchmark3 extends SimpleBenchmark {
+ private final int[] array = new int[1000000];
+
+ @SuppressWarnings("UnusedDeclaration") // IDEA tries to warn us!
+ public void timeArrayIteration_BAD(int reps) {
+ for (int i = 0; i < reps; i++) {
+ for (int ignoreMe : array) {}
+ }
+ }
+ }
+
+ /*
+ * Caliper reported that the benchmark above ran in 4 nanoseconds.
+ *
+ * Wait, what?
+ *
+ * How can it possibly iterate over a million zeroes in 4 ns!?
+ *
+ * It is very important to sanity-check benchmark results with common sense!
+ * In this case, we're indeed getting a bogus result. The problem is that the
+ * Java Virtual Machine is too smart: it detected the fact that the loop was
+ * producing no actual result, so it simply compiled it right out. The method
+ * never looped at all. To fix this, we need to use a dummy result value.
+ *
+ * Notice:
+ *
+ * - We simply change the 'time' method from 'void' to any return type we
+ * wish. Then we return a value that can't be known without actually
+ * performing the work, and thus we defeat the runtime optimizations.
+ * - We're no longer timing *just* the code we want to be testing - our
+ * result will now be inflated by the (small) cost of addition. This is an
+ * unfortunate fact of life with microbenchmarking. In fact, we were
+ * already inflated by the cost of an int comparison, "i < reps" as it was.
+ *
+ * With this change, Caliper should report a much more realistic value, more
+ * on the order of an entire millisecond.
+ */
+ public static class Benchmark4 extends SimpleBenchmark {
+ private final int[] array = new int[1000000];
+
+ public int timeArrayIteration_fixed(int reps) {
+ int dummy = 0;
+ for (int i = 0; i < reps; i++) {
+ for (int doNotIgnoreMe : array) {
+ dummy += doNotIgnoreMe;
+ }
+ }
+ return dummy; // framework ignores this, but it has served its purpose!
+ }
+ }
+
+ /*
+ * Now we'd like to know how various other *sizes* of arrays perform. We
+ * don't want to have to cut and paste the whole benchmark just to provide a
+ * different size. What we need is a parameter!
+ *
+ * When you run this benchmark the same way you ran the previous ones, you'll
+ * now get an error: "No values provided for benchmark parameter 'size'".
+ * You can provide the value requested at the command line like this:
+ *
+ * [caliper_home]/caliper tutorial.Tutorial.Benchmark5 -Dsize=100}
+ *
+ * You'll see output like this:
+ *
+ * Benchmark size ns
+ * -------------- ---- ---
+ * ArrayIteration 100 51
+ *
+ * Now that we've parameterized our benchmark, things are starting to get fun.
+ * Try passing '-Dsize=10,100,1000' and see what happens!
+ *
+ * Benchmark size ns
+ * -------------- ---- -----------------------------------
+ * ArrayIteration 10 7 |
+ * ArrayIteration 100 49 ||||
+ * ArrayIteration 1000 477 ||||||||||||||||||||||||||||||
+ *
+ */
+ public static class Benchmark5 extends SimpleBenchmark {
+ @Param int size; // set automatically by framework
+
+ private int[] array; // set by us, in setUp()
+
+ @Override protected void setUp() {
+ // @Param values are guaranteed to have been injected by now
+ array = new int[size];
+ }
+
+ public int timeArrayIteration(int reps) {
+ int dummy = 0;
+ for (int i = 0; i < reps; i++) {
+ for (int doNotIgnoreMe : array) {
+ dummy += doNotIgnoreMe;
+ }
+ }
+ return dummy;
+ }
+ }
+}