package org.radargun.config; import java.io.Serializable; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.regex.Pattern; import org.radargun.logging.Log; import org.radargun.logging.LogFactory; import org.radargun.utils.NumberConverter; import org.radargun.utils.ReflexiveConverters; import org.radargun.utils.SizeConverter; /** * Holds VM arguments configuration. Options descriptions are here: * http://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html All options are here: * http://hg.openjdk.java.net/jdk8/jdk8/hotspot/file/tip/src/share/vm/runtime/globals.hpp Another * good resource: * http://stas-blogspot.blogspot.com/2011/07/most-complete-list-of-xx-options-for.html * * @author Radim Vansa <rvansa@redhat.com> */ public class VmArgs implements Serializable { private static final Log log = LogFactory.getLog(VmArgs.class); @Property(doc = "Ignore all VM arguments passed to slave and use only those specified here. Default is false.") private Boolean ignoreDefault = false; @Property(doc = "Print all VM arguments. Default is false.") private Boolean printFlagsFinal = false; @PropertyDelegate(prefix = "memory.") private Memory memory = new Memory(); @PropertyDelegate(prefix = "gc.") private Gc gc = new Gc(); @PropertyDelegate(prefix = "jit.") private Jit jit = new Jit(); @PropertyDelegate(prefix = "flight-recorder.") private FlightRecorder flightRecorder = new FlightRecorder(); @PropertyDelegate(prefix = "class-loading.") private ClassLoading classLoading = new ClassLoading(); @Property(name = "unlock-diagnostic-vm-options", doc = "Unlock diagnostic VM options. Some other VM options may trigger this automically.") private Boolean unlockDiagnosticVMOptions; @Property(name = "unlock-experimental-vm-options", doc = "Unlock Experimental VM options.") private Boolean unlockExperimentalVMOptions = false; @Property(doc = "Properties (-Dfoo=bar)", complexConverter = Prop.Converter.class) private List<Prop> properties = Collections.emptyList(); @Property(doc = "Custom arguments.", complexConverter = Custom.Converter.class) private List<Custom> customArguments = Collections.emptyList(); public List<String> getVmArgs(Collection<String> defaultVmArgs) { List<String> vmArgs = ignoreDefault ? new LinkedList<String>() : new LinkedList<>(defaultVmArgs); Collection<Path> properties = PropertyHelper.getProperties(this.getClass(), true, true, false).values(); if (unlockDiagnosticVMOptions == null) { for (Path path : properties) { if (path.isAnnotationPresent(RequireDiagnostic.class)) { Object value = null; try { value = path.get(this); } catch (IllegalAccessException e) { throw new IllegalStateException(e); } if (value != null) { if (Boolean.FALSE.equals(unlockDiagnosticVMOptions)) { throw new IllegalStateException("JVM flag requires diagnostic VM options"); } else { unlockDiagnosticVMOptions = Boolean.TRUE; } } } } } if (unlockDiagnosticVMOptions != null) { set(vmArgs, "UnlockDiagnosticVMOptions", unlockDiagnosticVMOptions); } if (unlockExperimentalVMOptions) { set(vmArgs, "UnlockExperimentalVMOptions", unlockExperimentalVMOptions); } if (printFlagsFinal) { set(vmArgs, "PrintFlagsFinal", printFlagsFinal); } for (Path path : properties) { try { Object value = path.get(this); if (value instanceof VmArg) { ((VmArg) value).setArgs(vmArgs); } } catch (IllegalAccessException e) { log.warn("Failed to read path " + path, e); } } for (Custom c : customArguments) { c.setArgs(vmArgs); } for (Prop p : this.properties) { replace(vmArgs, "-D" + p.name + "=", p.value); } return vmArgs; } private static void replace(List<String> args, String prefix, String value) { for (Iterator<String> it = args.iterator(); it.hasNext();) { String arg = it.next(); if (arg.startsWith(prefix)) { it.remove(); } } args.add(prefix + value); } private static void set(List<String> args, String option, boolean on) { Pattern pattern = Pattern.compile("-XX:." + option); for (Iterator<String> it = args.iterator(); it.hasNext();) { String arg = it.next(); if (pattern.matcher(arg).matches()) { it.remove(); } } args.add("-XX:" + (on ? '+' : '-') + option); } private static void ensureArg(Collection<String> args, String arg) { if (!args.contains(arg)) args.add(arg); } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) private @interface RequireDiagnostic { } public interface VmArg extends Serializable { /* Override arguments */ void setArgs(List<String> args); } private class Memory implements VmArg { @Property(doc = "Max memory", converter = SizeConverter.class) private Long max; @Property(doc = "Min memory", converter = SizeConverter.class) private Long min; @Property(doc = "Initial size of the young generation", converter = SizeConverter.class) private Long newSize; @Property(doc = "Maximum size of the young generation", converter = SizeConverter.class) private Long maxNewSize; @Property(doc = "Thread stack size", converter = SizeConverter.class) private Long threadStackSize; @Property(doc = "Sets the ratio between young and old generation sizes", converter = NumberConverter.class) private Integer newRatio; @Property(doc = "Enables the use of large page memory.") private Boolean useLargePages; @Override public void setArgs(List<String> args) { if (min != null) replace(args, "-Xms", String.valueOf(min)); if (max != null) replace(args, "-Xmx", String.valueOf(max)); if (newSize != null) replace(args, "-XX:NewSize=", String.valueOf(newSize)); if (maxNewSize != null) replace(args, "-XX:MaxNewSize=", String.valueOf(maxNewSize)); if (threadStackSize != null) replace(args, "-Xss", String.valueOf(threadStackSize)); if (newRatio != null) replace(args, "-XX:NewRatio=", String.valueOf(newRatio)); if (useLargePages != null) set(args, "UseLargePages", useLargePages); } } private class FlightRecorder implements VmArg { @Property(doc = "Start flight recording for the benchmark.", optional = false) private boolean enabled = false; @Property(doc = "File for the recording.") private String filename; @Property(doc = "Settings file with recording configuration.") private String settings; @Override public void setArgs(List<String> args) { if (!enabled) return; StringBuilder recordingParams = new StringBuilder("=compress=false,delay=10s,duration=24h"); if (filename != null) recordingParams.append(",filename=").append(filename); if (settings != null) recordingParams.append(",settings=").append(settings); ensureArg(args, "-XX:+UnlockCommercialFeatures"); ensureArg(args, "-XX:+FlightRecorder"); replace(args, "-XX:StartFlightRecording", recordingParams.toString()); } } @DefinitionElement(name = "custom", doc = "Custom argument to VM.") private static class Custom implements VmArg { @Property(doc = "Argument as pasted on command-line", optional = false) private String arg; @Override public void setArgs(List<String> args) { // TODO: override instead of add args.add(arg); } private static class Converter extends ReflexiveConverters.ListConverter { public Converter() { super(new Class[] { Custom.class }); } } } private class Gc implements VmArg { @Property(doc = "Verbose GC log.") private Boolean printGc; @Property(doc = "Print more information about the GC.") private Boolean printGcDetails; @Property(doc = "Print timestamps of the GC.") private Boolean printGcTimestamps; @Property(doc = "Log file") private String logFile; @Property(doc = "Enables the use of the parallel scavenge garbage collector (also known " + "as the throughput collector) to improve the performance of your application by " + "leveraging multiple processors.") private Boolean useParallelGC; @Property(doc = "Sets the number of threads used for parallel garbage collection in the " + "young and old generations.", converter = NumberConverter.class) private Integer parallelGCThreads; @Property(doc = "Enables the use of the parallel garbage collector for full GCs.") private Boolean useParallelOldGC; @Property(doc = "Enables the use of the CMS garbage collector for the old generation.") private Boolean useConcMarkSweepGC; @Property(doc = "Enables the use of the garbage-first (G1) garbage collector.") private Boolean useG1GC; @Property(doc = "Enables the option that disables processing of calls to System.gc().") private Boolean disableExplicitGC; @Property(doc = "Sets the percentage of the heap occupancy (0 to 100) at which to start " + "a concurrent GC cycle.", converter = NumberConverter.class) private Integer initiatingHeapOccupancyPercent; @Property(doc = "Sets a target for the maximum GC pause time (in milliseconds).", converter = NumberConverter.class) private Integer maxGCPauseMillis; @Property(doc = "Sets the time interval over which GC pauses totaling up to " + "MaxGCPauseMillis may take place:", converter = NumberConverter.class) private Integer gcPauseIntervalMillis; @Property(doc = "Enables Java heap optimization.") private Boolean aggressiveHeap; @Property(doc = "Enables the use of aggressive performance optimization features, " + "which are expected to become default in upcoming releases.") private Boolean aggressiveOpts; @Property(doc = "Enables scavenging attempts before the CMS remark step.") private Boolean cmsScavengeBeforeRemark; @Property(doc = "Sets the percentage (0 to 100) of the value specified by " + "-XX:MinHeapFreeRatio that is allocated before a CMS collection cycle " + "commences.", converter = NumberConverter.class) private Integer cmsTriggerRatio; @Property(doc = "Adaptive size policy application time to GC time ratio", converter = NumberConverter.class) private Integer gcTimeRatio; @Override public void setArgs(List<String> args) { if (printGc != null) set(args, "PrintGC", printGc); if (printGcDetails != null) set(args, "PrintGCDetails", printGcDetails); if (printGcTimestamps != null) set(args, "PrintGCTimeStamps", printGcTimestamps); if (logFile != null) replace(args, "-Xloggc:", logFile); if (useParallelGC != null) set(args, "UseParallelGC", useParallelGC); if (useParallelOldGC != null) set(args, "UseParallelOldGC", useParallelOldGC); if (parallelGCThreads != null) replace(args, "-XX:ParallelGCThreads=", String.valueOf(parallelGCThreads)); if (useConcMarkSweepGC != null) set(args, "UseConcMarkSweepGC", useConcMarkSweepGC); if (useG1GC != null) set(args, "UseG1GC", useG1GC); if (disableExplicitGC != null) set(args, "DisableExplicitGC", disableExplicitGC); if (initiatingHeapOccupancyPercent != null) replace(args, "-XX:InitiatingHeapOccupancyPercent=", String.valueOf(initiatingHeapOccupancyPercent)); if (maxGCPauseMillis != null) replace(args, "-XX:MaxGCPauseMillis=", String.valueOf(maxGCPauseMillis)); if (gcPauseIntervalMillis != null) replace(args, "-XX:GCPauseIntervalMillis=", String.valueOf(gcPauseIntervalMillis)); if (aggressiveHeap != null) set(args, "AggressiveHeap", aggressiveHeap); if (aggressiveOpts != null) set(args, "AggressiveOpts", aggressiveOpts); if (cmsScavengeBeforeRemark != null) set(args, "CMSScavengeBeforeRemark", cmsScavengeBeforeRemark); if (cmsTriggerRatio != null) replace(args, "-XX:CMSTriggerRatio=", String.valueOf(cmsTriggerRatio)); } } @DefinitionElement(name = "property", doc = "JVM property, usually set by -D") private static class Prop implements Serializable { @Property(doc = "Name of the property", optional = false) private String name; @Property(doc = "Value of the property", optional = false) private String value; public static class Converter extends ReflexiveConverters.ListConverter { public Converter() { super(new Class[] { Prop.class }); } } } private class Jit implements VmArg { @Property(doc = "Preserve frame pointers in compiled code.") private Boolean preserveFramePointer; @Property(doc = "Print compilation.") private Boolean printCompilation; @RequireDiagnostic @Property(doc = "Print generated assembly code.") private Boolean printAssembly; @RequireDiagnostic @Property(doc = "Print method inlining") private Boolean printInlining; @RequireDiagnostic @Property(doc = "Log compilation.") private Boolean logCompilation; @RequireDiagnostic @Property(doc = "Log file for log-compilation") private String logFile; @Property(doc = "Maximum size of method bytecode to consider for inlining.") private Integer freqInlineSize; @Property(doc = "Maximum size of method bytecode for automatic inlining.") private Integer maxInlineSize; @Property(doc = "Maximum number of method calls that can be inlined.") private Integer maxInlineLevel; @Property(doc = "Inline a previously compiled method only if its generated native code size is less than this") private Integer inlineSmallCode; @Property(doc = "Controls the use of tiered compilation") private Boolean tieredCompilation; @Override public void setArgs(List<String> args) { if (preserveFramePointer != null) set(args, "PreserveFramePointer", preserveFramePointer); if (printCompilation != null) set(args, "PrintCompilation", printCompilation); if (printAssembly != null) set(args, "PrintAssembly", printAssembly); if (printInlining != null) set(args, "PrintInlining", printInlining); if (logCompilation != null) set(args, "LogCompilation", logCompilation); if (logFile != null) replace(args, "-XX:LogFile=", logFile); if (freqInlineSize != null) replace(args, "-XX:FreqInlineSize=", freqInlineSize.toString()); if (maxInlineSize != null) replace(args, "-XX:MaxInlineSize=", maxInlineSize.toString()); if (maxInlineLevel != null) replace(args, "-XX:MaxInlineLevel=", maxInlineLevel.toString()); if (inlineSmallCode != null) replace(args, "-XX:InlineSmallCode=", inlineSmallCode.toString()); if (tieredCompilation != null) set(args, "TieredCompilation", tieredCompilation); } } private class ClassLoading implements VmArg { @RequireDiagnostic @Property(doc = "Trace class loading") private Boolean traceClassLoading; @Override public void setArgs(List<String> args) { if (traceClassLoading != null) set(args, "TraceClassLoading", traceClassLoading); } } }