changeset 1237:15cd0bde0cee

7901387: UX: profiler options should be specifiable on command line
author shade
date Wed, 10 Jun 2015 22:28:27 +0300
parents 0dcde834de07
children 3378c82927e0
files jmh-core-it/src/test/java/org/openjdk/jmh/it/ccontrol/LogConsumeProfiler.java jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/GCProfilerTest.java jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/ItExternalProfiler.java jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/ItInternalProfiler.java jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/ProfilerTest.java jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/order/AbstractExternalProfiler.java jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/order/AbstractInternalProfiler.java jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/order/ExternalProfiler1.java jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/order/ExternalProfiler2.java jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/order/ExternalProfiler3.java jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/order/InternalProfiler1.java jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/order/InternalProfiler2.java jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/order/InternalProfiler3.java jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/order/ProfilerOrderTest.java jmh-core/src/main/java/org/openjdk/jmh/profile/AbstractHotspotProfiler.java jmh-core/src/main/java/org/openjdk/jmh/profile/AbstractPerfAsmProfiler.java jmh-core/src/main/java/org/openjdk/jmh/profile/ClassloaderProfiler.java jmh-core/src/main/java/org/openjdk/jmh/profile/CompilerProfiler.java jmh-core/src/main/java/org/openjdk/jmh/profile/GCProfiler.java jmh-core/src/main/java/org/openjdk/jmh/profile/HotspotClassloadingProfiler.java jmh-core/src/main/java/org/openjdk/jmh/profile/HotspotCompilationProfiler.java jmh-core/src/main/java/org/openjdk/jmh/profile/HotspotMemoryProfiler.java jmh-core/src/main/java/org/openjdk/jmh/profile/HotspotRuntimeProfiler.java jmh-core/src/main/java/org/openjdk/jmh/profile/HotspotThreadProfiler.java jmh-core/src/main/java/org/openjdk/jmh/profile/LinuxPerfAsmProfiler.java jmh-core/src/main/java/org/openjdk/jmh/profile/LinuxPerfNormProfiler.java jmh-core/src/main/java/org/openjdk/jmh/profile/LinuxPerfProfiler.java jmh-core/src/main/java/org/openjdk/jmh/profile/Profiler.java jmh-core/src/main/java/org/openjdk/jmh/profile/ProfilerException.java jmh-core/src/main/java/org/openjdk/jmh/profile/ProfilerFactory.java jmh-core/src/main/java/org/openjdk/jmh/profile/ProfilerOptionFormatter.java jmh-core/src/main/java/org/openjdk/jmh/profile/ProfilerUtils.java jmh-core/src/main/java/org/openjdk/jmh/profile/StackProfiler.java jmh-core/src/main/java/org/openjdk/jmh/profile/WinPerfAsmProfiler.java jmh-core/src/main/java/org/openjdk/jmh/runner/BenchmarkHandler.java jmh-core/src/main/java/org/openjdk/jmh/runner/Runner.java jmh-core/src/main/java/org/openjdk/jmh/runner/options/ChainedOptionsBuilder.java jmh-core/src/main/java/org/openjdk/jmh/runner/options/CommandLineOptions.java jmh-core/src/main/java/org/openjdk/jmh/runner/options/Options.java jmh-core/src/main/java/org/openjdk/jmh/runner/options/OptionsBuilder.java jmh-core/src/main/java/org/openjdk/jmh/runner/options/ProfilerConfig.java jmh-core/src/test/java/org/openjdk/jmh/runner/options/TestOptions.java jmh-core/src/test/java/org/openjdk/jmh/runner/options/TestParentOptions.java
diffstat 43 files changed, 1030 insertions(+), 818 deletions(-) [+]
line wrap: on
line diff
--- a/jmh-core-it/src/test/java/org/openjdk/jmh/it/ccontrol/LogConsumeProfiler.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core-it/src/test/java/org/openjdk/jmh/it/ccontrol/LogConsumeProfiler.java	Wed Jun 10 22:28:27 2015 +0300
@@ -26,11 +26,7 @@
 
 import org.openjdk.jmh.infra.BenchmarkParams;
 import org.openjdk.jmh.profile.ExternalProfiler;
-import org.openjdk.jmh.results.AggregationPolicy;
-import org.openjdk.jmh.results.Aggregator;
-import org.openjdk.jmh.results.BenchmarkResult;
-import org.openjdk.jmh.results.Result;
-import org.openjdk.jmh.results.ResultRole;
+import org.openjdk.jmh.results.*;
 import org.openjdk.jmh.util.FileUtils;
 
 import java.io.File;
@@ -39,7 +35,6 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.List;
 
 public class LogConsumeProfiler implements ExternalProfiler {
 
@@ -82,16 +77,6 @@
     }
 
     @Override
-    public boolean checkSupport(List<String> msgs) {
-        return true;
-    }
-
-    @Override
-    public String label() {
-        return "logaggr";
-    }
-
-    @Override
     public String getDescription() {
         return "Log aggregating profiler";
     }
--- a/jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/GCProfilerTest.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/GCProfilerTest.java	Wed Jun 10 22:28:27 2015 +0300
@@ -28,12 +28,12 @@
 import org.openjdk.jmh.annotations.*;
 import org.openjdk.jmh.it.Fixtures;
 import org.openjdk.jmh.profile.GCProfiler;
+import org.openjdk.jmh.profile.ProfilerException;
 import org.openjdk.jmh.runner.Runner;
 import org.openjdk.jmh.runner.RunnerException;
 import org.openjdk.jmh.runner.options.Options;
 import org.openjdk.jmh.runner.options.OptionsBuilder;
 
-import java.util.ArrayList;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -77,10 +77,10 @@
 
     @Test
     public void testAllocationProfiler() throws RunnerException {
-        GCProfiler profiler = new GCProfiler();
-        ArrayList<String> errors = new ArrayList<String>();
-        if (!profiler.checkSupport(errors)) {
-            System.out.println("Allocation profiler is not available: " + errors.toString());
+        try {
+            new GCProfiler();
+        } catch (ProfilerException e) {
+            // not supported
             return;
         }
         Options opts = new OptionsBuilder()
--- a/jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/ItExternalProfiler.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/ItExternalProfiler.java	Wed Jun 10 22:28:27 2015 +0300
@@ -33,7 +33,6 @@
 import java.io.File;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.List;
 
 public class ItExternalProfiler implements ExternalProfiler {
 
@@ -69,16 +68,6 @@
     }
 
     @Override
-    public boolean checkSupport(List<String> msgs) {
-        return true;
-    }
-
-    @Override
-    public String label() {
-        return "itexternal";
-    }
-
-    @Override
     public String getDescription() {
         return "Integration Test External Profiler";
     }
--- a/jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/ItInternalProfiler.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/ItInternalProfiler.java	Wed Jun 10 22:28:27 2015 +0300
@@ -32,7 +32,6 @@
 
 import java.util.Collection;
 import java.util.Collections;
-import java.util.List;
 
 public class ItInternalProfiler implements InternalProfiler {
 
@@ -47,16 +46,6 @@
     }
 
     @Override
-    public boolean checkSupport(List<String> msgs) {
-        return true;
-    }
-
-    @Override
-    public String label() {
-        return "itinternal";
-    }
-
-    @Override
     public String getDescription() {
         return "Integration Test Internal Profiler";
     }
--- a/jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/ProfilerTest.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/ProfilerTest.java	Wed Jun 10 22:28:27 2015 +0300
@@ -24,14 +24,12 @@
  */
 package org.openjdk.jmh.it.profilers;
 
-import junit.framework.Assert;
 import org.junit.Test;
 import org.openjdk.jmh.annotations.Benchmark;
 import org.openjdk.jmh.annotations.Fork;
 import org.openjdk.jmh.annotations.Measurement;
 import org.openjdk.jmh.annotations.Warmup;
 import org.openjdk.jmh.it.Fixtures;
-import org.openjdk.jmh.profile.Profiler;
 import org.openjdk.jmh.runner.Runner;
 import org.openjdk.jmh.runner.RunnerException;
 import org.openjdk.jmh.runner.options.CommandLineOptionException;
@@ -39,7 +37,6 @@
 import org.openjdk.jmh.runner.options.Options;
 import org.openjdk.jmh.runner.options.OptionsBuilder;
 
-import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 public class ProfilerTest {
@@ -72,16 +69,14 @@
 
     @Test
     public void testInternal_CLI() throws RunnerException, CommandLineOptionException {
-        List<Class<? extends Profiler>> ps = new CommandLineOptions("-prof", "itinternal").getProfilers();
-        Assert.assertEquals(1, ps.size());
-        Assert.assertEquals(ItInternalProfiler.class.getName(), ps.get(0).getName());
+        Options opts = new CommandLineOptions("-prof", ItInternalProfiler.class.getCanonicalName(), Fixtures.getTestMask(this.getClass()));
+        new Runner(opts).run();
     }
 
     @Test
     public void testExternal_CLI() throws RunnerException, CommandLineOptionException {
-        List<Class<? extends Profiler>> ps = new CommandLineOptions("-prof", "itexternal").getProfilers();
-        Assert.assertEquals(1, ps.size());
-        Assert.assertEquals(ItExternalProfiler.class.getName(), ps.get(0).getName());
+        Options opts = new CommandLineOptions("-prof", ItExternalProfiler.class.getCanonicalName(), Fixtures.getTestMask(this.getClass()));
+        new Runner(opts).run();
     }
 
 }
--- a/jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/order/AbstractExternalProfiler.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/order/AbstractExternalProfiler.java	Wed Jun 10 22:28:27 2015 +0300
@@ -33,7 +33,6 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 public abstract class AbstractExternalProfiler implements ExternalProfiler {
@@ -90,11 +89,6 @@
     }
 
     @Override
-    public boolean checkSupport(List<String> msgs) {
-        return true;
-    }
-
-    @Override
     public String getDescription() {
         return "Integration Test External Profiler";
     }
--- a/jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/order/AbstractInternalProfiler.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/order/AbstractInternalProfiler.java	Wed Jun 10 22:28:27 2015 +0300
@@ -32,7 +32,6 @@
 
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 public abstract class AbstractInternalProfiler implements InternalProfiler {
@@ -69,11 +68,6 @@
     }
 
     @Override
-    public boolean checkSupport(List<String> msgs) {
-        return true;
-    }
-
-    @Override
     public String getDescription() {
         return "Integration Test Internal Profiler";
     }
--- a/jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/order/ExternalProfiler1.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/order/ExternalProfiler1.java	Wed Jun 10 22:28:27 2015 +0300
@@ -28,9 +28,4 @@
     public ExternalProfiler1() {
         super("prof1");
     }
-
-    @Override
-    public String label() {
-        return "itexternal1";
-    }
 }
--- a/jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/order/ExternalProfiler2.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/order/ExternalProfiler2.java	Wed Jun 10 22:28:27 2015 +0300
@@ -28,9 +28,4 @@
     public ExternalProfiler2() {
         super("prof2");
     }
-
-    @Override
-    public String label() {
-        return "itexternal2";
-    }
 }
--- a/jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/order/ExternalProfiler3.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/order/ExternalProfiler3.java	Wed Jun 10 22:28:27 2015 +0300
@@ -28,9 +28,4 @@
     public ExternalProfiler3() {
         super("prof3");
     }
-
-    @Override
-    public String label() {
-        return "itexternal3";
-    }
 }
--- a/jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/order/InternalProfiler1.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/order/InternalProfiler1.java	Wed Jun 10 22:28:27 2015 +0300
@@ -28,9 +28,4 @@
     public InternalProfiler1() {
         super("prof1");
     }
-
-    @Override
-    public String label() {
-        return "itinternal1";
-    }
 }
--- a/jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/order/InternalProfiler2.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/order/InternalProfiler2.java	Wed Jun 10 22:28:27 2015 +0300
@@ -28,9 +28,4 @@
     public InternalProfiler2() {
         super("prof2");
     }
-
-    @Override
-    public String label() {
-        return "itinternal2";
-    }
 }
--- a/jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/order/InternalProfiler3.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/order/InternalProfiler3.java	Wed Jun 10 22:28:27 2015 +0300
@@ -28,9 +28,4 @@
     public InternalProfiler3() {
         super("prof3");
     }
-
-    @Override
-    public String label() {
-        return "itinternal3";
-    }
 }
--- a/jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/order/ProfilerOrderTest.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core-it/src/test/java/org/openjdk/jmh/it/profilers/order/ProfilerOrderTest.java	Wed Jun 10 22:28:27 2015 +0300
@@ -70,7 +70,10 @@
     public void testInternal_CLI() throws RunnerException, CommandLineOptionException {
         CommandLineOptions opts = new CommandLineOptions(
                 Fixtures.getTestMask(this.getClass()),
-                "-prof", "itinternal1,itinternal2,itinternal3");
+                "-prof", InternalProfiler1.class.getCanonicalName(),
+                "-prof", InternalProfiler2.class.getCanonicalName(),
+                "-prof", InternalProfiler3.class.getCanonicalName()
+        );
         runWith(opts);
     }
 
@@ -90,7 +93,10 @@
     public void testExternal_CLI() throws RunnerException, CommandLineOptionException {
         CommandLineOptions opts = new CommandLineOptions(
                 Fixtures.getTestMask(this.getClass()),
-                "-prof", "itexternal1,itexternal2,itexternal3");
+                "-prof", ExternalProfiler1.class.getCanonicalName(),
+                "-prof", ExternalProfiler2.class.getCanonicalName(),
+                "-prof", ExternalProfiler3.class.getCanonicalName()
+                );
         runWith(opts);
     }
 
--- a/jmh-core/src/main/java/org/openjdk/jmh/profile/AbstractHotspotProfiler.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/profile/AbstractHotspotProfiler.java	Wed Jun 10 22:28:27 2015 +0300
@@ -44,18 +44,11 @@
      */
     protected abstract Collection<Counter> getCounters();
 
-    /**
-     * Checks if this profiler is accessible
-     * @return true, if accessible; false otherwise
-     */
-    @Override
-    public boolean checkSupport(List<String> msgs) {
+    public AbstractHotspotProfiler() throws ProfilerException {
         try {
             Class.forName("sun.management.ManagementFactoryHelper");
-            return true;
         } catch (ClassNotFoundException e) {
-            msgs.add("Class not found: " + e.getMessage() + ", are you running HotSpot VM?");
-            return false;
+            throw new ProfilerException("Class not found: " + e.getMessage() + ", are you running HotSpot VM?");
         }
     }
 
@@ -64,7 +57,7 @@
         HotspotInternalResult res = counters();
         Collection<ProfilerResult> results = new ArrayList<ProfilerResult>();
         for (Map.Entry<String, Long> e : res.getDiff().entrySet()) {
-            results.add(new ProfilerResult(Defaults.PREFIX + label() + "." + e.getKey(), e.getValue(), "?", AggregationPolicy.AVG));
+            results.add(new ProfilerResult(Defaults.PREFIX + "." + e.getKey(), e.getValue(), "?", AggregationPolicy.AVG));
         }
         return results;
     }
--- a/jmh-core/src/main/java/org/openjdk/jmh/profile/AbstractPerfAsmProfiler.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/profile/AbstractPerfAsmProfiler.java	Wed Jun 10 22:28:27 2015 +0300
@@ -24,6 +24,9 @@
  */
 package org.openjdk.jmh.profile;
 
+import joptsimple.OptionParser;
+import joptsimple.OptionSet;
+import joptsimple.OptionSpec;
 import org.openjdk.jmh.infra.BenchmarkParams;
 import org.openjdk.jmh.infra.IterationParams;
 import org.openjdk.jmh.results.*;
@@ -35,134 +38,179 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-
-/**
- *
- */
 public abstract class AbstractPerfAsmProfiler implements ExternalProfiler {
 
-    /**
-     * Cutoff threshold for hot region: the regions with event count over threshold would be shown
-     */
-    private static final double THRESHOLD_RATE = Double.valueOf(System.getProperty("jmh.perfasm.hotThreshold", "0.10"));
+    protected final List<String> events;
 
-    /**
-     * Show this number of top hottest code regions
-     */
-    private static final int SHOW_TOP = Integer.getInteger("jmh.perfasm.top", 20);
+    private final double regionRateThreshold;
+    private final int regionShowTop;
+    private final int regionTooBigThreshold;
+    private final int printMargin;
+    private final int mergeMargin;
+    private final int delayMsec;
 
-    /**
-     * Cutoff threshold for large region: the region larger than this would be truncated
-     */
-    private static final int THRESHOLD_TOO_BIG = Integer.getInteger("jmh.perfasm.tooBigThreshold", 1000);
+    private final boolean skipAssembly;
+    private final boolean skipInterpreter;
+    private final boolean skipVMStubs;
 
-    /**
-     * Print margin: how many "context" lines without counters to show in each region
-     */
-    private static final int PRINT_MARGIN = Integer.getInteger("jmh.perfasm.printMargin", 10);
+    private final boolean savePerfOutput;
+    private final String savePerfOutputTo;
+    private final String savePerfOutputToFile;
 
-    /**
-     * Merge margin: the regions separated by less than the margin are considered the same
-     */
-    private static final int MERGE_MARGIN = Integer.getInteger("jmh.perfasm.mergeMargin", 32);
+    private final boolean savePerfBin;
+    private final String savePerfBinTo;
+    private final String savePerfBinFile;
 
-    /**
-     * Delay collection for given time; -1 to detect automatically
-     */
-    private static final int DELAY_MSEC = Integer.getInteger("jmh.perfasm.delayMs", -1);
+    private final boolean saveLog;
+    private final String saveLogTo;
+    private final String saveLogToFile;
 
-    /**
-     * Do -XX:+PrintAssembly instrumentation?
-     */
-    private static final Boolean SKIP_ASSEMBLY = Boolean.getBoolean("jmh.perfasm.skipAsm");
+    private final boolean printCompilationInfo;
+    private final boolean intelSyntax;
 
-    /**
-     * Skip printing out interpreter stubs. This may improve the parser performance at the expense
-     * of missing the resolution and disassembly of interpreter regions.
-     */
-    private static final Boolean SKIP_INTERPRETER = Boolean.getBoolean("jmh.perfasm.skipInterpreter");
+    protected final String hsLog;
+    protected final String perfBinData;
+    protected final String perfParsedData;
+    protected final OptionSet set;
 
-    /**
-     * Skip printing out VM stubs. This may improve the parser performance at the expense
-     * of missing the resolution and disassembly of VM stub regions.
-     */
-    private static final Boolean SKIP_VM_STUBS = Boolean.getBoolean("jmh.perfasm.skipVMStubs");
+    protected AbstractPerfAsmProfiler(String initLine, String... events) throws ProfilerException {
+        try {
+            hsLog = FileUtils.tempFile("hslog").getAbsolutePath();
+            perfBinData = FileUtils.tempFile("perfbin").getAbsolutePath();
+            perfParsedData = FileUtils.tempFile("perfparsed").getAbsolutePath();
+        } catch (IOException e) {
+            throw new ProfilerException(e);
+        }
 
-    /**
-     * Save perf output to file?
-     */
-    private static final Boolean SAVE_PERF_OUTPUT = Boolean.getBoolean("jmh.perfasm.savePerf");
+        OptionParser parser = new OptionParser();
+        parser.formatHelpWith(new ProfilerOptionFormatter("perfasm"));
 
-    /**
-     * Override the perf output location
-     */
-    private static final String SAVE_PERF_OUTPUT_TO = System.getProperty("jmh.perfasm.savePerfTo", ".");
+        OptionSpec<String> optEvents = parser.accepts("events",
+                        "Events to gather.")
+                .withRequiredArg().ofType(String.class).withValuesSeparatedBy(",").describedAs("event").defaultsTo(events);
 
-    /**
-     * Override the perf output filename
-     */
-    private static final String SAVE_PERF_OUTPUT_TO_FILE = System.getProperty("jmh.perfasm.savePerfToFile");
+        OptionSpec<Double> optThresholdRate = parser.accepts("hotThreshold",
+                        "Cutoff threshold for hot regions. The regions with event count over threshold would be expanded " +
+                        "with detailed disassembly.")
+                .withRequiredArg().ofType(Double.class).describedAs("rate").defaultsTo(0.10);
 
-    /**
-     * Save perf binary output to file?
-     */
-    private static final Boolean SAVE_PERF_BIN_OUTPUT = Boolean.getBoolean("jmh.perfasm.savePerfBin");
+        OptionSpec<Integer> optShowTop = parser.accepts("top",
+                        "Show this number of top hottest code regions.")
+                .withRequiredArg().ofType(Integer.class).describedAs("#").defaultsTo(20);
 
-    /**
-     * Override the perf binary output location
-     */
-    private static final String SAVE_PERF_BIN_OUTPUT_TO = System.getProperty("jmh.perfasm.savePerfBinTo", ".");
+        OptionSpec<Integer> optThreshold = parser.accepts("tooBigThreshold",
+                        "Cutoff threshold for large region. The region containing more than this number of lines " +
+                        "would be truncated.")
+                .withRequiredArg().ofType(Integer.class).describedAs("lines").defaultsTo(1000);
 
-    /**
-     * Override the perf binary output filename
-     */
-    private static final String SAVE_PERF_BIN_OUTPUT_TO_FILE = System.getProperty("jmh.perfasm.savePerfBinToFile");
+        OptionSpec<Integer> optPrintMargin = parser.accepts("printMargin",
+                        "Print margin. How many \"context\" lines without counters to show in each region.")
+                .withRequiredArg().ofType(Integer.class).describedAs("lines").defaultsTo(10);
 
-    /**
-     * Save annotated Hotspot log to file
-     */
-    private static final Boolean SAVE_LOG_OUTPUT = Boolean.getBoolean("jmh.perfasm.saveLog");
+        OptionSpec<Integer> optMergeMargin = parser.accepts("mergeMargin",
+                        "Merge margin. The regions separated by less than the margin are merged.")
+                .withRequiredArg().ofType(Integer.class).describedAs("lines").defaultsTo(32);
 
-    /**
-     * Override the annotated Hotspot log location
-     */
-    private static final String SAVE_LOG_OUTPUT_TO = System.getProperty("jmh.perfasm.saveLogTo", ".");
+        OptionSpec<Integer> optDelay = parser.accepts("delay",
+                        "Delay collection for a given time, in milliseconds; -1 to detect automatically.")
+                .withRequiredArg().ofType(Integer.class).describedAs("ms").defaultsTo(-1);
 
-    /**
-     * Override the annotated Hotspot log filename
-     */
-    private static final String SAVE_LOG_OUTPUT_TO_FILE = System.getProperty("jmh.perfasm.saveLogToFile");
+        OptionSpec<Boolean> optSkipAsm = parser.accepts("skipAsm",
+                        "Skip -XX:+PrintAssembly instrumentation.")
+                .withRequiredArg().ofType(Boolean.class).describedAs("bool").defaultsTo(false);
 
-    /**
-     * Print the collateral compilation information.
-     * Enabling this might corrupt the assembly output, see https://bugs.openjdk.java.net/browse/CODETOOLS-7901102
-     */
-    private static final Boolean PRINT_COMPILATION_INFO = Boolean.getBoolean("jmh.perfasm.printCompilationInfo");
+        OptionSpec<Boolean> optSkipInterpreter = parser.accepts("skipInterpreter",
+                        "Skip printing out interpreter stubs. This may improve the parser performance at the expense " +
+                        "of missing the resolution and disassembly of interpreter regions.")
+                .withRequiredArg().ofType(Boolean.class).describedAs("bool").defaultsTo(false);
 
-    /**
-     * Override the default assembly syntax
-     */
-    private static final String ASSEMBLY_SYNTAX = System.getProperty("jmh.perfasm.assemblySyntax");
+        OptionSpec<Boolean> optSkipVMStubs = parser.accepts("skipVMStubs",
+                        "Skip printing out VM stubs. This may improve the parser performance at the expense " +
+                        "of missing the resolution and disassembly of VM stub regions.")
+                .withRequiredArg().ofType(Boolean.class).describedAs("bool").defaultsTo(false);
 
-    protected final String[] tracedEvents;
+        OptionSpec<Boolean> optPerfOut = parser.accepts("savePerf",
+                        "Save parsed perf output to file. Use this for debugging.")
+                .withRequiredArg().ofType(Boolean.class).describedAs("bool").defaultsTo(false);
 
-    protected String hsLog;
-    protected String perfBinData;
-    protected String perfParsedData;
+        OptionSpec<String> optPerfOutTo = parser.accepts("savePerfTo",
+                        "Override the parsed perf output log location. This will use the unique file name per test. Use this for debugging.")
+                .withRequiredArg().ofType(String.class).describedAs("dir").defaultsTo(".");
 
-    protected AbstractPerfAsmProfiler(String[] events) throws IOException {
-        tracedEvents = events;
-        hsLog = FileUtils.tempFile("hslog").getAbsolutePath();
-        perfBinData = FileUtils.tempFile("perfbin").getAbsolutePath();
-        perfParsedData = FileUtils.tempFile("perfparsed").getAbsolutePath();
+        OptionSpec<String> optPerfOutToFile = parser.accepts("savePerfToFile",
+                        "Override the perf output log filename. Use this for debugging.")
+                .withRequiredArg().ofType(String.class).describedAs("file");
+
+        OptionSpec<Boolean> optPerfBin = parser.accepts("savePerfBin",
+                        "Save binary perf data to file. Use this for debugging.")
+                .withRequiredArg().ofType(Boolean.class).describedAs("bool").defaultsTo(false);
+
+        OptionSpec<String> optPerfBinTo = parser.accepts("savePerfBinTo",
+                        "Override the binary perf data location. This will use the unique file name per test. Use this for debugging.")
+                .withRequiredArg().ofType(String.class).describedAs("dir").defaultsTo(".");
+
+        OptionSpec<String> optPerfBinToFile = parser.accepts("savePerfBinToFile",
+                        "Override the perf binary data filename. Use this for debugging.")
+                .withRequiredArg().ofType(String.class).describedAs("file");
+
+        OptionSpec<Boolean> optSaveLog = parser.accepts("saveLog",
+                        "Save annotated Hotspot log to file.")
+                .withRequiredArg().ofType(Boolean.class).describedAs("bool").defaultsTo(false);
+
+        OptionSpec<String> optSaveLogTo = parser.accepts("saveLogTo",
+                        "Override the annotated Hotspot log location. This will use the unique file name per test.")
+                .withRequiredArg().ofType(String.class).describedAs("dir").defaultsTo(".");
+
+        OptionSpec<String> optSaveLogToFile = parser.accepts("saveLogToFile",
+                        "Override the annotated Hotspot log filename.")
+                .withRequiredArg().ofType(String.class).describedAs("file");
+
+        OptionSpec<Boolean> optPrintCompilationInfo = parser.accepts("printCompilationInfo",
+                        "Print the collateral compilation information. Enabling this might corrupt the " +
+                        "assembly output, see https://bugs.openjdk.java.net/browse/CODETOOLS-7901102.")
+                .withRequiredArg().ofType(Boolean.class).describedAs("bool").defaultsTo(false);
+
+        OptionSpec<Boolean> optIntelSyntax = parser.accepts("intelSyntax",
+                        "Should perfasm use intel syntax?")
+                .withRequiredArg().ofType(Boolean.class).describedAs("boolean").defaultsTo(false);
+
+        addMyOptions(parser);
+
+        set = ProfilerUtils.parseInitLine(initLine, parser);
+
+        this.events = set.valuesOf(optEvents);
+        regionRateThreshold = set.valueOf(optThresholdRate);
+        regionShowTop = set.valueOf(optShowTop);
+        regionTooBigThreshold = set.valueOf(optThreshold);
+        printMargin = set.valueOf(optPrintMargin);
+        mergeMargin = set.valueOf(optMergeMargin);
+        delayMsec = set.valueOf(optDelay);
+
+        skipAssembly = set.valueOf(optSkipAsm);
+        skipInterpreter = set.valueOf(optSkipInterpreter);
+        skipVMStubs = set.valueOf(optSkipVMStubs);
+
+        savePerfOutput = set.valueOf(optPerfOut);
+        savePerfOutputTo = set.valueOf(optPerfOutTo);
+        savePerfOutputToFile = set.valueOf(optPerfOutToFile);
+
+        savePerfBin = set.valueOf(optPerfBin);
+        savePerfBinTo = set.valueOf(optPerfBinTo);
+        savePerfBinFile = set.valueOf(optPerfBinToFile);
+
+        saveLog = set.valueOf(optSaveLog);
+        saveLogTo = set.valueOf(optSaveLogTo);
+        saveLogToFile = set.valueOf(optSaveLogToFile);
+
+        intelSyntax = set.valueOf(optIntelSyntax);
+        printCompilationInfo = set.valueOf(optPrintCompilationInfo);
     }
 
-    @Override
-    public abstract boolean checkSupport(List<String> msgs);
+    protected abstract void addMyOptions(OptionParser parser);
 
     @Override
     public Collection<String> addJVMOptions(BenchmarkParams params) {
-        if (!SKIP_ASSEMBLY) {
+        if (!skipAssembly) {
             Collection<String> opts = new ArrayList<String>();
             opts.addAll(Arrays.asList(
                 "-XX:+UnlockDiagnosticVMOptions",
@@ -170,23 +218,23 @@
                 "-XX:LogFile=" + hsLog,
                 "-XX:+PrintAssembly"));
 
-            if (!SKIP_INTERPRETER) {
+            if (!skipInterpreter) {
                 opts.add("-XX:+PrintInterpreter");
             }
-            if (!SKIP_VM_STUBS) {
+            if (!skipVMStubs) {
                 opts.add("-XX:+PrintNMethods");
                 opts.add("-XX:+PrintNativeNMethods");
                 opts.add("-XX:+PrintSignatureHandlers");
                 opts.add("-XX:+PrintAdapterHandlers");
                 opts.add("-XX:+PrintStubCode");
             }
-            if (PRINT_COMPILATION_INFO) {
+            if (printCompilationInfo) {
                 opts.add("-XX:+PrintCompilation");
                 opts.add("-XX:+PrintInlining");
                 opts.add("-XX:+TraceClassLoading");
             }
-            if (ASSEMBLY_SYNTAX != null) {
-                opts.add("-XX:PrintAssemblyOptions=" + ASSEMBLY_SYNTAX);
+            if (intelSyntax) {
+                opts.add("-XX:PrintAssemblyOptions=intel");
             }
             return opts;
         } else {
@@ -253,7 +301,7 @@
         Assembly assembly = readAssembly(new File(hsLog));
         if (assembly.size() > 0) {
             pw.printf("PrintAssembly processed: %d total address lines.%n", assembly.size());
-        } else if (SKIP_ASSEMBLY) {
+        } else if (skipAssembly) {
             pw.println();
             pw.println("PrintAssembly skipped, Java methods are not resolved.");
             pw.println();
@@ -268,7 +316,7 @@
          */
 
         long delayNs;
-        if (DELAY_MSEC == -1) { // not set
+        if (delayMsec == -1) { // not set
             BenchmarkResultMetaData md = br.getMetadata();
             if (md != null) {
                 // try to ask harness itself:
@@ -280,7 +328,7 @@
                         + TimeUnit.SECONDS.toNanos(1); // loosely account for the JVM lag
             }
         } else {
-            delayNs = TimeUnit.MILLISECONDS.toNanos(DELAY_MSEC);
+            delayNs = TimeUnit.MILLISECONDS.toNanos(delayMsec);
         }
 
         double skipSec = 1.0 * delayNs / TimeUnit.SECONDS.toNanos(1);
@@ -290,7 +338,7 @@
         if (!events.isEmpty()) {
             pw.printf("Perf output processed (skipped %.3f seconds):%n", skipSec);
             int cnt = 1;
-            for (String event : tracedEvents) {
+            for (String event : this.events) {
                 pw.printf(" Column %d: %s (%d events)%n", cnt, event, events.get(event).size());
                 cnt++;
             }
@@ -313,7 +361,7 @@
          * We would sort the regions by the hotness of the first (main) event type.
          */
 
-        final String mainEvent = tracedEvents[0];
+        final String mainEvent = this.events.get(0);
 
         Collections.sort(regions, new Comparator<Region>() {
             @Override
@@ -323,7 +371,7 @@
             }
         });
 
-        long threshold = (long) (THRESHOLD_RATE * events.getTotalEvents(mainEvent));
+        long threshold = (long) (regionRateThreshold * events.getTotalEvents(mainEvent));
 
         boolean headerPrinted = false;
 
@@ -331,7 +379,7 @@
         for (Region r : regions) {
             if (r.getEventCount(events, mainEvent) > threshold) {
                 if (!headerPrinted) {
-                    pw.printf("Hottest code regions (>%.2f%% \"%s\" events):%n", THRESHOLD_RATE * 100, mainEvent);
+                    pw.printf("Hottest code regions (>%.2f%% \"%s\" events):%n", regionRateThreshold * 100, mainEvent);
                     headerPrinted = true;
                 }
 
@@ -340,7 +388,7 @@
                 r.printCode(pw, events);
 
                 printDottedLine(pw);
-                for (String event : tracedEvents) {
+                for (String event : this.events) {
                     printLine(pw, events, event, r.getEventCount(events, event));
                 }
                 pw.println("<total for region " + cnt + ">");
@@ -359,30 +407,30 @@
             printDottedLine(pw, "Hottest Regions");
             int shown = 0;
             for (Region r : regions) {
-                if (shown++ < SHOW_TOP) {
-                    for (String event : tracedEvents) {
+                if (shown++ < regionShowTop) {
+                    for (String event : this.events) {
                         printLine(pw, events, event, r.getEventCount(events, event));
                     }
                     pw.printf("[0x%x:0x%x] in %s%n", r.begin, r.end, r.getName());
                 } else {
-                    for (String event : tracedEvents) {
+                    for (String event : this.events) {
                         other.add(event, r.getEventCount(events, event));
                     }
                 }
-                for (String event : tracedEvents) {
+                for (String event : this.events) {
                     total.add(event, r.getEventCount(events, event));
                 }
             }
 
-            if (regions.size() - SHOW_TOP > 0) {
-                for (String event : tracedEvents) {
+            if (regions.size() - regionShowTop > 0) {
+                for (String event : this.events) {
                     printLine(pw, events, event, other.count(event));
                 }
-                pw.println("<...other " + (regions.size() - SHOW_TOP) + " warm regions...>");
+                pw.println("<...other " + (regions.size() - regionShowTop) + " warm regions...>");
             }
             printDottedLine(pw);
 
-            for (String event : tracedEvents) {
+            for (String event : this.events) {
                 printLine(pw, events, event, total.count(event));
             }
             pw.println("<totals>");
@@ -390,7 +438,7 @@
         }
 
         final Map<String, Multiset<String>> methodsByType = new HashMap<String, Multiset<String>>();
-        for (String event : tracedEvents) {
+        for (String event : this.events) {
             methodsByType.put(event, new HashMultiset<String>());
         }
 
@@ -401,12 +449,12 @@
             printDottedLine(pw, "Hottest Methods (after inlining)");
 
             Map<String, Multiset<String>> methods = new HashMap<String, Multiset<String>>();
-            for (String event : tracedEvents) {
+            for (String event : this.events) {
                 methods.put(event, new HashMultiset<String>());
             }
 
             for (Region r : regions) {
-                for (String event : tracedEvents) {
+                for (String event : this.events) {
                     long count = r.getEventCount(events, event);
                     methods.get(event).add(r.getName(), count);
                     methodsByType.get(event).add(r.getType(), count);
@@ -419,30 +467,30 @@
             int shownMethods = 0;
             List<String> top = Multisets.sortedDesc(methods.get(mainEvent));
             for (String m : top) {
-                if (shownMethods++ < SHOW_TOP) {
-                    for (String event : tracedEvents) {
+                if (shownMethods++ < regionShowTop) {
+                    for (String event : this.events) {
                         printLine(pw, events, event, methods.get(event).count(m));
                     }
                     pw.printf("%s%n", m);
                 } else {
-                    for (String event : tracedEvents) {
+                    for (String event : this.events) {
                         other.add(event, methods.get(event).count(m));
                     }
                 }
-                for (String event : tracedEvents) {
+                for (String event : this.events) {
                     total.add(event, methods.get(event).count(m));
                 }
             }
 
-            if (top.size() - SHOW_TOP > 0) {
-                for (String event : tracedEvents) {
+            if (top.size() - regionShowTop > 0) {
+                for (String event : this.events) {
                     printLine(pw, events, event, other.count(event));
                 }
-                pw.println("<...other " + (top.size() - SHOW_TOP) + " warm methods...>");
+                pw.println("<...other " + (top.size() - regionShowTop) + " warm methods...>");
             }
             printDottedLine(pw);
 
-            for (String event : tracedEvents) {
+            for (String event : this.events) {
                 printLine(pw, events, event, total.count(event));
             }
             pw.println("<totals>");
@@ -456,7 +504,7 @@
             printDottedLine(pw, "Distribution by Area");
 
             for (String m : Multisets.sortedDesc(methodsByType.get(mainEvent))) {
-                for (String event : tracedEvents) {
+                for (String event : this.events) {
                     printLine(pw, events, event, methodsByType.get(event).count(m));
                 }
                 pw.printf("%s%n", m);
@@ -464,7 +512,7 @@
 
             printDottedLine(pw);
 
-            for (String event : tracedEvents) {
+            for (String event : this.events) {
                 printLine(pw, events, event, methodsByType.get(event).size());
             }
 
@@ -504,10 +552,10 @@
         /**
          * Print perf output, if needed:
          */
-        if (SAVE_PERF_OUTPUT) {
-            String target = (SAVE_PERF_OUTPUT_TO_FILE == null) ?
-                SAVE_PERF_OUTPUT_TO + "/" + br.getParams().id() + ".perf" :
-                SAVE_PERF_OUTPUT_TO_FILE;
+        if (savePerfOutput) {
+            String target = (savePerfOutputToFile == null) ?
+                savePerfOutputTo + "/" + br.getParams().id() + ".perf" :
+                savePerfOutputToFile;
             try {
                 FileUtils.copy(perfParsedData, target);
                 pw.println("Perf output saved to " + target);
@@ -519,10 +567,10 @@
         /**
          * Print binary perf output, if needed:
          */
-        if (SAVE_PERF_BIN_OUTPUT) {
-            String target = (SAVE_PERF_BIN_OUTPUT_TO_FILE == null) ?
-                SAVE_PERF_BIN_OUTPUT_TO + "/" + br.getParams().id() + perfBinaryExtension() :
-                SAVE_PERF_BIN_OUTPUT_TO_FILE;
+        if (savePerfBin) {
+            String target = (savePerfBinFile == null) ?
+                savePerfBinTo + "/" + br.getParams().id() + perfBinaryExtension() :
+                savePerfBinFile;
             try {
                 FileUtils.copy(perfBinData, target);
                 pw.println("Perf binary output saved to " + target);
@@ -534,16 +582,16 @@
         /**
          * Print annotated assembly, if needed:
          */
-        if (SAVE_LOG_OUTPUT) {
-            String target = (SAVE_LOG_OUTPUT_TO_FILE == null) ?
-                SAVE_LOG_OUTPUT_TO + "/" + br.getParams().id() + ".log" :
-                SAVE_LOG_OUTPUT_TO_FILE;
+        if (saveLog) {
+            String target = (saveLogToFile == null) ?
+                saveLogTo + "/" + br.getParams().id() + ".log" :
+                saveLogToFile;
             FileOutputStream asm;
             try {
                 asm = new FileOutputStream(target);
                 PrintWriter pwAsm = new PrintWriter(asm);
                 for (ASMLine line : assembly.lines) {
-                    for (String event : tracedEvents) {
+                    for (String event : this.events) {
                         long count = (line.addr != null) ? events.get(event).count(line.addr) : 0;
                         printLine(pwAsm, events, event, count);
                     }
@@ -611,10 +659,10 @@
                 lastAddr = addr;
                 lastBegin = addr;
             } else {
-                if (addr - lastAddr > MERGE_MARGIN) {
-                    List<ASMLine> regionLines = asms.getLines(lastBegin, lastAddr, PRINT_MARGIN);
+                if (addr - lastAddr > mergeMargin) {
+                    List<ASMLine> regionLines = asms.getLines(lastBegin, lastAddr, printMargin);
                     if (!regionLines.isEmpty()) {
-                        regions.add(new GeneratedRegion(tracedEvents, asms, lastBegin, lastAddr, regionLines, eventfulAddrs));
+                        regions.add(new GeneratedRegion(this.events, asms, lastBegin, lastAddr, regionLines, eventfulAddrs, regionTooBigThreshold));
                     } else {
                         regions.add(new NativeRegion(events, lastBegin, lastAddr, eventfulAddrs));
                     }
@@ -785,7 +833,7 @@
         final Map<Long, String> libs;
         final Map<String, Long> totalCounts;
 
-        PerfEvents(String[] tracedEvents, Map<String, Multiset<Long>> events, Map<Long, String> methods, Map<Long, String> libs) {
+        PerfEvents(Collection<String> tracedEvents, Map<String, Multiset<Long>> events, Map<Long, String> methods, Map<Long, String> libs) {
             this.events = events;
             this.methods = methods;
             this.libs = libs;
@@ -795,7 +843,7 @@
             }
         }
 
-        public PerfEvents(String[] tracedEvents) {
+        public PerfEvents(Collection<String> tracedEvents) {
             this(tracedEvents, Collections.<String, Multiset<Long>>emptyMap(), Collections.<Long, String>emptyMap(), Collections.<Long, String>emptyMap());
         }
 
@@ -942,13 +990,15 @@
     }
 
     static class GeneratedRegion extends Region {
-        final String[] tracedEvents;
+        final Collection<String> tracedEvents;
         final Collection<ASMLine> code;
+        final int threshold;
 
-        GeneratedRegion(String[] tracedEvents, Assembly asms, long begin, long end, Collection<ASMLine> code, Set<Long> eventfulAddrs) {
+        GeneratedRegion(Collection<String> tracedEvents, Assembly asms, long begin, long end, Collection<ASMLine> code, Set<Long> eventfulAddrs, int threshold) {
             super(generateName(asms, eventfulAddrs), begin, end, eventfulAddrs);
             this.tracedEvents = tracedEvents;
             this.code = code;
+            this.threshold = threshold;
         }
 
         static String generateName(Assembly asm, Set<Long> eventfulAddrs) {
@@ -964,8 +1014,8 @@
 
         @Override
         public void printCode(PrintWriter pw, PerfEvents events) {
-            if (code.size() > THRESHOLD_TOO_BIG) {
-                pw.printf(" <region is too big to display, has %d lines, but threshold is %d>%n", code.size(), THRESHOLD_TOO_BIG);
+            if (code.size() > threshold) {
+                pw.printf(" <region is too big to display, has %d lines, but threshold is %d>%n", code.size(), threshold);
             } else {
                 for (ASMLine line : code) {
                     for (String event : tracedEvents) {
--- a/jmh-core/src/main/java/org/openjdk/jmh/profile/ClassloaderProfiler.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/profile/ClassloaderProfiler.java	Wed Jun 10 22:28:27 2015 +0300
@@ -50,16 +50,6 @@
     }
 
     @Override
-    public boolean checkSupport(List<String> msgs) {
-        return true;
-    }
-
-    @Override
-    public String label() {
-        return "cl";
-    }
-
-    @Override
     public void beforeIteration(BenchmarkParams benchmarkParams, IterationParams iterationParams) {
         ClassLoadingMXBean cl = ManagementFactory.getClassLoadingMXBean();
 
--- a/jmh-core/src/main/java/org/openjdk/jmh/profile/CompilerProfiler.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/profile/CompilerProfiler.java	Wed Jun 10 22:28:27 2015 +0300
@@ -35,7 +35,6 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.List;
 
 public class CompilerProfiler implements InternalProfiler {
 
@@ -46,19 +45,10 @@
         return "JIT compiler profiling via standard MBeans";
     }
 
-    @Override
-    public String label() {
-        return "comp";
-    }
-
-    @Override
-    public boolean checkSupport(List<String> msgs) {
+    public CompilerProfiler() throws ProfilerException {
         CompilationMXBean comp = ManagementFactory.getCompilationMXBean();
-        if (comp.isCompilationTimeMonitoringSupported()) {
-            return true;
-        } else {
-            msgs.add("The MXBean is available, but compilation time monitoring is disabled.");
-            return false;
+        if (!comp.isCompilationTimeMonitoringSupported()) {
+            throw new ProfilerException("The MXBean is available, but compilation time monitoring is disabled.");
         }
     }
 
--- a/jmh-core/src/main/java/org/openjdk/jmh/profile/GCProfiler.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/profile/GCProfiler.java	Wed Jun 10 22:28:27 2015 +0300
@@ -55,10 +55,10 @@
     private final NotificationListener listener;
     private volatile Multiset<String> churn;
 
-    public GCProfiler() {
+    public GCProfiler() throws ProfilerException {
         churn = new HashMultiset<String>();
 
-        NotificationListener listener = null;
+        NotificationListener listener;
         try {
             final Class<?> infoKlass = Class.forName("com.sun.management.GarbageCollectionNotificationInfo");
             final Field notifNameField = infoKlass.getField("GARBAGE_COLLECTION_NOTIFICATION");
@@ -94,11 +94,11 @@
                 }
             };
         } catch (ClassNotFoundException e) {
-            // Nothing to do here, listener is not functional
+            throw new ProfilerException(e);
         } catch (NoSuchFieldException e) {
-            // Nothing to do here, listener is not functional
+            throw new ProfilerException(e);
         } catch (NoSuchMethodException e) {
-            // Nothing to do here, listener is not functional
+            throw new ProfilerException(e);
         }
 
         this.listener = listener;
@@ -110,16 +110,6 @@
     }
 
     @Override
-    public boolean checkSupport(List<String> msgs) {
-        return true;
-    }
-
-    @Override
-    public String label() {
-        return "gc";
-    }
-
-    @Override
     public void beforeIteration(BenchmarkParams benchmarkParams, IterationParams iterationParams) {
         installHooks();
 
@@ -215,7 +205,6 @@
     private boolean hooksInstalled;
 
     public synchronized void installHooks() {
-        if (listener == null) return;
         if (hooksInstalled) return;
         hooksInstalled = true;
         churn = new HashMultiset<String>();
@@ -225,7 +214,6 @@
     }
 
     public synchronized void uninstallHooks() {
-        if (listener == null) return;
         if (!hooksInstalled) return;
         hooksInstalled = false;
         for (GarbageCollectorMXBean bean : ManagementFactory.getGarbageCollectorMXBeans()) {
--- a/jmh-core/src/main/java/org/openjdk/jmh/profile/HotspotClassloadingProfiler.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/profile/HotspotClassloadingProfiler.java	Wed Jun 10 22:28:27 2015 +0300
@@ -31,17 +31,15 @@
 
 public class HotspotClassloadingProfiler extends AbstractHotspotProfiler {
 
+    public HotspotClassloadingProfiler() throws ProfilerException {
+    }
+
     @Override
     public List<Counter> getCounters() {
         return AbstractHotspotProfiler.<HotspotClassLoadingMBean>getInstance("HotspotClassLoadingMBean").getInternalClassLoadingCounters();
     }
 
     @Override
-    public String label() {
-        return "hs_cl";
-    }
-
-    @Override
     public String getDescription() {
         return "HotSpot (tm) classloader profiling via implementation-specific MBeans";
     }
--- a/jmh-core/src/main/java/org/openjdk/jmh/profile/HotspotCompilationProfiler.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/profile/HotspotCompilationProfiler.java	Wed Jun 10 22:28:27 2015 +0300
@@ -40,17 +40,15 @@
 
 public class HotspotCompilationProfiler extends AbstractHotspotProfiler {
 
+    public HotspotCompilationProfiler() throws ProfilerException {
+    }
+
     @Override
     public List<Counter> getCounters() {
         return AbstractHotspotProfiler.<HotspotCompilationMBean>getInstance("HotspotCompilationMBean").getInternalCompilerCounters();
     }
 
     @Override
-    public String label() {
-        return "hs_comp";
-    }
-
-    @Override
     public String getDescription() {
         return "HotSpot (tm) JIT compiler profiling via implementation-specific MBeans";
     }
--- a/jmh-core/src/main/java/org/openjdk/jmh/profile/HotspotMemoryProfiler.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/profile/HotspotMemoryProfiler.java	Wed Jun 10 22:28:27 2015 +0300
@@ -31,17 +31,15 @@
 
 public class HotspotMemoryProfiler extends AbstractHotspotProfiler {
 
+    public HotspotMemoryProfiler() throws ProfilerException {
+    }
+
     @Override
     public List<Counter> getCounters() {
         return AbstractHotspotProfiler.<HotspotMemoryMBean>getInstance("HotspotMemoryMBean").getInternalMemoryCounters();
     }
 
     @Override
-    public String label() {
-        return "hs_gc";
-    }
-
-    @Override
     public String getDescription() {
         return "HotSpot (tm) memory manager (GC) profiling via implementation-specific MBeans";
     }
--- a/jmh-core/src/main/java/org/openjdk/jmh/profile/HotspotRuntimeProfiler.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/profile/HotspotRuntimeProfiler.java	Wed Jun 10 22:28:27 2015 +0300
@@ -40,17 +40,15 @@
 
 public class HotspotRuntimeProfiler extends AbstractHotspotProfiler {
 
+    public HotspotRuntimeProfiler() throws ProfilerException {
+    }
+
     @Override
     public List<Counter> getCounters() {
         return AbstractHotspotProfiler.<HotspotRuntimeMBean>getInstance("HotspotRuntimeMBean").getInternalRuntimeCounters();
     }
 
     @Override
-    public String label() {
-        return "hs_rt";
-    }
-
-    @Override
     public String getDescription() {
         return "HotSpot (tm) runtime profiling via implementation-specific MBeans";
     }
--- a/jmh-core/src/main/java/org/openjdk/jmh/profile/HotspotThreadProfiler.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/profile/HotspotThreadProfiler.java	Wed Jun 10 22:28:27 2015 +0300
@@ -39,17 +39,15 @@
 
 public class HotspotThreadProfiler extends AbstractHotspotProfiler {
 
+    public HotspotThreadProfiler() throws ProfilerException {
+    }
+
     @Override
     public List<Counter> getCounters() {
         return AbstractHotspotProfiler.<HotspotThreadMBean>getInstance("HotspotThreadMBean").getInternalThreadingCounters();
     }
 
     @Override
-    public String label() {
-        return "hs_thr";
-    }
-
-    @Override
     public String getDescription() {
         return "HotSpot (tm) threading subsystem via implementation-specific MBeans";
     }
--- a/jmh-core/src/main/java/org/openjdk/jmh/profile/LinuxPerfAsmProfiler.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/profile/LinuxPerfAsmProfiler.java	Wed Jun 10 22:28:27 2015 +0300
@@ -24,6 +24,8 @@
  */
 package org.openjdk.jmh.profile;
 
+import joptsimple.OptionParser;
+import joptsimple.OptionSpec;
 import org.openjdk.jmh.infra.BenchmarkParams;
 import org.openjdk.jmh.util.*;
 
@@ -36,51 +38,31 @@
 
 public class LinuxPerfAsmProfiler extends AbstractPerfAsmProfiler {
 
-    /**
-     * Sampling frequency
-     */
-    private static final long SAMPLE_FREQUENCY = Long.getLong("jmh.perfasm.frequency", 1000);
+    private final long sampleFrequency;
 
-    /**
-     * Events to gather
-     */
-    private static final String[] EVENTS = System.getProperty("jmh.perfasm.events", "cycles,instructions").split(",");
+    private OptionSpec<Long> optFrequency;
 
-    private static final boolean IS_SUPPORTED;
-    private static final boolean IS_DELAYED;
-    private static final Collection<String> FAIL_MSGS;
+    public LinuxPerfAsmProfiler(String initLine) throws ProfilerException {
+        super(initLine, "cycles", "instructions");
 
-    static {
-        FAIL_MSGS = Utils.tryWith("perf", "stat", "--log-fd", "2", "echo", "1");
-        IS_SUPPORTED = FAIL_MSGS.isEmpty();
+        Collection<String> failMsg = Utils.tryWith("perf", "stat", "--log-fd", "2", "echo", "1");
+        if (!failMsg.isEmpty()) {
+            throw new ProfilerException(failMsg.toString());
+        }
 
-        Collection<String> delay = Utils.tryWith("perf", "stat", "--log-fd", "2", "-D", "1", "echo", "1");
-        IS_DELAYED = delay.isEmpty();
+        sampleFrequency = set.valueOf(optFrequency);
     }
 
     @Override
-    public boolean checkSupport(List<String> msgs) {
-        if (IS_SUPPORTED) {
-            return true;
-        } else {
-            msgs.addAll(FAIL_MSGS);
-            return false;
-        }
-    }
-
-
-    public LinuxPerfAsmProfiler() throws IOException {
-        super(EVENTS);
+    protected void addMyOptions(OptionParser parser) {
+        optFrequency = parser.accepts("frequency",
+                "Sampling frequency. This is synonymous to perf -F #")
+                .withRequiredArg().ofType(Long.class).describedAs("freq").defaultsTo(1000L);
     }
 
     @Override
     public Collection<String> addJVMInvokeOptions(BenchmarkParams params) {
-        return Arrays.asList("perf", "record", "-F" + SAMPLE_FREQUENCY, "-e" + Utils.join(EVENTS, ","), "-o" + perfBinData);
-    }
-
-    @Override
-    public String label() {
-        return "perfasm";
+        return Arrays.asList("perf", "record", "-F" + sampleFrequency, "-e" + Utils.join(events, ","), "-o" + perfBinData);
     }
 
     @Override
@@ -128,7 +110,7 @@
             Map<Long, String> methods = new HashMap<Long, String>();
             Map<Long, String> libs = new HashMap<Long, String>();
             Map<String, Multiset<Long>> events = new LinkedHashMap<String, Multiset<Long>>();
-            for (String evName : EVENTS) {
+            for (String evName : this.events) {
                 events.put(evName, new TreeMultiset<Long>());
             }
 
@@ -198,9 +180,9 @@
 
             methods.put(0L, "<unknown>");
 
-            return new PerfEvents(tracedEvents, events, methods, libs);
+            return new PerfEvents(this.events, events, methods, libs);
         } catch (IOException e) {
-            return new PerfEvents(tracedEvents);
+            return new PerfEvents(events);
         } finally {
             FileUtils.safelyClose(fr);
         }
--- a/jmh-core/src/main/java/org/openjdk/jmh/profile/LinuxPerfNormProfiler.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/profile/LinuxPerfNormProfiler.java	Wed Jun 10 22:28:27 2015 +0300
@@ -24,6 +24,9 @@
  */
 package org.openjdk.jmh.profile;
 
+import joptsimple.OptionParser;
+import joptsimple.OptionSet;
+import joptsimple.OptionSpec;
 import org.openjdk.jmh.infra.BenchmarkParams;
 import org.openjdk.jmh.infra.IterationParams;
 import org.openjdk.jmh.results.*;
@@ -40,27 +43,8 @@
 
 public class LinuxPerfNormProfiler implements ExternalProfiler {
 
-    /** Delay collection for given time; -1 to detect automatically */
-    private static final int DELAY_MSEC = Integer.getInteger("jmh.perfnorm.delayMs", -1);
-
-    /** Events to gather */
-    private static final String[] USER_EVENTS = System.getProperty("jmh.perfnorm.events", "").split(",");
-
-    /** Use "perf stat -d -d -d" instead of explicit counter list */
-    private static final Boolean USE_DEFAULT_STAT = Boolean.getBoolean("jmh.perfnorm.useDefaultStat");
-
-    /** Ignore event increments larger that this */
-    private static final long HIGH_PASS_FILTER = Long.getLong("jmh.perfnorm.filterHigh", 100000000000L);
-
-    /** The interval between incremental updates from concurrently running perf */
-    private static final int INCREMENT_INTERVAL = Integer.getInteger("jmh.perfnorm.intervalMs", 100);
-
-    private static final boolean IS_SUPPORTED;
-    private static final boolean IS_INCREMENTABLE;
-    private static final Collection<String> FAIL_MSGS;
-
     /** This is a non-exhaustive list of events we care about. */
-    private static final String[] INTERESTING_EVENTS = new String[]{
+    private static final String[] interestingEvents = new String[]{
             "cycles", "instructions",
             "branches", "branch-misses",
             "bus-cycles", "ref-cycles",
@@ -76,25 +60,68 @@
             "stalled-cycles-frontend", "stalled-cycles-backend",
     };
 
-    private static final Collection<String> SUPPORTED_EVENTS = new ArrayList<String>();
+    private final int delayMs;
+    private final boolean useDefaultStats;
+    private final long highPassFilter;
+    private final int incrementInterval;
+    private final boolean isIncrementable;
 
-    static {
-        FAIL_MSGS = Utils.tryWith("perf", "stat", "--log-fd", "2", "-x,", "echo", "1");
-        IS_SUPPORTED = FAIL_MSGS.isEmpty();
+    private final Collection<String> supportedEvents = new ArrayList<String>();
 
-        Collection<String> incremental = Utils.tryWith("perf", "stat", "--log-fd", "2", "-x,", "-I", String.valueOf(INCREMENT_INTERVAL), "echo", "1");
-        IS_INCREMENTABLE = incremental.isEmpty();
+    public LinuxPerfNormProfiler(String initLine) throws ProfilerException {
+        OptionParser parser = new OptionParser();
+        parser.formatHelpWith(new ProfilerOptionFormatter("perfnorm"));
 
-        for (String ev : USER_EVENTS) {
-            if (ev.trim().isEmpty()) continue;
-            SUPPORTED_EVENTS.add(ev);
+        OptionSpec<String> optEvents = parser.accepts("events",
+                        "Events to gather.")
+                .withRequiredArg().ofType(String.class).withValuesSeparatedBy(",").describedAs("event+").defaultsTo(interestingEvents);
+
+        OptionSpec<Integer> optDelay = parser.accepts("delay",
+                        "Delay collection for a given time, in milliseconds; -1 to detect automatically.")
+                .withRequiredArg().ofType(Integer.class).describedAs("ms").defaultsTo(-1);
+
+        OptionSpec<Integer> optIncrementInterval = parser.accepts("interval",
+                        "The interval between incremental updates from a concurrently running perf. " +
+                        "Lower values may improve accuracy, while increasing the profiling overhead.")
+                .withRequiredArg().ofType(Integer.class).describedAs("ms").defaultsTo(100);
+
+        OptionSpec<Long> optHighPassFilter = parser.accepts("highPassFilter",
+                        "Ignore event increments larger that this.")
+                .withRequiredArg().ofType(Long.class).describedAs("#").defaultsTo(100000000000L);
+
+        OptionSpec<Boolean> optDefaultStat = parser.accepts("useDefaultStat",
+                        "Use \"perf stat -d -d -d\" instead of explicit counter list.")
+                .withRequiredArg().ofType(Boolean.class).describedAs("bool").defaultsTo(false);
+
+        OptionSet set = ProfilerUtils.parseInitLine(initLine, parser);
+
+        delayMs = set.valueOf(optDelay);
+        incrementInterval = set.valueOf(optIncrementInterval);
+        highPassFilter = set.valueOf(optHighPassFilter);
+        useDefaultStats = set.valueOf(optDefaultStat);
+
+        Collection<String> userEvents = set.valuesOf(optEvents);
+
+        Collection<String> msgs = Utils.tryWith("perf", "stat", "--log-fd", "2", "-x,", "echo", "1");
+        if (!msgs.isEmpty()) {
+            throw new ProfilerException(msgs.toString());
         }
 
-        if (SUPPORTED_EVENTS.isEmpty()) {
-            for (String ev : INTERESTING_EVENTS) {
+        Collection<String> incremental = Utils.tryWith("perf", "stat", "--log-fd", "2", "-x,", "-I", String.valueOf(incrementInterval), "echo", "1");
+        isIncrementable = incremental.isEmpty();
+
+        if (userEvents != null) {
+            for (String ev : userEvents) {
+                if (ev.trim().isEmpty()) continue;
+                supportedEvents.add(ev);
+            }
+        }
+
+        if (supportedEvents.isEmpty()) {
+            for (String ev : interestingEvents) {
                 Collection<String> res = Utils.tryWith("perf", "stat", "--log-fd", "2", "-x,", "-e", "cycles,instructions," + ev, "echo", "1");
                 if (res.isEmpty()) {
-                    SUPPORTED_EVENTS.add(ev);
+                    supportedEvents.add(ev);
                 }
             }
         }
@@ -103,13 +130,13 @@
     @Override
     public Collection<String> addJVMInvokeOptions(BenchmarkParams params) {
         List<String> cmd = new ArrayList<String>();
-        if (USE_DEFAULT_STAT) {
+        if (useDefaultStats) {
             cmd.addAll(Arrays.asList("perf", "stat", "--log-fd", "2", "-x,", "-d", "-d", "-d"));
         } else {
-            cmd.addAll(Arrays.asList("perf", "stat", "--log-fd", "2", "-x,", "-e", Utils.join(SUPPORTED_EVENTS, ",")));
+            cmd.addAll(Arrays.asList("perf", "stat", "--log-fd", "2", "-x,", "-e", Utils.join(supportedEvents, ",")));
         }
-        if (IS_INCREMENTABLE) {
-            cmd.addAll(Arrays.asList("-I", String.valueOf(INCREMENT_INTERVAL)));
+        if (isIncrementable) {
+            cmd.addAll(Arrays.asList("-I", String.valueOf(incrementInterval)));
         }
         return cmd;
     }
@@ -140,27 +167,12 @@
     }
 
     @Override
-    public boolean checkSupport(List<String> msgs) {
-        if (IS_SUPPORTED) {
-            return true;
-        } else {
-            msgs.addAll(FAIL_MSGS);
-            return false;
-        }
-    }
-
-    @Override
-    public String label() {
-        return "perfnorm";
-    }
-
-    @Override
     public String getDescription() {
         return "Linux perf statistics, normalized by operation count";
     }
 
     public long getDelay(BenchmarkResult br) {
-        if (DELAY_MSEC == -1) { // not set
+        if (delayMs == -1) { // not set
             BenchmarkResultMetaData md = br.getMetadata();
             if (md != null) {
                 // try to ask harness itself:
@@ -172,7 +184,7 @@
                         + TimeUnit.SECONDS.toNanos(1); // loosely account for the JVM lag
             }
         } else {
-            return TimeUnit.MILLISECONDS.toNanos(DELAY_MSEC);
+            return TimeUnit.MILLISECONDS.toNanos(delayMs);
         }
     }
 
@@ -192,7 +204,7 @@
             while ((line = reader.readLine()) != null) {
                 if (line.startsWith("#")) continue;
 
-                if (IS_INCREMENTABLE) {
+                if (isIncrementable) {
                     int idx1 = line.indexOf(",");
                     int idx2 = line.lastIndexOf(",");
 
@@ -217,7 +229,7 @@
 
                     try {
                         long lValue = NumberFormat.getInstance().parse(count).longValue();
-                        if (lValue > HIGH_PASS_FILTER) {
+                        if (lValue > highPassFilter) {
                             // anomalous value, pretend we did not see it
                             continue nextline;
                         }
@@ -247,7 +259,7 @@
 
             }
 
-            if (!IS_INCREMENTABLE) {
+            if (!isIncrementable) {
                 System.out.println();
                 System.out.println();
                 System.out.println("WARNING: Your system uses old \"perf\", which cannot print data incrementally (-I).\n" +
@@ -258,7 +270,7 @@
 
             BenchmarkResultMetaData md = br.getMetadata();
             if (md != null) {
-                if (IS_INCREMENTABLE) {
+                if (isIncrementable) {
                     totalOpts = md.getMeasurementOps();
                 } else {
                     totalOpts = md.getWarmupOps() + md.getMeasurementOps();
--- a/jmh-core/src/main/java/org/openjdk/jmh/profile/LinuxPerfProfiler.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/profile/LinuxPerfProfiler.java	Wed Jun 10 22:28:27 2015 +0300
@@ -24,61 +24,63 @@
  */
 package org.openjdk.jmh.profile;
 
+import joptsimple.OptionParser;
+import joptsimple.OptionSet;
+import joptsimple.OptionSpec;
 import org.openjdk.jmh.infra.BenchmarkParams;
-import org.openjdk.jmh.results.AggregationPolicy;
-import org.openjdk.jmh.results.Aggregator;
-import org.openjdk.jmh.results.BenchmarkResult;
-import org.openjdk.jmh.results.Result;
-import org.openjdk.jmh.results.ResultRole;
+import org.openjdk.jmh.results.*;
 import org.openjdk.jmh.util.FileUtils;
 import org.openjdk.jmh.util.ScoreFormatter;
 import org.openjdk.jmh.util.Utils;
 
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.StringWriter;
+import java.io.*;
 import java.text.NumberFormat;
 import java.text.ParseException;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.List;
 import java.util.concurrent.TimeUnit;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 public class LinuxPerfProfiler implements ExternalProfiler {
 
-    private static final boolean IS_SUPPORTED;
-    private static final boolean IS_DELAYED;
-    private static final Collection<String> FAIL_MSGS;
+    private final boolean isDelayed;
+    private final int delayMs;
 
-    static {
-        FAIL_MSGS = Utils.tryWith("perf", "stat", "--log-fd", "2", "echo", "1");
-        IS_SUPPORTED = FAIL_MSGS.isEmpty();
+    public LinuxPerfProfiler(String initLine) throws ProfilerException {
+        OptionParser parser = new OptionParser();
+        parser.formatHelpWith(new ProfilerOptionFormatter("perf"));
+
+        OptionSpec<Integer> optDelay = parser.accepts("delay",
+                "Delay collection for a given time, in milliseconds; -1 to detect automatically.")
+                .withRequiredArg().ofType(Integer.class).describedAs("ms").defaultsTo(-1);
+
+        OptionSet set = ProfilerUtils.parseInitLine(initLine, parser);
+
+        delayMs = set.valueOf(optDelay);
+
+        Collection<String> msgs = Utils.tryWith("perf", "stat", "--log-fd", "2", "echo", "1");
+        if (!msgs.isEmpty()) {
+            throw new ProfilerException(msgs.toString());
+        }
 
         Collection<String> delay = Utils.tryWith("perf", "stat", "--log-fd", "2", "-D", "1", "echo", "1");
-        IS_DELAYED = delay.isEmpty();
+        isDelayed = delay.isEmpty();
     }
 
-    /** Delay collection for given time; -1 to detect automatically */
-    private static final int DELAY_MSEC = Integer.getInteger("jmh.perf.delayMs", -1);
-
     @Override
     public Collection<String> addJVMInvokeOptions(BenchmarkParams params) {
         long delay;
-        if (DELAY_MSEC == -1) { // not set
+        if (delayMs == -1) { // not set
             delay = TimeUnit.NANOSECONDS.toMillis(params.getWarmup().getCount() *
                             params.getWarmup().getTime().convertTo(TimeUnit.NANOSECONDS))
                     + TimeUnit.SECONDS.toMillis(1); // loosely account for the JVM lag
         } else {
-            delay = DELAY_MSEC;
+            delay = delayMs;
         }
 
-        if (IS_DELAYED) {
+        if (isDelayed) {
             return Arrays.asList("perf", "stat", "--log-fd", "2", "-d", "-d", "-d", "-D", String.valueOf(delay));
         } else {
             return Arrays.asList("perf", "stat", "--log-fd", "2", "-d", "-d", "-d");
@@ -112,21 +114,6 @@
     }
 
     @Override
-    public boolean checkSupport(List<String> msgs) {
-        if (IS_SUPPORTED) {
-            return true;
-        } else {
-            msgs.addAll(FAIL_MSGS);
-            return false;
-        }
-    }
-
-    @Override
-    public String label() {
-        return "perf";
-    }
-
-    @Override
     public String getDescription() {
         return "Linux perf Statistics";
     }
@@ -173,7 +160,7 @@
                 }
             }
 
-            if (!IS_DELAYED) {
+            if (!isDelayed) {
                 pw.println();
                 pw.println("WARNING: Your system uses old \"perf\", which can not delay data collection.\n" +
                         "Therefore, perf performance data includes benchmark warmup.");
--- a/jmh-core/src/main/java/org/openjdk/jmh/profile/Profiler.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/profile/Profiler.java	Wed Jun 10 22:28:27 2015 +0300
@@ -24,8 +24,6 @@
  */
 package org.openjdk.jmh.profile;
 
-import java.util.List;
-
 /**
  * Root profiler interface.
  *
@@ -36,19 +34,6 @@
 public interface Profiler {
 
     /**
-     * Check the support for a given profiler.
-     * @param msgs where to put the diagnostic messages
-     * @return true, if supported
-     */
-    boolean checkSupport(List<String> msgs);
-
-    /**
-     * Unique label for the profiler.
-     * @return label
-     */
-    String label();
-
-    /**
      * Human-readable one-line description of the profiler.
      * @return description
      */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jmh-core/src/main/java/org/openjdk/jmh/profile/ProfilerException.java	Wed Jun 10 22:28:27 2015 +0300
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package org.openjdk.jmh.profile;
+
+public class ProfilerException extends Exception {
+    public ProfilerException(Exception e) {
+        super(e);
+    }
+
+    public ProfilerException(String s) {
+        super(s);
+    }
+}
--- a/jmh-core/src/main/java/org/openjdk/jmh/profile/ProfilerFactory.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/profile/ProfilerFactory.java	Wed Jun 10 22:28:27 2015 +0300
@@ -24,39 +24,163 @@
  */
 package org.openjdk.jmh.profile;
 
-import org.openjdk.jmh.runner.options.VerboseMode;
+import org.openjdk.jmh.runner.options.ProfilerConfig;
 
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.ServiceLoader;
+import java.io.PrintStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.*;
 
 public class ProfilerFactory {
 
-    public static List<Class<? extends Profiler>> getAvailableProfilers() {
-        List<Class<? extends Profiler>> profs = new ArrayList<Class<? extends Profiler>>();
-
-        // All built-in profilers:
-        profs.add(ClassloaderProfiler.class);
-        profs.add(CompilerProfiler.class);
-        profs.add(GCProfiler.class);
-        profs.add(HotspotClassloadingProfiler.class);
-        profs.add(HotspotCompilationProfiler.class);
-        profs.add(HotspotMemoryProfiler.class);
-        profs.add(HotspotRuntimeProfiler.class);
-        profs.add(HotspotThreadProfiler.class);
-        profs.add(StackProfiler.class);
-        profs.add(LinuxPerfProfiler.class);
-        profs.add(LinuxPerfNormProfiler.class);
-        profs.add(LinuxPerfAsmProfiler.class);
-        profs.add(WinPerfAsmProfiler.class);
-
-        // Try to discover more profilers through the SPI
-        profs.addAll(getDiscoveredProfilers());
-        return profs;
+    public static Profiler getProfilerOrException(ProfilerConfig cfg) throws ProfilerException {
+        try {
+            return getProfiler(cfg);
+        } catch (InvocationTargetException e) {
+            if (e.getCause() instanceof ProfilerException) {
+                throw (ProfilerException) e.getCause();
+            }
+            throw new ProfilerException(e);
+        } catch (ProfilerException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new ProfilerException(e);
+        }
     }
 
-    public static List<Class<? extends Profiler>> getDiscoveredProfilers() {
+    private static Profiler getProfilerOrNull(ProfilerConfig cfg) {
+        try {
+            return getProfiler(cfg);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    private static Profiler getProfiler(ProfilerConfig cfg) throws Exception {
+        String desc = cfg.getKlass();
+
+        // Try built-in profilers first
+        Class<? extends Profiler> builtIn = BUILT_IN.get(desc);
+        if (builtIn != null) {
+            return instantiate(cfg, builtIn);
+        }
+
+        // Try discovered profilers then
+        Collection<Class<? extends Profiler>> profilers = getDiscoveredProfilers();
+        for (Class<? extends Profiler> p : profilers) {
+            if (p.getCanonicalName().equals(desc)) {
+                return instantiate(cfg, p);
+            }
+        }
+
+        // Try the direct hit
+        Class<? extends Profiler> klass = (Class<? extends Profiler>) Class.forName(desc);
+        return instantiate(cfg, klass);
+    }
+
+    private static Profiler instantiate(ProfilerConfig cfg, Class<? extends Profiler> p) throws InstantiationException, IllegalAccessException, InvocationTargetException {
+        Profiler prof;
+        try {
+            Constructor<? extends Profiler> constructor = p.getConstructor(String.class);
+            prof = constructor.newInstance(cfg.getOpts());
+        } catch (NoSuchMethodException e) {
+            prof = p.newInstance();
+        }
+        return prof;
+    }
+
+    public static List<ExternalProfiler> getSupportedExternal(Collection<ProfilerConfig> cfg) {
+        List<ExternalProfiler> profilers = new ArrayList<ExternalProfiler>();
+        for (ProfilerConfig p : cfg) {
+            Profiler prof = ProfilerFactory.getProfilerOrNull(p);
+            if (prof instanceof ExternalProfiler) {
+                profilers.add((ExternalProfiler) prof);
+            }
+        }
+        return profilers;
+    }
+
+    public static List<InternalProfiler> getSupportedInternal(Collection<ProfilerConfig> cfg) {
+        List<InternalProfiler> profilers = new ArrayList<InternalProfiler>();
+        for (ProfilerConfig p : cfg) {
+            Profiler prof = ProfilerFactory.getProfilerOrNull(p);
+            if (prof instanceof InternalProfiler) {
+                profilers.add((InternalProfiler) prof);
+            }
+        }
+        return profilers;
+    }
+
+    public static void listProfilers(PrintStream out) {
+        StringBuilder supported = new StringBuilder();
+        StringBuilder unsupported = new StringBuilder();
+
+        for (String s : BUILT_IN.keySet()) {
+            try {
+                Profiler prof = getProfilerOrException(new ProfilerConfig(s, ""));
+                String descr = (prof != null) ? prof.getDescription() : "(unable to instantiate the profiler)";
+
+                if (prof != null) {
+                    supported.append(String.format("%20s: %s %s\n", s, descr, ""));
+                } else {
+                    unsupported.append(String.format("%20s: %s %s\n", s, descr, ""));
+                    unsupported.append("\n");
+                }
+            } catch (ProfilerException e) {
+                unsupported.append(String.format("%20s: %s %s\n", s, "<none>", ""));
+                unsupported.append(e.getMessage());
+                unsupported.append("\n");
+            }
+        }
+
+        for (Class<? extends Profiler> s : ProfilerFactory.getDiscoveredProfilers()) {
+            try {
+                Profiler prof = getProfilerOrException(new ProfilerConfig(s.getCanonicalName(), ""));
+                String descr = (prof != null) ? prof.getDescription() : "(unable to instantiate the profiler)";
+
+                if (prof != null) {
+                    supported.append(String.format("%20s: %s %s\n", "<none>", descr, "(discovered)"));
+                } else {
+                    unsupported.append(String.format("%20s: %s %s\n", "<none>", descr, "(discovered)"));
+                    unsupported.append("\n");
+                }
+            } catch (ProfilerException e) {
+                unsupported.append(String.format("%20s: %s %s\n", s, "<none>", ""));
+                unsupported.append(e.getMessage());
+                unsupported.append("\n");
+            }
+        }
+
+        if (!supported.toString().isEmpty()) {
+            out.println("Supported profilers:\n" + supported.toString());
+        }
+
+        if (!unsupported.toString().isEmpty()) {
+            out.println("Unsupported profilers:\n" + unsupported.toString());
+        }
+    }
+
+
+    private static final Map<String, Class<? extends Profiler>> BUILT_IN;
+
+    static {
+        BUILT_IN = new TreeMap<String, Class<? extends Profiler>>();
+        BUILT_IN.put("cl",       ClassloaderProfiler.class);
+        BUILT_IN.put("comp",     CompilerProfiler.class);
+        BUILT_IN.put("gc",       GCProfiler.class);
+        BUILT_IN.put("hs_cl",    HotspotClassloadingProfiler.class);
+        BUILT_IN.put("hs_comp",  HotspotCompilationProfiler.class);
+        BUILT_IN.put("hs_gc",    HotspotMemoryProfiler.class);
+        BUILT_IN.put("hs_rt",    HotspotRuntimeProfiler.class);
+        BUILT_IN.put("hs_thr",   HotspotThreadProfiler.class);
+        BUILT_IN.put("stack",    StackProfiler.class);
+        BUILT_IN.put("perf",     LinuxPerfProfiler.class);
+        BUILT_IN.put("perfnorm", LinuxPerfNormProfiler.class);
+        BUILT_IN.put("perfasm",  LinuxPerfAsmProfiler.class);
+        BUILT_IN.put("xperfasm", WinPerfAsmProfiler.class);
+    }
+
+    private static List<Class<? extends Profiler>> getDiscoveredProfilers() {
         List<Class<? extends Profiler>> profs = new ArrayList<Class<? extends Profiler>>();
         for (Profiler s : ServiceLoader.load(Profiler.class)) {
             profs.add(s.getClass());
@@ -64,83 +188,4 @@
         return profs;
     }
 
-    public static boolean checkSupport(Class<? extends Profiler> klass, List<String> msgs) {
-        try {
-            Profiler prof = klass.newInstance();
-            return prof.checkSupport(msgs);
-        } catch (InstantiationException e) {
-            msgs.add("Unable to instantiate " + klass);
-            return false;
-        } catch (IllegalAccessException e) {
-            msgs.add("Unable to instantiate " + klass);
-            return false;
-        }
-    }
-
-    public static String getDescription(Class<? extends Profiler> klass) {
-        try {
-            Profiler prof = klass.newInstance();
-            return prof.getDescription();
-        } catch (InstantiationException e) {
-            return "(unable to instantiate the profiler)";
-        } catch (IllegalAccessException e) {
-            return "(unable to instantiate the profiler)";
-        }
-    }
-
-    public static Class<? extends Profiler> getProfilerByName(String name) {
-        try {
-            Class<?> klass = Class.forName(name);
-            if (Profiler.class.isAssignableFrom(klass)) {
-                return (Class<? extends Profiler>) klass;
-            }
-        } catch (ClassNotFoundException e) {
-            // omit
-        }
-
-        Collection<Class<? extends Profiler>> profilers = getAvailableProfilers();
-        for (Class<? extends Profiler> p : profilers) {
-            try {
-                Profiler prof = p.newInstance();
-                if (prof.label().equalsIgnoreCase(name)) {
-                    return p;
-                }
-            } catch (InstantiationException e) {
-                // omit
-            } catch (IllegalAccessException e) {
-                // omit
-            }
-        }
-
-        return null;
-    }
-
-    public static Profiler prepareProfiler(Class<? extends Profiler> klass, VerboseMode verboseMode) {
-        try {
-            return klass.newInstance();
-        } catch (InstantiationException e) {
-            throw new IllegalStateException(e);
-        } catch (IllegalAccessException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
-    public static String getLabel(Class<? extends Profiler> klass) {
-        try {
-            Profiler prof = klass.newInstance();
-            return prof.label();
-        } catch (InstantiationException e) {
-            return "(unable to instantiate the profiler)";
-        } catch (IllegalAccessException e) {
-            return "(unable to instantiate the profiler)";
-        }
-    }
-
-    public static boolean isInternal(Class<? extends Profiler> klass) {
-        return InternalProfiler.class.isAssignableFrom(klass);
-    }
-
-    public static boolean isExternal(Class<? extends Profiler> klass) {
-        return ExternalProfiler.class.isAssignableFrom(klass);
-    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jmh-core/src/main/java/org/openjdk/jmh/profile/ProfilerOptionFormatter.java	Wed Jun 10 22:28:27 2015 +0300
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2005, 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package org.openjdk.jmh.profile;
+
+import joptsimple.HelpFormatter;
+import joptsimple.OptionDescriptor;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+class ProfilerOptionFormatter implements HelpFormatter {
+
+    private static final String LINE_SEPARATOR = System.getProperty("line.separator");
+
+    private final String name;
+
+    public ProfilerOptionFormatter(String name) {
+        this.name = name;
+    }
+
+    public String format(Map<String, ? extends OptionDescriptor> options) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("Usage: -prof <profiler-name>:opt1=value1,value2;opt2=value3");
+        sb.append(LINE_SEPARATOR);
+        sb.append(LINE_SEPARATOR);
+        sb.append("Options accepted by " + name + ":");
+        for (OptionDescriptor each : options.values()) {
+            sb.append(lineFor(each));
+        }
+
+        return sb.toString();
+    }
+
+    private Collection<String> rewrap(String lines) {
+        Collection<String> result = new ArrayList<String>();
+        String[] words = lines.split("[ \n]");
+        String line = "";
+        int cols = 0;
+        for (String w : words) {
+            cols += w.length();
+            line += w + " ";
+            if (cols > 40) {
+                result.add(line);
+                line = "";
+                cols = 0;
+            }
+        }
+        if (!line.trim().isEmpty()) {
+            result.add(line);
+        }
+        return result;
+    }
+
+    private String lineFor(OptionDescriptor d) {
+        StringBuilder line = new StringBuilder();
+
+        StringBuilder o = new StringBuilder();
+        o.append("  ");
+        for (String str : d.options()) {
+            if (d.representsNonOptions()) continue;
+            o.append(str);
+            if (d.acceptsArguments()) {
+                o.append("=");
+                if (d.requiresArgument()) {
+                    o.append("<");
+                } else {
+                    o.append("[");
+                }
+                o.append(d.argumentDescription());
+                if (d.requiresArgument()) {
+                    o.append(">");
+                } else {
+                    o.append("]");
+                }
+            }
+        }
+
+        final int optWidth = 35;
+
+        line.append(String.format("%-" + optWidth + "s", o.toString()));
+        boolean first = true;
+        String desc = d.description();
+        List<?> defaults = d.defaultValues();
+        if (defaults != null && !defaults.isEmpty()) {
+            desc += " (default: " + defaults.toString() + ")";
+        }
+        for (String l : rewrap(desc)) {
+            if (first) {
+                first = false;
+            } else {
+                line.append(LINE_SEPARATOR);
+                line.append(String.format("%-" + optWidth + "s", ""));
+            }
+            line.append(l);
+        }
+
+        line.append(LINE_SEPARATOR);
+        line.append(LINE_SEPARATOR);
+        return line.toString();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jmh-core/src/main/java/org/openjdk/jmh/profile/ProfilerUtils.java	Wed Jun 10 22:28:27 2015 +0300
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package org.openjdk.jmh.profile;
+
+import joptsimple.OptionException;
+import joptsimple.OptionParser;
+import joptsimple.OptionSet;
+import joptsimple.OptionSpec;
+
+import java.io.IOException;
+import java.io.StringWriter;
+
+class ProfilerUtils {
+
+    public static OptionSet parseInitLine(String initLine, OptionParser parser) throws ProfilerException {
+        parser.accepts("help", "Display help.");
+
+        OptionSpec<String> nonOptions = parser.nonOptions();
+
+        String[] split = initLine.split(";");
+        for (int c = 0; c < split.length; c++) {
+            if (!split[c].isEmpty()) {
+                split[c] = "-" + split[c];
+            }
+        }
+
+        OptionSet set;
+        try {
+            set = parser.parse(split);
+        } catch (OptionException e) {
+            try {
+                StringWriter sw = new StringWriter();
+                sw.append(e.getMessage());
+                sw.append("\n");
+                parser.printHelpOn(sw);
+                throw new ProfilerException(sw.toString());
+            } catch (IOException e1) {
+                throw new ProfilerException(e1);
+            }
+        }
+
+        if (set.has("help")) {
+            try {
+                StringWriter sw = new StringWriter();
+                parser.printHelpOn(sw);
+                throw new ProfilerException(sw.toString());
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+
+        String s = set.valueOf(nonOptions);
+        if (s != null && !s.isEmpty()) {
+            throw new ProfilerException("Unhandled options: " + s + " in " + initLine);
+        }
+        return set;
+    }
+
+
+}
--- a/jmh-core/src/main/java/org/openjdk/jmh/profile/StackProfiler.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/profile/StackProfiler.java	Wed Jun 10 22:28:27 2015 +0300
@@ -24,13 +24,13 @@
  */
 package org.openjdk.jmh.profile;
 
+import joptsimple.OptionParser;
+import joptsimple.OptionSet;
+import joptsimple.OptionSpec;
 import org.openjdk.jmh.infra.BenchmarkParams;
 import org.openjdk.jmh.infra.IterationParams;
-import org.openjdk.jmh.results.AggregationPolicy;
-import org.openjdk.jmh.results.Aggregator;
-import org.openjdk.jmh.results.IterationResult;
-import org.openjdk.jmh.results.Result;
-import org.openjdk.jmh.results.ResultRole;
+import org.openjdk.jmh.results.*;
+import org.openjdk.jmh.runner.options.IntegerValueConverter;
 import org.openjdk.jmh.util.HashMultiset;
 import org.openjdk.jmh.util.Multiset;
 import org.openjdk.jmh.util.Multisets;
@@ -38,16 +38,7 @@
 import java.io.Serializable;
 import java.lang.management.ManagementFactory;
 import java.lang.management.ThreadInfo;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.EnumMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -55,19 +46,9 @@
  */
 public class StackProfiler implements InternalProfiler {
 
-    /** Number of stack lines to save */
-    private static final int SAMPLE_STACK_LINES = Integer.getInteger("jmh.stack.lines", 1);
-
-    /** Number of top stacks to show */
-    private static final int SAMPLE_TOP_STACKS =  Integer.getInteger("jmh.stack.top", 10);
-
-    /** Sampling period */
-    private static final int SAMPLE_PERIOD_MSEC = Integer.getInteger("jmh.stack.period", 10);
-
-    /** Record detailed line info */
-    private static final boolean SAMPLE_LINE =    Boolean.getBoolean("jmh.stack.detailLine");
-
-    /** Threads to ignore (known system and harness threads) */
+    /**
+     * Threads to ignore (known system and harness threads)
+     */
     private static final String[] IGNORED_THREADS = {
             "Finalizer",
             "Signal Dispatcher",
@@ -77,26 +58,55 @@
             "Attach Listener"
     };
 
-    /** Whether or not filter the packages. */
-    private static final Boolean EXCLUDE_PACKAGES = Boolean.getBoolean("jmh.stack.excludePackages");
+    private final int stackLines;
+    private final int topStacks;
+    private final int periodMsec;
+    private final boolean sampleLine;
+    private final Set<String> excludePackageNames;
 
-    /**
-     * Requested excluded packages from system properties. This is expected to be a comma (,) separated list
-     * of the fully qualified package names to be excluded. Every stack line that starts with the provided
-     * patterns will be excluded. If the default package exclusion is enabled, the list would be added.
-     */
-    private static final Set<String> EXCLUDE_PACKAGES_NAMES;
+    public StackProfiler(String initLine) throws ProfilerException {
+        OptionParser parser = new OptionParser();
+        parser.formatHelpWith(new ProfilerOptionFormatter(StackProfiler.class.getCanonicalName()));
 
-    static {
-        if (EXCLUDE_PACKAGES) {
-            String userNames = System.getProperty("jmh.stack.excludePackageNames");
-            EXCLUDE_PACKAGES_NAMES = new HashSet<String>(
-                    (userNames != null) ?
-                        Arrays.asList(userNames.split(",")) :
-                        Arrays.asList("java.", "javax.", "sun.", "sunw.", "com.sun.", "org.openjdk.jmh."));
-        } else {
-            EXCLUDE_PACKAGES_NAMES = Collections.emptySet();
-        }
+        OptionSpec<Integer> optStackLines = parser.accepts("lines", "Number of stack lines to save in each stack trace. " +
+                "Larger values provide more insight into who is calling the top stack method, as the expense " +
+                "of more stack trace shapes to collect.")
+                .withRequiredArg().withValuesConvertedBy(IntegerValueConverter.POSITIVE).describedAs("int").defaultsTo(1);
+
+        OptionSpec<Integer> optTopStacks = parser.accepts("top", "Number of top stacks to show in the profiling results. " +
+                "Larger values may catch some stack traces that linger in the distribution tail.")
+                .withRequiredArg().withValuesConvertedBy(IntegerValueConverter.POSITIVE).describedAs("int").defaultsTo(10);
+
+        OptionSpec<Integer> optSamplePeriod = parser.accepts("period", "Sampling period, in milliseconds. " +
+                "Smaller values improve accuracy, at the expense of more profiling overhead.")
+                .withRequiredArg().withValuesConvertedBy(IntegerValueConverter.POSITIVE).describedAs("int").defaultsTo(10);
+
+        OptionSpec<Boolean> optDetailLine = parser.accepts("detailLine", "Record detailed source line info. " +
+                "This adds the line numbers to the recorded stack traces.")
+                .withRequiredArg().ofType(Boolean.class).describedAs("bool").defaultsTo(false);
+
+        OptionSpec<Boolean> optExclude = parser.accepts("excludePackages", "Enable package filtering. " +
+                "Use excludePackages option to control what packages are filtered")
+                .withRequiredArg().ofType(Boolean.class).describedAs("bool").defaultsTo(false);
+
+        OptionSpec<String> optExcludeClasses = parser.accepts("excludePackageNames", "Filter there packages. " +
+                "This is expected to be a comma-separated list\n" +
+                "of the fully qualified package names to be excluded. Every stack line that starts with the provided\n" +
+                "patterns will be excluded.")
+                .withRequiredArg().withValuesSeparatedBy(",").ofType(String.class).describedAs("package+")
+                .defaultsTo("java.", "javax.", "sun.", "sunw.", "com.sun.", "org.openjdk.jmh.");
+
+        OptionSet set = ProfilerUtils.parseInitLine(initLine, parser);
+
+        sampleLine = set.valueOf(optDetailLine);
+        periodMsec = set.valueOf(optSamplePeriod);
+        topStacks = set.valueOf(optTopStacks);
+        stackLines = set.valueOf(optStackLines);
+
+        boolean excludePackages = set.valueOf(optExclude);
+        excludePackageNames = excludePackages ?
+                new HashSet<String>(set.valuesOf(optExcludeClasses)) :
+                Collections.<String>emptySet();
     }
 
     private volatile SamplingTask samplingTask;
@@ -110,17 +120,7 @@
     @Override
     public Collection<? extends Result> afterIteration(BenchmarkParams benchmarkParams, IterationParams iterationParams, IterationResult result) {
         samplingTask.stop();
-        return Collections.singleton(new StackResult(samplingTask.stacks));
-    }
-
-    @Override
-    public boolean checkSupport(List<String> msg) {
-        return true;
-    }
-
-    @Override
-    public String label() {
-        return "stack";
+        return Collections.singleton(new StackResult(samplingTask.stacks, topStacks));
     }
 
     @Override
@@ -128,7 +128,7 @@
         return "Simple and naive Java stack profiler";
     }
 
-    public static class SamplingTask implements Runnable {
+    public class SamplingTask implements Runnable {
 
         private final Thread thread;
         private final Map<Thread.State, Multiset<StackRecord>> stacks;
@@ -162,30 +162,30 @@
                     //   - Get the remaining number of stack lines and build the stack record
 
                     StackTraceElement[] stack = info.getStackTrace();
-                    List<String> stackLines = new ArrayList<String>();
+                    List<String> lines = new ArrayList<String>();
 
                     for (StackTraceElement l : stack) {
                         String className = l.getClassName();
                         if (!isExcluded(className)) {
-                            stackLines.add(className + '.' + l.getMethodName()
-                                    + (SAMPLE_LINE ? ":" + l.getLineNumber() : ""));
+                            lines.add(className + '.' + l.getMethodName()
+                                    + (sampleLine ? ":" + l.getLineNumber() : ""));
 
-                            if (stackLines.size() >= SAMPLE_STACK_LINES) {
+                            if (lines.size() >= stackLines) {
                                 break;
                             }
                         }
                     }
 
-                    if (stackLines.isEmpty()) {
-                        stackLines.add("<stack is empty, everything is filtered?>");
+                    if (lines.isEmpty()) {
+                        lines.add("<stack is empty, everything is filtered?>");
                     }
 
                     Thread.State state = info.getThreadState();
-                    stacks.get(state).add(new StackRecord(stackLines));
+                    stacks.get(state).add(new StackRecord(lines));
                 }
 
                 try {
-                    TimeUnit.MILLISECONDS.sleep(SAMPLE_PERIOD_MSEC);
+                    TimeUnit.MILLISECONDS.sleep(periodMsec);
                 } catch (InterruptedException e) {
                     return;
                 }
@@ -206,7 +206,7 @@
         }
 
         private boolean isExcluded(String className) {
-            for (String p : EXCLUDE_PACKAGES_NAMES) {
+            for (String p : excludePackageNames) {
                 if (className.startsWith(p)) {
                     return true;
                 }
@@ -244,10 +244,12 @@
         private static final long serialVersionUID = 2609170863630346073L;
 
         private final Map<Thread.State, Multiset<StackRecord>> stacks;
+        private final int topStacks;
 
-        public StackResult(Map<Thread.State, Multiset<StackRecord>> stacks) {
+        public StackResult(Map<Thread.State, Multiset<StackRecord>> stacks, int topStacks) {
             super(ResultRole.SECONDARY, Defaults.PREFIX + "stack", of(Double.NaN), "---", AggregationPolicy.AVG);
             this.stacks = stacks;
+            this.topStacks = topStacks;
         }
 
         @Override
@@ -305,7 +307,7 @@
                     builder.append(dottedLine("Thread state: " + state.toString()));
 
                     int totalDisplayed = 0;
-                    for (StackRecord s : Multisets.countHighest(stateStacks, SAMPLE_TOP_STACKS)) {
+                    for (StackRecord s : Multisets.countHighest(stateStacks, topStacks)) {
                         List<String> lines = s.lines;
                         if (!lines.isEmpty()) {
                             totalDisplayed += stateStacks.count(s);
@@ -371,6 +373,7 @@
     public static class StackResultAggregator implements Aggregator<StackResult> {
         @Override
         public StackResult aggregate(Collection<StackResult> results) {
+            int topStacks = 0;
             Map<Thread.State, Multiset<StackRecord>> sum = new EnumMap<Thread.State, Multiset<StackRecord>>(Thread.State.class);
             for (StackResult r : results) {
                 for (Map.Entry<Thread.State, Multiset<StackRecord>> entry : r.stacks.entrySet()) {
@@ -382,8 +385,9 @@
                         sumSet.add(rec, entry.getValue().count(rec));
                     }
                 }
+                topStacks = r.topStacks;
             }
-            return new StackResult(sum);
+            return new StackResult(sum, topStacks);
         }
     }
 
--- a/jmh-core/src/main/java/org/openjdk/jmh/profile/WinPerfAsmProfiler.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/profile/WinPerfAsmProfiler.java	Wed Jun 10 22:28:27 2015 +0300
@@ -24,6 +24,8 @@
  */
 package org.openjdk.jmh.profile;
 
+import joptsimple.OptionParser;
+import joptsimple.OptionSpec;
 import org.openjdk.jmh.infra.BenchmarkParams;
 import org.openjdk.jmh.results.BenchmarkResult;
 import org.openjdk.jmh.results.Result;
@@ -65,68 +67,45 @@
  */
 public class WinPerfAsmProfiler extends AbstractPerfAsmProfiler {
 
-    /**
-     * Events to gather.
-     * There is only one predefined event type to gather.
-     */
-    private static final String[] EVENTS = new String[] { "SampledProfile" };
+    private final String xperfProviders;
+    private final String symbolDir;
+    private final String path;
+
+    /** PID. */
+    private volatile String pid;
+    private OptionSpec<String> optXperfDir;
+    private OptionSpec<String> optXperfProviders;
+    private OptionSpec<String> optSymbolDir;
 
     /**
-     * Error messages caught during support check.
+     * Constructor.
      */
-    protected static final Collection<String> FAIL_MSGS = new ArrayList<String>();
+    public WinPerfAsmProfiler(String initLine) throws ProfilerException {
+        super(initLine, "SampledProfile");
 
-    /**
-     * Path to "xperf" installation directory. Empty by default, so that xperf is expected to be in PATH.
-     */
-    private static final String XPERF_DIR = System.getProperty("jmh.perfasm.xperf.dir");
+        String xperfDir = set.valueOf(optXperfDir);
+        xperfProviders = set.valueOf(optXperfProviders);
+        symbolDir = set.valueOf(optSymbolDir);
 
-    /**
-     * Providers.
-     */
-    private static final String XPERF_PROVIDERS = System.getProperty("jmh.perfasm.xperf.providers",
-        "loader+proc_thread+profile");
+        path = xperfDir != null && !xperfDir.isEmpty() ? xperfDir + File.separatorChar + "xperf" : "xperf";
 
-    /**
-     * Path to a directory with jvm.dll symbols (optional).
-     */
-    private static final String SYMBOL_DIR = System.getProperty("jmh.perfasm.symbol.dir", "");
-
-    /**
-     * Path to binary.
-     */
-    private static final String PATH;
-
-    static {
-        PATH = XPERF_DIR != null && !XPERF_DIR.isEmpty() ? XPERF_DIR + File.separatorChar + "xperf" : "xperf";
-
-        Collection<String> errs = Utils.tryWith(PATH);
-
-        if (errs != null && !errs.isEmpty()) {
-            FAIL_MSGS.addAll(errs);
+        Collection<String> errs = Utils.tryWith(path);
+        if (!errs.isEmpty()) {
+            throw new ProfilerException(errs.toString());
         }
     }
 
-    /** PID. */
-    private volatile String pid;
-
-    /**
-     * Constructor.
-     *
-     * @throws IOException If failed.
-     */
-    public WinPerfAsmProfiler() throws IOException {
-        super(EVENTS);
-    }
-
     @Override
-    public boolean checkSupport(List<String> msgs) {
-        if (FAIL_MSGS.isEmpty()) {
-            return true;
-        } else {
-            msgs.addAll(FAIL_MSGS);
-            return false;
-        }
+    protected void addMyOptions(OptionParser parser) {
+        optXperfDir = parser.accepts("xperf.dir",
+                "Path to \"xperf\" installation directory. Empty by default, so that xperf is expected to be in PATH.")
+                .withRequiredArg().ofType(String.class).describedAs("path");
+        optXperfProviders = parser.accepts("xperf.providers",
+                "xperf providers to use.")
+                .withRequiredArg().ofType(String.class).describedAs("string").defaultsTo("loader+proc_thread+profile");
+        optSymbolDir = parser.accepts("symbol.dir",
+                "Path to a directory with jvm.dll symbols (optional).")
+                .withRequiredArg().ofType(String.class).describedAs("string").defaultsTo("");
     }
 
     @Override
@@ -140,7 +119,7 @@
     @Override
     public void beforeTrial(BenchmarkParams params) {
         // Start profiler before forked JVM is started.insta
-        Collection<String> errs = Utils.tryWith(PATH, "-on", XPERF_PROVIDERS);
+        Collection<String> errs = Utils.tryWith(path, "-on", xperfProviders);
 
         if (!errs.isEmpty())
             throw new IllegalStateException("Failed to start xperf: " + errs);
@@ -149,18 +128,13 @@
     @Override
     public Collection<? extends Result> afterTrial(BenchmarkResult br, long pid, File stdOut, File stdErr) {
         if (pid == 0) {
-            throw new IllegalStateException(label() + " needs the forked VM PID, but it is not initialized.");
+            throw new IllegalStateException("perfasm needs the forked VM PID, but it is not initialized.");
         }
         this.pid = String.valueOf(pid);
         return super.afterTrial(br, pid, stdOut, stdErr);
     }
 
     @Override
-    public String label() {
-        return "xperfasm";
-    }
-
-    @Override
     public String getDescription() {
         return "Windows xperf + PrintAssembly Profiler";
     }
@@ -168,15 +142,15 @@
     @Override
     protected void parseEvents() {
         // 1. Stop profiling by calling xperf dumper.
-        Collection<String> errs = Utils.tryWith(PATH, "-d", perfBinData);
+        Collection<String> errs = Utils.tryWith(path, "-d", perfBinData);
 
         if (!errs.isEmpty())
             throw new IllegalStateException("Failed to stop xperf: " + errs);
 
         // 2. Convert binary data to text form.
         try {
-            ProcessBuilder pb = new ProcessBuilder(PATH, "-i", perfBinData, "-symbols", "-a", "dumper");
-            pb.environment().put("_NT_SYMBOL_PATH", SYMBOL_DIR);
+            ProcessBuilder pb = new ProcessBuilder(path, "-i", perfBinData, "-symbols", "-a", "dumper");
+            pb.environment().put("_NT_SYMBOL_PATH", symbolDir);
 
             Process p = pb.start();
 
@@ -211,7 +185,7 @@
             Map<Long, String> methods = new HashMap<Long, String>();
             Map<Long, String> libs = new HashMap<Long, String>();
             Map<String, Multiset<Long>> events = new LinkedHashMap<String, Multiset<Long>>();
-            for (String evName : EVENTS) {
+            for (String evName : this.events) {
                 events.put(evName, new TreeMultiset<Long>());
             }
 
@@ -224,7 +198,7 @@
                 String evName = elems[0].trim();
 
                 // We work with only one event type - "SampledProfile".
-                if (!EVENTS[0].equals(evName))
+                if (!this.events.get(0).equals(evName))
                     continue;
 
                 // Check PID.
@@ -276,9 +250,9 @@
 
             methods.put(0L, "<kernel>");
 
-            return new PerfEvents(tracedEvents, events, methods, libs);
+            return new PerfEvents(this.events, events, methods, libs);
         } catch (IOException e) {
-            return new PerfEvents(tracedEvents);
+            return new PerfEvents(events);
         } finally {
             FileUtils.safelyClose(fr);
         }
--- a/jmh-core/src/main/java/org/openjdk/jmh/runner/BenchmarkHandler.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/runner/BenchmarkHandler.java	Wed Jun 10 22:28:27 2015 +0300
@@ -28,7 +28,6 @@
 import org.openjdk.jmh.infra.IterationParams;
 import org.openjdk.jmh.infra.ThreadParams;
 import org.openjdk.jmh.profile.InternalProfiler;
-import org.openjdk.jmh.profile.Profiler;
 import org.openjdk.jmh.profile.ProfilerFactory;
 import org.openjdk.jmh.results.BenchmarkTaskResult;
 import org.openjdk.jmh.results.IterationResult;
@@ -42,19 +41,8 @@
 
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Callable;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
+import java.util.*;
+import java.util.concurrent.*;
 
 
 /**
@@ -82,7 +70,7 @@
         final Class<?> clazz = ClassUtils.loadClass(target.substring(0, lastDot));
 
         this.method = BenchmarkHandler.findBenchmarkMethod(clazz, target.substring(lastDot + 1));
-        this.profilers = createProfilers(options);
+        this.profilers = ProfilerFactory.getSupportedInternal(options.getProfilers());
         this.profilersRev = new ArrayList<InternalProfiler>(profilers);
         Collections.reverse(profilersRev);
 
@@ -106,16 +94,6 @@
         }
     }
 
-    private static List<InternalProfiler> createProfilers(Options options) {
-        List<InternalProfiler> list = new ArrayList<InternalProfiler>();
-        // register the profilers
-        for (Class<? extends Profiler> prof : options.getProfilers()) {
-            if (!ProfilerFactory.isInternal(prof)) continue;
-            list.add((InternalProfiler) ProfilerFactory.prepareProfiler(prof, options.verbosity().orElse(Defaults.VERBOSITY)));
-        }
-        return list;
-    }
-
     static ThreadParams[] distributeThreads(int threads, int[] groups) {
         ThreadParams[] result = new ThreadParams[threads];
         int totalGroupThreads = Utils.sum(groups);
--- a/jmh-core/src/main/java/org/openjdk/jmh/runner/Runner.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/runner/Runner.java	Wed Jun 10 22:28:27 2015 +0300
@@ -29,7 +29,7 @@
 import org.openjdk.jmh.infra.BenchmarkParams;
 import org.openjdk.jmh.infra.IterationParams;
 import org.openjdk.jmh.profile.ExternalProfiler;
-import org.openjdk.jmh.profile.Profiler;
+import org.openjdk.jmh.profile.ProfilerException;
 import org.openjdk.jmh.profile.ProfilerFactory;
 import org.openjdk.jmh.results.*;
 import org.openjdk.jmh.results.format.ResultFormatFactory;
@@ -37,6 +37,7 @@
 import org.openjdk.jmh.runner.format.OutputFormatFactory;
 import org.openjdk.jmh.runner.link.BinaryLinkServer;
 import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.ProfilerConfig;
 import org.openjdk.jmh.runner.options.TimeValue;
 import org.openjdk.jmh.runner.options.VerboseMode;
 import org.openjdk.jmh.util.*;
@@ -196,14 +197,16 @@
     }
 
     private Collection<RunResult> internalRun() throws RunnerException {
-        for (Class<? extends Profiler> p : options.getProfilers()) {
-            List<String> initMessages = new ArrayList<String>();
-            if (!ProfilerFactory.checkSupport(p, initMessages)) {
-                StringBuilder sb = new StringBuilder();
-                for (String im : initMessages) {
-                    sb.append(String.format("%5s %s\n", "", im));
-                }
-                throw new RunnerException("The requested profiler (" + p.getName() + ") is not supported: \n" + sb.toString());
+        boolean someProfilersFail = false;
+        for (ProfilerConfig p : options.getProfilers()) {
+            try {
+                ProfilerFactory.getProfilerOrException(p);
+            } catch (ProfilerException e) {
+                out.println(e.getMessage());
+                someProfilersFail = true;
+            }
+            if (someProfilersFail) {
+                throw new RunnerException("Some profilers have failed to initialize");
             }
         }
 
@@ -551,16 +554,13 @@
 
             BenchmarkParams params = actionPlan.getMeasurementActions().get(0).getParams();
 
+            List<ExternalProfiler> profilers = ProfilerFactory.getSupportedExternal(options.getProfilers());
+
             boolean printOut = true;
             boolean printErr = true;
-            List<ExternalProfiler> profilers = new ArrayList<ExternalProfiler>();
-
             List<String> javaInvokeOptions = new ArrayList<String>();
             List<String> javaOptions = new ArrayList<String>();
-            for (Class<? extends Profiler> p : options.getProfilers()) {
-                if (!ProfilerFactory.isExternal(p)) continue;
-                ExternalProfiler prof = (ExternalProfiler) ProfilerFactory.prepareProfiler(p, null);
-                profilers.add(prof);
+            for (ExternalProfiler prof : profilers) {
                 javaInvokeOptions.addAll(prof.addJVMInvokeOptions(params));
                 javaOptions.addAll(prof.addJVMOptions(params));
                 printOut &= prof.allowPrintOut();
@@ -615,7 +615,7 @@
                 if (!profilers.isEmpty()) {
                     out.print("# Preparing profilers: ");
                     for (ExternalProfiler profiler : profilers) {
-                        out.print(profiler.label() + " ");
+                        out.print(profiler.getClass().getSimpleName() + " ");
                         profiler.beforeTrial(params);
                     }
                     out.println("");
@@ -638,7 +638,7 @@
                     if (!profilersRev.isEmpty()) {
                         out.print("# Processing profiler results: ");
                         for (ExternalProfiler profiler : profilersRev) {
-                            out.print(profiler.label() + " ");
+                            out.print(profiler.getClass().getSimpleName() + " ");
                             for (Result profR : profiler.afterTrial(br, pid, stdOut, stdErr)) {
                                 br.addBenchmarkResult(profR);
                             }
--- a/jmh-core/src/main/java/org/openjdk/jmh/runner/options/ChainedOptionsBuilder.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/runner/options/ChainedOptionsBuilder.java	Wed Jun 10 22:28:27 2015 +0300
@@ -101,6 +101,29 @@
     ChainedOptionsBuilder addProfiler(Class<? extends Profiler> profiler);
 
     /**
+     * Add the profiler in the run
+     * @param profiler profiler class
+     * @param initLine profiler options initialization line
+     * @return builder
+     */
+    ChainedOptionsBuilder addProfiler(Class<? extends Profiler> profiler, String initLine);
+
+    /**
+     * Add the profiler in the run
+     * @param profiler profiler class name, or profiler alias
+     * @return builder
+     */
+    ChainedOptionsBuilder addProfiler(String profiler);
+
+    /**
+     * Add the profiler in the run
+     * @param profiler profiler class name, or profiler alias
+     * @param initLine profiler options initialization line
+     * @return builder
+     */
+    ChainedOptionsBuilder addProfiler(String profiler, String initLine);
+
+    /**
      * Control verbosity level.
      * @param mode flag
      * @return builder
--- a/jmh-core/src/main/java/org/openjdk/jmh/runner/options/CommandLineOptions.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/runner/options/CommandLineOptions.java	Wed Jun 10 22:28:27 2015 +0300
@@ -26,7 +26,6 @@
 
 import joptsimple.*;
 import org.openjdk.jmh.annotations.Mode;
-import org.openjdk.jmh.profile.Profiler;
 import org.openjdk.jmh.profile.ProfilerFactory;
 import org.openjdk.jmh.results.format.ResultFormatType;
 import org.openjdk.jmh.util.HashMultimap;
@@ -58,7 +57,7 @@
     private final Optional<Boolean> gcEachIteration;
     private final Optional<VerboseMode> verbose;
     private final Optional<Boolean> failOnError;
-    private final List<Class<? extends Profiler>> profilers = new ArrayList<Class<? extends Profiler>>();
+    private final List<ProfilerConfig> profilers = new ArrayList<ProfilerConfig>();
     private final Optional<TimeUnit> timeUnit;
     private final Optional<Integer> opsPerInvocation;
     private final List<String> regexps = new ArrayList<String>();
@@ -154,7 +153,7 @@
 
         OptionSpec<String> optProfilers = parser.accepts("prof", "Use profilers to collect additional data." +
                 " See the list of available profilers first.")
-                .withRequiredArg().withValuesSeparatedBy(',').ofType(String.class).describedAs("profiler+");
+                .withRequiredArg().ofType(String.class).describedAs("profiler");
 
         OptionSpec<Integer> optThreadGroups = parser.accepts("tg", "Override thread group distribution for asymmetric benchmarks.")
                 .withRequiredArg().withValuesSeparatedBy(',').ofType(Integer.class)
@@ -310,11 +309,10 @@
             if (set.has(optProfilers)) {
                 try {
                     for (String m : optProfilers.values(set)) {
-                        Class<? extends Profiler> pClass = ProfilerFactory.getProfilerByName(m);
-                        if (pClass == null) {
-                            throw new CommandLineOptionException("Unable to find profiler: " + m);
-                        }
-                        profilers.add(pClass);
+                        int idx = m.indexOf(":");
+                        String profName = (idx == -1) ?  m : m.substring(0, idx);
+                        String params   = (idx == -1) ? "" : m.substring(idx + 1);
+                        profilers.add(new ProfilerConfig(profName, params));
                     }
                 } catch (IllegalArgumentException iae) {
                     throw new CommandLineOptionException(iae.getMessage(), iae);
@@ -387,37 +385,7 @@
     }
 
     public void listProfilers() {
-        StringBuilder supported = new StringBuilder();
-        StringBuilder unsupported = new StringBuilder();
-
-        List<Class<? extends Profiler>> discoveredProfilers = ProfilerFactory.getDiscoveredProfilers();
-
-        for (Class<? extends Profiler> s : ProfilerFactory.getAvailableProfilers()) {
-            List<String> initMessages = new ArrayList<String>();
-            if (ProfilerFactory.checkSupport(s, initMessages)) {
-                supported.append(String.format("%20s: %s %s\n",
-                        ProfilerFactory.getLabel(s),
-                        ProfilerFactory.getDescription(s),
-                        discoveredProfilers.contains(s) ? "(discovered)" : ""));
-            } else {
-                unsupported.append(String.format("%20s: %s %s\n",
-                        ProfilerFactory.getLabel(s),
-                        ProfilerFactory.getDescription(s),
-                        discoveredProfilers.contains(s) ? "(discovered)" : ""));
-                for (String im : initMessages) {
-                    unsupported.append(String.format("%20s  %s\n", "", im));
-                }
-                unsupported.append("\n");
-            }
-        }
-
-        if (!supported.toString().isEmpty()) {
-            System.out.println("Supported profilers:\n" + supported.toString());
-        }
-
-        if (!unsupported.toString().isEmpty()) {
-            System.out.println("Unsupported profilers:\n" + unsupported.toString());
-        }
+        ProfilerFactory.listProfilers(System.out);
     }
 
     public void listResultFormats() {
@@ -602,7 +570,7 @@
     }
 
     @Override
-    public List<Class<? extends Profiler>> getProfilers() {
+    public List<ProfilerConfig> getProfilers() {
         return profilers;
     }
 
--- a/jmh-core/src/main/java/org/openjdk/jmh/runner/options/Options.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/runner/options/Options.java	Wed Jun 10 22:28:27 2015 +0300
@@ -25,7 +25,6 @@
 package org.openjdk.jmh.runner.options;
 
 import org.openjdk.jmh.annotations.Mode;
-import org.openjdk.jmh.profile.Profiler;
 import org.openjdk.jmh.results.format.ResultFormatType;
 import org.openjdk.jmh.util.Optional;
 
@@ -77,7 +76,7 @@
      * Profilers will start in the order specified by collection, and will stop in the reverse order.
      * @return profilers to use; empty collection if no profilers are required
      */
-    List<Class<? extends Profiler>> getProfilers();
+    List<ProfilerConfig> getProfilers();
 
     /**
      * How verbose should we be?
--- a/jmh-core/src/main/java/org/openjdk/jmh/runner/options/OptionsBuilder.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/runner/options/OptionsBuilder.java	Wed Jun 10 22:28:27 2015 +0300
@@ -192,16 +192,34 @@
 
     // ---------------------------------------------------------------------------
 
-    private List<Class<? extends Profiler>> profilers = new ArrayList<Class<? extends Profiler>>();
+    private List<ProfilerConfig> profilers = new ArrayList<ProfilerConfig>();
 
     @Override
     public ChainedOptionsBuilder addProfiler(Class<? extends Profiler> prof) {
-        this.profilers.add(prof);
+        this.profilers.add(new ProfilerConfig(prof.getCanonicalName()));
         return this;
     }
 
     @Override
-    public List<Class<? extends Profiler>> getProfilers() {
+    public ChainedOptionsBuilder addProfiler(Class<? extends Profiler> prof, String initLine) {
+        this.profilers.add(new ProfilerConfig(prof.getCanonicalName(), initLine));
+        return this;
+    }
+
+    @Override
+    public ChainedOptionsBuilder addProfiler(String prof) {
+        this.profilers.add(new ProfilerConfig(prof, ""));
+        return this;
+    }
+
+    @Override
+    public ChainedOptionsBuilder addProfiler(String prof, String initLine) {
+        this.profilers.add(new ProfilerConfig(prof, initLine));
+        return this;
+    }
+
+    @Override
+    public List<ProfilerConfig> getProfilers() {
         if (otherOptions != null) {
             profilers.addAll(otherOptions.getProfilers());
         }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jmh-core/src/main/java/org/openjdk/jmh/runner/options/ProfilerConfig.java	Wed Jun 10 22:28:27 2015 +0300
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package org.openjdk.jmh.runner.options;
+
+import java.io.Serializable;
+
+public class ProfilerConfig implements Serializable {
+
+    private final String klass;
+    private final String opts;
+
+    public ProfilerConfig(String klass, String opts) {
+        this.klass = klass;
+        this.opts = opts;
+    }
+
+    public ProfilerConfig(String klass) {
+        this.klass = klass;
+        this.opts = "";
+    }
+
+    public String getKlass() {
+        return klass;
+    }
+
+    public String getOpts() {
+        return opts;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        ProfilerConfig that = (ProfilerConfig) o;
+
+        if (!klass.equals(that.klass)) return false;
+        if (!opts.equals(that.opts)) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = klass.hashCode();
+        result = 31 * result + opts.hashCode();
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return klass + ":" + opts;
+    }
+}
--- a/jmh-core/src/test/java/org/openjdk/jmh/runner/options/TestOptions.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core/src/test/java/org/openjdk/jmh/runner/options/TestOptions.java	Wed Jun 10 22:28:27 2015 +0300
@@ -29,8 +29,6 @@
 import org.junit.Test;
 import org.openjdk.jmh.annotations.Mode;
 import org.openjdk.jmh.annotations.Threads;
-import org.openjdk.jmh.profile.ClassloaderProfiler;
-import org.openjdk.jmh.profile.CompilerProfiler;
 import org.openjdk.jmh.results.format.ResultFormatType;
 
 import java.io.ByteArrayOutputStream;
@@ -165,9 +163,8 @@
 
     @Test
     public void testProfilers() throws Exception {
-        // TODO: Should be able to accept multiple values without concat?
-        CommandLineOptions cmdLine = new CommandLineOptions("-prof", "cl,comp");
-        Options builder = new OptionsBuilder().addProfiler(ClassloaderProfiler.class).addProfiler(CompilerProfiler.class).build();
+        CommandLineOptions cmdLine = new CommandLineOptions("-prof", "cl", "-prof", "comp");
+        Options builder = new OptionsBuilder().addProfiler("cl").addProfiler("comp").build();
         Assert.assertEquals(builder.getProfilers(), cmdLine.getProfilers());
     }
 
--- a/jmh-core/src/test/java/org/openjdk/jmh/runner/options/TestParentOptions.java	Tue Jun 09 10:27:26 2015 +0300
+++ b/jmh-core/src/test/java/org/openjdk/jmh/runner/options/TestParentOptions.java	Wed Jun 10 22:28:27 2015 +0300
@@ -27,8 +27,6 @@
 import org.junit.Assert;
 import org.junit.Test;
 import org.openjdk.jmh.annotations.Mode;
-import org.openjdk.jmh.profile.ClassloaderProfiler;
-import org.openjdk.jmh.profile.CompilerProfiler;
 import org.openjdk.jmh.results.format.ResultFormatType;
 
 import java.util.Arrays;
@@ -89,19 +87,19 @@
 
     @Test
     public void testProfiler_Parent() throws Exception {
-        Options parent = new OptionsBuilder().addProfiler(ClassloaderProfiler.class).build();
+        Options parent = new OptionsBuilder().addProfiler("cl").build();
         Options builder = new OptionsBuilder().parent(parent).build();
         Assert.assertTrue(builder.getProfilers().size() == 1);
-        Assert.assertTrue(builder.getProfilers().contains(ClassloaderProfiler.class));
+        Assert.assertTrue(builder.getProfilers().contains(new ProfilerConfig("cl", "")));
     }
 
     @Test
     public void testProfiler_Merge() throws Exception {
-        Options parent = new OptionsBuilder().addProfiler(ClassloaderProfiler.class).build();
-        Options builder = new OptionsBuilder().parent(parent).addProfiler(CompilerProfiler.class).build();
+        Options parent = new OptionsBuilder().addProfiler("cl").build();
+        Options builder = new OptionsBuilder().parent(parent).addProfiler("comp").build();
         Assert.assertTrue(builder.getProfilers().size() == 2);
-        Assert.assertTrue(builder.getProfilers().contains(ClassloaderProfiler.class));
-        Assert.assertTrue(builder.getProfilers().contains(CompilerProfiler.class));
+        Assert.assertTrue(builder.getProfilers().contains(new ProfilerConfig("cl", "")));
+        Assert.assertTrue(builder.getProfilers().contains(new ProfilerConfig("comp", "")));
     }
 
     @Test