blob: 74691177f5fac171ac3eb01a23e620c2e0e2b357 [file] [log] [blame]
Paul Duffine2363012015-11-30 16:20:41 +00001/*
2 * Copyright (C) 2011 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.google.caliper.runner;
18
19import static com.google.common.base.Preconditions.checkArgument;
20import static com.google.common.base.Preconditions.checkNotNull;
21import static com.google.common.base.Throwables.propagateIfInstanceOf;
22import static java.util.logging.Level.SEVERE;
23
24import com.google.caliper.Benchmark;
25import com.google.caliper.api.SkipThisScenarioException;
26import com.google.caliper.config.VmConfig;
27import com.google.caliper.platform.Platform;
28import com.google.caliper.platform.SupportedPlatform;
29import com.google.caliper.worker.MacrobenchmarkAllocationWorker;
30import com.google.caliper.worker.MicrobenchmarkAllocationWorker;
31import com.google.caliper.worker.Worker;
32import com.google.common.base.Optional;
33import com.google.common.base.Strings;
34import com.google.common.collect.ImmutableMap;
35import com.google.common.collect.ImmutableSet;
36import com.google.monitoring.runtime.instrumentation.AllocationInstrumenter;
37
38import java.io.File;
39import java.io.IOException;
40import java.lang.reflect.InvocationTargetException;
41import java.lang.reflect.Method;
42import java.util.jar.JarFile;
43import java.util.jar.Manifest;
44import java.util.logging.Logger;
45
46/**
47 * {@link Instrument} that watches the memory allocations in an invocation of the
48 * benchmark method and reports some statistic. The benchmark method must accept a
49 * single int argument 'reps', which is the number of times to execute the guts of
50 * the benchmark method, and it must be public and non-static.
51 *
52 * <p>Note that the allocation instruments reports a "worst case" for allocation in that it reports
53 * the bytes and objects allocated in interpreted mode (no JIT).
54 */
55@SupportedPlatform(Platform.Type.JVM)
56public final class AllocationInstrument extends Instrument {
57 private static final String ALLOCATION_AGENT_JAR_OPTION = "allocationAgentJar";
58 /**
59 * If this option is set to {@code true} then every individual allocation will be tracked and
60 * logged. This will also increase the detail of certain error messages.
61 */
62 private static final String TRACK_ALLOCATIONS_OPTION = "trackAllocations";
63 private static final Logger logger = Logger.getLogger(AllocationInstrument.class.getName());
64
65 @Override
66 public boolean isBenchmarkMethod(Method method) {
67 return method.isAnnotationPresent(Benchmark.class) || BenchmarkMethods.isTimeMethod(method);
68 }
69
70 @Override
71 public Instrumentation createInstrumentation(Method benchmarkMethod)
72 throws InvalidBenchmarkException {
73 checkNotNull(benchmarkMethod);
74 checkArgument(isBenchmarkMethod(benchmarkMethod));
75 try {
76 switch (BenchmarkMethods.Type.of(benchmarkMethod)) {
77 case MACRO:
78 return new MacroAllocationInstrumentation(benchmarkMethod);
79 case MICRO:
80 case PICO:
81 return new MicroAllocationInstrumentation(benchmarkMethod);
82 default:
83 throw new AssertionError("unknown type");
84 }
85 } catch (IllegalArgumentException e) {
86 throw new InvalidBenchmarkException("Benchmark methods must have no arguments or accept "
87 + "a single int or long parameter: %s", benchmarkMethod.getName());
88 }
89 }
90
91 private final class MicroAllocationInstrumentation extends Instrumentation {
92 MicroAllocationInstrumentation(Method benchmarkMethod) {
93 super(benchmarkMethod);
94 }
95
96 @Override
97 public void dryRun(Object benchmark) throws UserCodeException {
98 // execute the benchmark method, but don't try to take any measurements, because this JVM
99 // may not have the allocation instrumenter agent.
100 try {
101 benchmarkMethod.invoke(benchmark, 1);
102 } catch (IllegalAccessException impossible) {
103 throw new AssertionError(impossible);
104 } catch (InvocationTargetException e) {
105 Throwable userException = e.getCause();
106 propagateIfInstanceOf(userException, SkipThisScenarioException.class);
107 throw new UserCodeException(userException);
108 }
109 }
110
111 @Override public ImmutableMap<String, String> workerOptions() {
112 return ImmutableMap.of(TRACK_ALLOCATIONS_OPTION, options.get(TRACK_ALLOCATIONS_OPTION));
113 }
114
115 @Override
116 public Class<? extends Worker> workerClass() {
117 return MicrobenchmarkAllocationWorker.class;
118 }
119
120 @Override
121 MeasurementCollectingVisitor getMeasurementCollectingVisitor() {
122 return new Instrument.DefaultMeasurementCollectingVisitor(
123 ImmutableSet.of("bytes", "objects"));
124 }
125 }
126
127 @Override public TrialSchedulingPolicy schedulingPolicy() {
128 // Assuming there is enough memory it should be fine to run these in parallel.
129 return TrialSchedulingPolicy.PARALLEL;
130 }
131
132 private final class MacroAllocationInstrumentation extends Instrumentation {
133 MacroAllocationInstrumentation(Method benchmarkMethod) {
134 super(benchmarkMethod);
135 }
136
137 @Override
138 public void dryRun(Object benchmark) throws InvalidBenchmarkException {
139 // execute the benchmark method, but don't try to take any measurements, because this JVM
140 // may not have the allocation instrumenter agent.
141 try {
142 benchmarkMethod.invoke(benchmark);
143 } catch (IllegalAccessException impossible) {
144 throw new AssertionError(impossible);
145 } catch (InvocationTargetException e) {
146 Throwable userException = e.getCause();
147 propagateIfInstanceOf(userException, SkipThisScenarioException.class);
148 throw new UserCodeException(userException);
149 }
150 }
151
152 @Override public ImmutableMap<String, String> workerOptions() {
153 return ImmutableMap.of(TRACK_ALLOCATIONS_OPTION, options.get(TRACK_ALLOCATIONS_OPTION));
154 }
155
156 @Override
157 public Class<? extends Worker> workerClass() {
158 return MacrobenchmarkAllocationWorker.class;
159 }
160
161 @Override
162 MeasurementCollectingVisitor getMeasurementCollectingVisitor() {
163 return new Instrument.DefaultMeasurementCollectingVisitor(
164 ImmutableSet.of("bytes", "objects"));
165 }
166 }
167
168 @Override
169 public ImmutableSet<String> instrumentOptions() {
170 return ImmutableSet.of(ALLOCATION_AGENT_JAR_OPTION, TRACK_ALLOCATIONS_OPTION);
171 }
172
173 private static Optional<File> findAllocationInstrumentJarOnClasspath() throws IOException {
174 ImmutableSet<File> jarFiles = JarFinder.findJarFiles(
175 Thread.currentThread().getContextClassLoader(),
176 ClassLoader.getSystemClassLoader());
177 for (File file : jarFiles) {
178 JarFile jarFile = null;
179 try {
180 jarFile = new JarFile(file);
181 Manifest manifest = jarFile.getManifest();
182 if ((manifest != null)
183 && AllocationInstrumenter.class.getName().equals(
184 manifest.getMainAttributes().getValue("Premain-Class"))) {
185 return Optional.of(file);
186 }
187 } finally {
188 if (jarFile != null) {
189 jarFile.close();
190 }
191 }
192 }
193 return Optional.absent();
194 }
195
196 /**
197 * This instrument's worker requires the allocationinstrumenter agent jar, specified
198 * on the worker VM's command line with "-javaagent:[jarfile]".
199 */
200 @Override ImmutableSet<String> getExtraCommandLineArgs(VmConfig vmConfig) {
201 String agentJar = options.get(ALLOCATION_AGENT_JAR_OPTION);
202 if (Strings.isNullOrEmpty(agentJar)) {
203 try {
204 Optional<File> instrumentJar = findAllocationInstrumentJarOnClasspath();
205 // TODO(gak): bundle up the allocation jar and unpack it if it's not on the classpath
206 if (instrumentJar.isPresent()) {
207 agentJar = instrumentJar.get().getAbsolutePath();
208 }
209 } catch (IOException e) {
210 logger.log(SEVERE,
211 "An exception occurred trying to locate the allocation agent jar on the classpath", e);
212 }
213 }
214 if (Strings.isNullOrEmpty(agentJar) || !new File(agentJar).exists()) {
215 throw new IllegalStateException("Can't find required allocationinstrumenter agent jar");
216 }
217 // Add microbenchmark args to minimize differences in the output
218 return new ImmutableSet.Builder<String>()
219 .addAll(super.getExtraCommandLineArgs(vmConfig))
220 // we just run in interpreted mode to ensure that intrinsics don't break the instrumentation
221 .add("-Xint")
222 .add("-javaagent:" + agentJar)
223 // Some environments rename files and use symlinks to improve resource caching,
224 // if the agent jar path is actually a symlink it will prevent the agent from finding itself
225 // and adding itself to the bootclasspath, so we do it manually here.
226 .add("-Xbootclasspath/a:" + agentJar)
227 .build();
228 }
229}