• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.google.caliper.runner;
18 
19 import static com.google.common.base.Preconditions.checkArgument;
20 import static com.google.common.base.Preconditions.checkNotNull;
21 import static com.google.common.base.Throwables.propagateIfInstanceOf;
22 import static java.util.logging.Level.SEVERE;
23 
24 import com.google.caliper.Benchmark;
25 import com.google.caliper.api.SkipThisScenarioException;
26 import com.google.caliper.config.VmConfig;
27 import com.google.caliper.platform.Platform;
28 import com.google.caliper.platform.SupportedPlatform;
29 import com.google.caliper.worker.MacrobenchmarkAllocationWorker;
30 import com.google.caliper.worker.MicrobenchmarkAllocationWorker;
31 import com.google.caliper.worker.Worker;
32 import com.google.common.base.Optional;
33 import com.google.common.base.Strings;
34 import com.google.common.collect.ImmutableMap;
35 import com.google.common.collect.ImmutableSet;
36 import com.google.monitoring.runtime.instrumentation.AllocationInstrumenter;
37 
38 import java.io.File;
39 import java.io.IOException;
40 import java.lang.reflect.InvocationTargetException;
41 import java.lang.reflect.Method;
42 import java.util.jar.JarFile;
43 import java.util.jar.Manifest;
44 import 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)
56 public 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
isBenchmarkMethod(Method method)66   public boolean isBenchmarkMethod(Method method) {
67     return method.isAnnotationPresent(Benchmark.class) || BenchmarkMethods.isTimeMethod(method);
68   }
69 
70   @Override
createInstrumentation(Method benchmarkMethod)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 {
MicroAllocationInstrumentation(Method benchmarkMethod)92     MicroAllocationInstrumentation(Method benchmarkMethod) {
93       super(benchmarkMethod);
94     }
95 
96     @Override
dryRun(Object benchmark)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 
workerOptions()111     @Override public ImmutableMap<String, String> workerOptions() {
112       return ImmutableMap.of(TRACK_ALLOCATIONS_OPTION, options.get(TRACK_ALLOCATIONS_OPTION));
113     }
114 
115     @Override
workerClass()116     public Class<? extends Worker> workerClass() {
117       return MicrobenchmarkAllocationWorker.class;
118     }
119 
120     @Override
getMeasurementCollectingVisitor()121     MeasurementCollectingVisitor getMeasurementCollectingVisitor() {
122       return new Instrument.DefaultMeasurementCollectingVisitor(
123           ImmutableSet.of("bytes", "objects"));
124     }
125   }
126 
schedulingPolicy()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 {
MacroAllocationInstrumentation(Method benchmarkMethod)133     MacroAllocationInstrumentation(Method benchmarkMethod) {
134       super(benchmarkMethod);
135     }
136 
137     @Override
dryRun(Object benchmark)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 
workerOptions()152     @Override public ImmutableMap<String, String> workerOptions() {
153       return ImmutableMap.of(TRACK_ALLOCATIONS_OPTION, options.get(TRACK_ALLOCATIONS_OPTION));
154     }
155 
156     @Override
workerClass()157     public Class<? extends Worker> workerClass() {
158       return MacrobenchmarkAllocationWorker.class;
159     }
160 
161     @Override
getMeasurementCollectingVisitor()162     MeasurementCollectingVisitor getMeasurementCollectingVisitor() {
163       return new Instrument.DefaultMeasurementCollectingVisitor(
164           ImmutableSet.of("bytes", "objects"));
165     }
166   }
167 
168   @Override
instrumentOptions()169   public ImmutableSet<String> instrumentOptions() {
170     return ImmutableSet.of(ALLOCATION_AGENT_JAR_OPTION, TRACK_ALLOCATIONS_OPTION);
171   }
172 
findAllocationInstrumentJarOnClasspath()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    */
getExtraCommandLineArgs(VmConfig vmConfig)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 }
230