diff --git a/README.md b/README.md index 3eb74859..c491a63c 100644 --- a/README.md +++ b/README.md @@ -287,9 +287,26 @@ Here is an example: Values are optional and default to the values provided on the command-line or defined in the build. -### Profiling incremental builds +### Benchmark options -A scenario can define changes that should be applied to the source before each build. You can use this to benchmark or profile an incremental build. The following mutations are available: +- `iterations`: Number of builds to actually measure +- `warm-ups`: Number of warmups to perform before measurement +- `jvm-args`: Sets or overrides the jvm arguments set by `org.gradle.jvmargs` in gradle.properties. + +### Profiling change handling + +How a build tool handles changes to the source code can have a significant impact on the performance of the build. +Gradle Profiler can simulate different kinds of changes to the source code to measure the impact of these changes on the build performance. +These changes are applied by mutators at different points in the build benchmark process. +Some mutators execute at a specific point, others can be configured to execute at a specific point, specified by the `schedule` parameter: + +- `SCENARIO`: before the scenario is executed, +- `CLEANUP`: before cleaning preceeding each build invocation, +- `BUILD`: before the build invocation (after cleanup). + +#### Source code mutators + +These mutations are applied before each build, and they introduce different kinds of change to the source code. - `apply-abi-change-to`: Add a public method to a Java or Kotlin source class. Each iteration adds a new method and removes the method added by the previous iteration. - `apply-android-layout-change-to`: Add a hidden view with id to an Android layout file. Supports traditional layouts as well as Databinding layouts with a ViewGroup as the root element. @@ -303,23 +320,29 @@ A scenario can define changes that should be applied to the source before each b - `apply-non-abi-change-to`: Change the body of a public method in a Java or Kotlin source class. - `apply-project-dependency-change-to`: Add project dependencies to a Groovy or a Kotlin DSL build script. Each iteration adds a new combination of projects as dependencies and removes the projects added by the previous iteration. - `apply-property-resource-change-to`: Add an entry to a properties file. Each iteration adds a new entry and removes the entry added by the previous iteration. -- `clear-android-studio-cache-before`: Invalidates the Android Studio caches before the scenario is executed (`SCENARIO`) or before the build is executed (`BUILD`). Due to Android Studio client specifics before cleanup (`CLEANUP`) is not supported. Note: cleaning the Android Studio caches is run only when Android Studio sync (`android-studio-sync`) is used. -- `clear-build-cache-before`: Deletes the contents of the build cache before the scenario is executed (`SCENARIO`), before cleanup (`CLEANUP`) or before the build is executed (`BUILD`). -- `clear-configuration-cache-state-before`: Deletes the contents of the `.gradle/configuration-cache-state` directory before the scenario is executed (`SCENARIO`), before cleanup (`CLEANUP`) or before the build is executed (`BUILD`). -- `clear-gradle-user-home-before`: Deletes the contents of the Gradle user home directory before the scenario is executed (`SCENARIO`), before cleanup (`CLEANUP`) or before the build is executed (`BUILD`). + +#### Cache cleanup + +When simulating scenarios like ephemeral builds, it is important to make sure caches are not present. +These mutators can be scheduled to execute at different points in the build benchmark process, specified by the `schedule` parameter. + +- `clear-android-studio-cache-before`: Invalidates the Android Studio caches. Due to Android Studio client specifics scheduling to run before cleanup (`CLEANUP`) is not supported. Note: cleaning the Android Studio caches is run only when Android Studio sync (`android-studio-sync`) is used. +- `clear-build-cache-before`: Deletes the contents of the build cache at the given schedule. +- `clear-configuration-cache-state-before`: Deletes the contents of the `.gradle/configuration-cache-state` directory. +- `clear-gradle-user-home-before`: Deletes the contents of the Gradle user home directory. The mutator retains the `wrapper` cache in the Gradle user home, since the downloaded wrapper in that location is used to run Gradle. - Requires to use the `none` daemon option to use with `CLEANUP` or `BUILD`. -- `clear-jars-cache-before`: Deletes the contents of the instrumented jars cache before the scenario is executed (`SCENARIO`), before cleanup (`CLEANUP`) or before the build is executed (`BUILD`). -- `clear-project-cache-before`: Deletes the contents of the `.gradle` and `buildSrc/.gradle` project cache directories before the scenario is executed (`SCENARIO`), before cleanup (`CLEANUP`) or before the build is executed (`BUILD`). -- `clear-transform-cache-before`: Deletes the contents of the transform cache before the scenario is executed (`SCENARIO`), before cleanup (`CLEANUP`) or before the build is executed (`BUILD`). -- `copy-file`: Copies a file or a directory from one location to another. Has to specify a `source` and a `target` path; relative paths are resolved against the project directory. Can also specify a schedule (defaults `SCENARIO`). Can take an array of operations. -- `delete-file`: Deletes a file or a directory. Has to specify a `target` path; when relative it is resolved against the project directory. Can also specify a schedule (defaults `SCENARIO`). Can take an array of operations. + Requires to use the `none` daemon option to use with `CLEANUP` or `BUILD` schedules. +- `clear-jars-cache-before`: Deletes the contents of the instrumented jars cache. +- `clear-project-cache-before`: Deletes the contents of the `.gradle` and `buildSrc/.gradle` project cache directories. +- `clear-transform-cache-before`: Deletes the contents of the transform cache. +- `show-build-cache-size`: Shows the number of files and their size in the build cache before scenario execution, and after each cleanup and build round. + +#### File operations + +- `copy-file`: Copies a file or a directory from one location to another. Has to specify a `source` and a `target` path; relative paths are resolved against the project directory. Can take an array of operations. Defaults to `SCNEARIO` schedule. +- `delete-file`: Deletes a file or a directory. Has to specify a `target` path; when relative it is resolved against the project directory. Can take an array of operations. Defaults to `SCNEARIO` schedule. - `git-checkout`: Checks out a specific commit for the build step, and a different one for the cleanup step. -- `git-revert`: Reverts a given set of commits before the build and resets it afterward. -- `iterations`: Number of builds to actually measure -- `jvm-args`: Sets or overrides the jvm arguments set by `org.gradle.jvmargs` in gradle.properties. -- `show-build-cache-size`: Shows the number of files and their size in the build cache before scenario execution, and after each cleanup and build round.. -- `warm-ups`: Number of warmups to perform before measurement +- `git-revert`: Reverts a given set of commits before the build and resets it afterward. They can be added to a scenario file like this: diff --git a/src/main/java/org/gradle/profiler/BuildToolCommandLineInvoker.java b/src/main/java/org/gradle/profiler/BuildToolCommandLineInvoker.java index d1264580..8d814f84 100644 --- a/src/main/java/org/gradle/profiler/BuildToolCommandLineInvoker.java +++ b/src/main/java/org/gradle/profiler/BuildToolCommandLineInvoker.java @@ -1,11 +1,14 @@ package org.gradle.profiler; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import org.gradle.profiler.result.BuildActionResult; import org.gradle.profiler.result.BuildInvocationResult; import java.io.File; import java.time.Duration; import java.util.List; +import java.util.Map; import java.util.function.Consumer; import java.util.function.Supplier; @@ -13,7 +16,19 @@ import static org.gradle.profiler.Phase.WARM_UP; public abstract class BuildToolCommandLineInvoker extends ScenarioInvoker { - protected void doRun(T scenario, InvocationSettings settings, Consumer resultConsumer, List commandLine) { + protected void doRun(T scenario, InvocationSettings settings, Consumer resultConsumer, List commandLine, Map envVars) { + doRun(scenario, settings, resultConsumer, commandLine, envVars, ImmutableList.of(), ImmutableMap.of()); + } + + protected void doRun( + T scenario, + InvocationSettings settings, + Consumer resultConsumer, + List commandLine, + Map envVars, + List profileCommandLine, + Map profileEnvVars + ) { ScenarioContext scenarioContext = ScenarioContext.from(settings, scenario); BuildMutator mutator = CompositeBuildMutator.from(scenario.getBuildMutators()); @@ -21,11 +36,21 @@ protected void doRun(T scenario, InvocationSettings settings, Consumer action = measureCommandLineExecution(commandLine, envVars, settings.getProjectDir(), settings.getBuildLog()); + runMeasured(buildContext, mutator, action, resultConsumer); } for (int iteration = 1; iteration <= scenario.getBuildCount(); iteration++) { BuildContext buildContext = scenarioContext.withBuild(MEASURE, iteration); - runMeasured(buildContext, mutator, measureCommandLineExecution(commandLine, settings.getProjectDir(), settings.getBuildLog()), resultConsumer); + List commandLineCombined = ImmutableList.builder() + .addAll(commandLine) + .addAll(profileCommandLine) + .build(); + Map envVarsCombined = ImmutableMap.builder() + .putAll(envVars) + .putAll(profileEnvVars) + .build(); + BuildStepAction action = measureCommandLineExecution(commandLineCombined, envVarsCombined, settings.getProjectDir(), settings.getBuildLog()); + runMeasured(buildContext, mutator, action, resultConsumer); } } finally { mutator.afterScenario(scenarioContext); @@ -35,7 +60,7 @@ protected void doRun(T scenario, InvocationSettings settings, Consumer measureCommandLineExecution(List commandLine, File workingDir, File buildLog) { + private BuildStepAction measureCommandLineExecution(List commandLine, Map envVars, File workingDir, File buildLog) { return new BuildStepAction() { @Override public boolean isDoesSomething() { @@ -44,11 +69,16 @@ public boolean isDoesSomething() { @Override public R run(BuildContext buildContext, BuildStep buildStep) { + Logging.detailed().println(" Command: " + commandLine); + Logging.detailed().println(" Environment: " + envVars); + CommandExec commandExec = new CommandExec() + .inDir(workingDir) + .environmentVariables(envVars); Timer timer = new Timer(); if (buildLog == null) { - new CommandExec().inDir(workingDir).run(commandLine); + commandExec.run(commandLine); } else { - new CommandExec().inDir(workingDir).runAndCollectOutput(buildLog, commandLine); + commandExec.runAndCollectOutput(buildLog, commandLine); } Duration executionTime = timer.elapsed(); return (R) new BuildInvocationResult(buildContext, new BuildActionResult(executionTime)); diff --git a/src/main/java/org/gradle/profiler/BuildToolCommandLineScenarioDefinition.java b/src/main/java/org/gradle/profiler/BuildToolCommandLineScenarioDefinition.java index 07cf74fa..d13a1a36 100644 --- a/src/main/java/org/gradle/profiler/BuildToolCommandLineScenarioDefinition.java +++ b/src/main/java/org/gradle/profiler/BuildToolCommandLineScenarioDefinition.java @@ -1,6 +1,7 @@ package org.gradle.profiler; import javax.annotation.Nullable; +import javax.annotation.OverridingMethodsMustInvokeSuper; import java.io.File; import java.io.PrintStream; import java.util.List; @@ -33,9 +34,14 @@ protected void printDetail(PrintStream out) { out.println(" Targets: " + getTargets()); } - public String getExecutablePath() { + public String getExecutablePath(File projectDir) { String toolHomePath = getToolHome() == null ? System.getenv(getToolHomeEnvName()) : getToolHome().getAbsolutePath(); - return toolHomePath == null ? getExecutableName() : toolHomePath + "/bin/" + getExecutableName(); + return toolHomePath == null ? getExecutablePathWithoutToolHome(projectDir) : toolHomePath + "/bin/" + getExecutableName(); + } + + @OverridingMethodsMustInvokeSuper + protected String getExecutablePathWithoutToolHome(File projectDir) { + return getExecutableName(); } @Override @@ -53,7 +59,7 @@ public File getToolHome() { @Override public boolean createsMultipleProcesses() { - return false; + return true; } @Override diff --git a/src/main/java/org/gradle/profiler/CompositeProfiler.java b/src/main/java/org/gradle/profiler/CompositeProfiler.java index c6df0b89..d5a4a052 100644 --- a/src/main/java/org/gradle/profiler/CompositeProfiler.java +++ b/src/main/java/org/gradle/profiler/CompositeProfiler.java @@ -13,6 +13,11 @@ class CompositeProfiler extends Profiler { this.delegates = delegates; } + @Override + public boolean requiresGradle() { + return delegates.stream().anyMatch(Profiler::requiresGradle); + } + @Override public String toString() { return delegates.stream().map(Object::toString).collect(Collectors.joining(", ")); diff --git a/src/main/java/org/gradle/profiler/InstrumentingProfiler.java b/src/main/java/org/gradle/profiler/InstrumentingProfiler.java index def89933..6630ccfa 100644 --- a/src/main/java/org/gradle/profiler/InstrumentingProfiler.java +++ b/src/main/java/org/gradle/profiler/InstrumentingProfiler.java @@ -18,6 +18,12 @@ *

The profiler may support starting recording multiple times for a given JVM. The implementation should indicate this by overriding {@link #canRestartRecording(ScenarioSettings)}.

*/ public abstract class InstrumentingProfiler extends Profiler { + + @Override + public boolean requiresGradle() { + return false; + } + /** * Calculates the JVM args for all builds, including warm-ups. * @@ -67,7 +73,7 @@ public JvmArgsCalculator newInstrumentedBuildsJvmArgsCalculator(ScenarioSettings */ @Override public ProfilerController newController(String pid, ScenarioSettings settings) { - SnapshotCapturingProfilerController controller = doNewController(settings); + SnapshotCapturingProfilerController controller = newSnapshottingController(settings); if (settings.getScenario().getInvoker().isDoesNotUseDaemon()) { return new SessionOnlyController(pid, controller); } @@ -109,7 +115,7 @@ protected boolean canRestartRecording(ScenarioSettings settings) { */ protected abstract JvmArgsCalculator jvmArgsWithInstrumentation(ScenarioSettings settings, boolean startRecordingOnProcessStart, boolean captureSnapshotOnProcessExit); - protected abstract SnapshotCapturingProfilerController doNewController(ScenarioSettings settings); + public abstract SnapshotCapturingProfilerController newSnapshottingController(ScenarioSettings settings); public interface SnapshotCapturingProfilerController { void startRecording(String pid) throws IOException, InterruptedException; diff --git a/src/main/java/org/gradle/profiler/Main.java b/src/main/java/org/gradle/profiler/Main.java index 117c6722..9c34fd9a 100644 --- a/src/main/java/org/gradle/profiler/Main.java +++ b/src/main/java/org/gradle/profiler/Main.java @@ -88,6 +88,10 @@ public void run(String[] args) throws Exception { for (ScenarioDefinition scenario : scenarios) { scenarioCount++; Logging.startOperation("Running scenario " + scenario.getDisplayName() + " (scenario " + scenarioCount + "/" + totalScenarios + ")"); + if (settings.isProfile() && scenario.getWarmUpCount() == 0) { + throw new IllegalStateException("Using the --profile option requires at least one warm-up"); + } + if (scenario instanceof BazelScenarioDefinition) { invoke(bazelScenarioInvoker, (BazelScenarioDefinition) scenario, settings, benchmarkResults, failures); } else if (scenario instanceof BuckScenarioDefinition) { diff --git a/src/main/java/org/gradle/profiler/Profiler.java b/src/main/java/org/gradle/profiler/Profiler.java index 2f006905..7c0552a9 100644 --- a/src/main/java/org/gradle/profiler/Profiler.java +++ b/src/main/java/org/gradle/profiler/Profiler.java @@ -18,15 +18,25 @@ import java.io.File; import java.util.function.Consumer; -public class Profiler { +public abstract class Profiler { public static final Profiler NONE = new Profiler() { + @Override + public boolean requiresGradle() { + return false; + } + @Override public String toString() { return "none"; } }; + /** + * Whether this profiler supports only Gradle builds. + */ + public abstract boolean requiresGradle(); + public void validate(ScenarioSettings settings, Consumer reporter) { } diff --git a/src/main/java/org/gradle/profiler/ScenarioDefinition.java b/src/main/java/org/gradle/profiler/ScenarioDefinition.java index 9041fba9..311d43b9 100644 --- a/src/main/java/org/gradle/profiler/ScenarioDefinition.java +++ b/src/main/java/org/gradle/profiler/ScenarioDefinition.java @@ -1,8 +1,12 @@ package org.gradle.profiler; +import org.apache.commons.io.FileUtils; + import javax.annotation.Nullable; import java.io.File; +import java.io.IOException; import java.io.PrintStream; +import java.io.UncheckedIOException; import java.util.List; import java.util.function.Consumer; @@ -28,6 +32,11 @@ public ScenarioDefinition( this.warmUpCount = warmUpCount; this.buildCount = buildCount; this.outputDir = outputDir; + try { + FileUtils.forceMkdir(outputDir); + } catch (IOException e) { + throw new UncheckedIOException(e); + } } public void validate() { @@ -98,6 +107,7 @@ public void printTo(PrintStream out) { } public void visitProblems(InvocationSettings settings, Consumer reporter) { + settings.getProfiler().validate(new ScenarioSettings(settings, this), reporter); } protected void printDetail(PrintStream out) { @@ -108,4 +118,8 @@ protected void printDetail(PrintStream out) { public abstract boolean doesCleanup(); public abstract BuildConfiguration getBuildConfiguration(); + + public static String safeFileName(String name) { + return name.replace("/", "-"); + } } diff --git a/src/main/java/org/gradle/profiler/ScenarioLoader.java b/src/main/java/org/gradle/profiler/ScenarioLoader.java index 0f0a6c63..6fbc9f99 100644 --- a/src/main/java/org/gradle/profiler/ScenarioLoader.java +++ b/src/main/java/org/gradle/profiler/ScenarioLoader.java @@ -238,7 +238,7 @@ static List loadScenarios(File scenarioFile, InvocationSetti String title = scenario.hasPath(TITLE) ? scenario.getString(TITLE) : null; int buildCount = getBuildCount(settings, scenario); - File scenarioBaseDir = selectedScenarios.size() == 1 ? settings.getOutputDir() : new File(settings.getOutputDir(), GradleScenarioDefinition.safeFileName(scenarioName)); + File scenarioBaseDir = selectedScenarios.size() == 1 ? settings.getOutputDir() : new File(settings.getOutputDir(), ScenarioDefinition.safeFileName(scenarioName)); if (scenario.hasPath(BAZEL) && settings.isBazel()) { Config executionInstructions = getConfig(scenarioFile, settings, scenarioName, scenario, BAZEL, BAZEL_KEYS); @@ -331,8 +331,8 @@ private static List getMutators(Config scenario, String scenarioNa } private static Config getConfig(File scenarioFile, InvocationSettings settings, String scenarioName, Config scenario, String toolName, List toolKeys) { - if (settings.isProfile()) { - throw new IllegalArgumentException("Can only profile scenario '" + scenarioName + "' when building using Gradle."); + if (settings.getProfiler().requiresGradle()) { + throw new IllegalArgumentException("Profiler " + settings.getProfiler() + " is not compatible with " + toolName + " scenarios."); } Config executionInstructions = scenario.getConfig(toolName); for (String key : scenario.getObject(toolName).keySet()) { diff --git a/src/main/java/org/gradle/profiler/asyncprofiler/AsyncProfiler.java b/src/main/java/org/gradle/profiler/asyncprofiler/AsyncProfiler.java index 051f321f..032cce1f 100644 --- a/src/main/java/org/gradle/profiler/asyncprofiler/AsyncProfiler.java +++ b/src/main/java/org/gradle/profiler/asyncprofiler/AsyncProfiler.java @@ -24,7 +24,7 @@ protected JvmArgsCalculator jvmArgsWithInstrumentation(ScenarioSettings settings } @Override - protected SnapshotCapturingProfilerController doNewController(ScenarioSettings settings) { + public SnapshotCapturingProfilerController newSnapshottingController(ScenarioSettings settings) { return new AsyncProfilerController(config, settings); } diff --git a/src/main/java/org/gradle/profiler/bazel/BazelScenarioInvoker.java b/src/main/java/org/gradle/profiler/bazel/BazelScenarioInvoker.java index ee50848e..af7382dd 100644 --- a/src/main/java/org/gradle/profiler/bazel/BazelScenarioInvoker.java +++ b/src/main/java/org/gradle/profiler/bazel/BazelScenarioInvoker.java @@ -1,5 +1,6 @@ package org.gradle.profiler.bazel; +import com.google.common.collect.ImmutableMap; import org.gradle.profiler.BuildToolCommandLineInvoker; import org.gradle.profiler.InvocationSettings; import org.gradle.profiler.result.BuildInvocationResult; @@ -11,12 +12,16 @@ public class BazelScenarioInvoker extends BuildToolCommandLineInvoker { @Override public void run(BazelScenarioDefinition scenario, InvocationSettings settings, Consumer resultConsumer) { + if (settings.isProfile()) { + throw new IllegalArgumentException("Profiling is not supported for Bazel builds"); + } + List targets = scenario.getTargets(); List commandLine = new ArrayList<>(); - commandLine.add(scenario.getExecutablePath()); + commandLine.add(scenario.getExecutablePath(settings.getProjectDir())); commandLine.addAll(targets); - doRun(scenario, settings, resultConsumer, commandLine); + doRun(scenario, settings, resultConsumer, commandLine, ImmutableMap.of()); } } diff --git a/src/main/java/org/gradle/profiler/buck/BuckScenarioInvoker.java b/src/main/java/org/gradle/profiler/buck/BuckScenarioInvoker.java index 9c473212..45e45b49 100644 --- a/src/main/java/org/gradle/profiler/buck/BuckScenarioInvoker.java +++ b/src/main/java/org/gradle/profiler/buck/BuckScenarioInvoker.java @@ -1,5 +1,6 @@ package org.gradle.profiler.buck; +import com.google.common.collect.ImmutableMap; import org.gradle.profiler.BuildToolCommandLineInvoker; import org.gradle.profiler.CommandExec; import org.gradle.profiler.InvocationSettings; @@ -15,6 +16,10 @@ public class BuckScenarioInvoker extends BuildToolCommandLineInvoker { @Override public void run(BuckScenarioDefinition scenario, InvocationSettings settings, Consumer resultConsumer) { + if (settings.isProfile()) { + throw new IllegalArgumentException("Profiling is not supported for Buck builds"); + } + String buckwExe = settings.getProjectDir() + "/buckw"; List targets = new ArrayList<>(scenario.getTargets()); if (scenario.getType() != null) { @@ -37,6 +42,6 @@ public void run(BuckScenarioDefinition scenario, InvocationSettings settings, Co commandLine.add("build"); commandLine.addAll(targets); - doRun(scenario, settings, resultConsumer, commandLine); + doRun(scenario, settings, resultConsumer, commandLine, ImmutableMap.of()); } } diff --git a/src/main/java/org/gradle/profiler/buildscan/BuildScanProfiler.java b/src/main/java/org/gradle/profiler/buildscan/BuildScanProfiler.java index 5a438a1d..967c89a1 100644 --- a/src/main/java/org/gradle/profiler/buildscan/BuildScanProfiler.java +++ b/src/main/java/org/gradle/profiler/buildscan/BuildScanProfiler.java @@ -33,6 +33,11 @@ public static String defaultBuildScanVersion(GradleVersion gradleVersion) { this.buildScanVersion = buildScanVersion; } + @Override + public boolean requiresGradle() { + return true; + } + @Override public String toString() { return "buildscan"; diff --git a/src/main/java/org/gradle/profiler/chrometrace/ChromeTraceProfiler.java b/src/main/java/org/gradle/profiler/chrometrace/ChromeTraceProfiler.java index da490476..d5db686e 100644 --- a/src/main/java/org/gradle/profiler/chrometrace/ChromeTraceProfiler.java +++ b/src/main/java/org/gradle/profiler/chrometrace/ChromeTraceProfiler.java @@ -13,6 +13,12 @@ public String toString() { return "chrome-trace"; } + @Override + public boolean requiresGradle() { + return true; + } + + @Override public void summarizeResultFile(File resultFile, Consumer consumer) { if (resultFile.getName().endsWith("-trace.json")) { diff --git a/src/main/java/org/gradle/profiler/gradle/GradleScenarioDefinition.java b/src/main/java/org/gradle/profiler/gradle/GradleScenarioDefinition.java index 5cb0485f..8977f18a 100644 --- a/src/main/java/org/gradle/profiler/gradle/GradleScenarioDefinition.java +++ b/src/main/java/org/gradle/profiler/gradle/GradleScenarioDefinition.java @@ -1,8 +1,11 @@ package org.gradle.profiler.gradle; -import org.gradle.profiler.*; +import org.gradle.profiler.BuildAction; +import org.gradle.profiler.BuildMutator; +import org.gradle.profiler.GradleBuildConfiguration; +import org.gradle.profiler.InvocationSettings; +import org.gradle.profiler.ScenarioDefinition; import org.gradle.profiler.buildops.BuildOperationUtil; -import org.gradle.profiler.gradle.GradleBuildInvoker; import org.gradle.util.GradleVersion; import java.io.File; @@ -60,10 +63,6 @@ public String getProfileName() { return safeFileName(getName()) + "-" + buildConfiguration.getGradleVersion().getVersion(); } - public static String safeFileName(String name) { - return name.replace("/", "-"); - } - @Override public String getBuildToolDisplayName() { return buildConfiguration.getGradleVersion().toString(); @@ -133,7 +132,7 @@ public void visitProblems(InvocationSettings settings, Consumer reporter if (settings.isMeasureConfigTime() && isBuildServiceUnsupported()) { reporter.accept("Measuring build configuration is only supported for Gradle 6.1-milestone-3 and later"); } - settings.getProfiler().validate(new ScenarioSettings(settings, this), reporter); + super.visitProblems(settings, reporter); } private boolean isBuildServiceUnsupported() { diff --git a/src/main/java/org/gradle/profiler/gradle/GradleScenarioInvoker.java b/src/main/java/org/gradle/profiler/gradle/GradleScenarioInvoker.java index 5ea3ba58..ce9388ad 100644 --- a/src/main/java/org/gradle/profiler/gradle/GradleScenarioInvoker.java +++ b/src/main/java/org/gradle/profiler/gradle/GradleScenarioInvoker.java @@ -52,10 +52,6 @@ public SampleProvider samplesFor(InvocationSettings @Override public void run(GradleScenarioDefinition scenario, InvocationSettings settings, Consumer resultConsumer) throws IOException, InterruptedException { - if (settings.isProfile() && scenario.getWarmUpCount() == 0) { - throw new IllegalStateException("Using the --profile option requires at least one warm-up"); - } - ScenarioSettings scenarioSettings = new ScenarioSettings(settings, scenario); FileUtils.forceMkdir(scenario.getOutputDir()); JvmArgsCalculator allBuildsJvmArgsCalculator = settings.getProfiler().newJvmArgsCalculator(scenarioSettings); diff --git a/src/main/java/org/gradle/profiler/heapdump/HeapDumpProfiler.java b/src/main/java/org/gradle/profiler/heapdump/HeapDumpProfiler.java index a1aa38e5..9232b309 100644 --- a/src/main/java/org/gradle/profiler/heapdump/HeapDumpProfiler.java +++ b/src/main/java/org/gradle/profiler/heapdump/HeapDumpProfiler.java @@ -10,6 +10,11 @@ import java.util.function.Consumer; public class HeapDumpProfiler extends Profiler { + @Override + public boolean requiresGradle() { + return true; + } + @Override public GradleArgsCalculator newGradleArgsCalculator(ScenarioSettings settings) { return new GradleInstrumentation() { diff --git a/src/main/java/org/gradle/profiler/jfr/JfrProfiler.java b/src/main/java/org/gradle/profiler/jfr/JfrProfiler.java index be08e85e..9d04b894 100644 --- a/src/main/java/org/gradle/profiler/jfr/JfrProfiler.java +++ b/src/main/java/org/gradle/profiler/jfr/JfrProfiler.java @@ -29,7 +29,7 @@ public void summarizeResultFile(File resultFile, Consumer consumer) { } @Override - protected SnapshotCapturingProfilerController doNewController(ScenarioSettings settings) { + public SnapshotCapturingProfilerController newSnapshottingController(ScenarioSettings settings) { return new JFRControl(jfrArgs, settings.computeJfrProfilerOutputLocation()); } diff --git a/src/main/java/org/gradle/profiler/jprofiler/JProfilerProfiler.java b/src/main/java/org/gradle/profiler/jprofiler/JProfilerProfiler.java index 3c4ad27a..8498614f 100644 --- a/src/main/java/org/gradle/profiler/jprofiler/JProfilerProfiler.java +++ b/src/main/java/org/gradle/profiler/jprofiler/JProfilerProfiler.java @@ -32,7 +32,7 @@ protected boolean canRestartRecording(ScenarioSettings settings) { } @Override - protected SnapshotCapturingProfilerController doNewController(ScenarioSettings settings) { + public SnapshotCapturingProfilerController newSnapshottingController(ScenarioSettings settings) { return new JProfilerController(settings, jProfilerConfig); } diff --git a/src/main/java/org/gradle/profiler/maven/MavenScenarioDefinition.java b/src/main/java/org/gradle/profiler/maven/MavenScenarioDefinition.java index 1619e6f5..34e7ab06 100644 --- a/src/main/java/org/gradle/profiler/maven/MavenScenarioDefinition.java +++ b/src/main/java/org/gradle/profiler/maven/MavenScenarioDefinition.java @@ -36,7 +36,7 @@ public String getDisplayName() { @Override public String getProfileName() { - throw new UnsupportedOperationException(); + return safeFileName(getName()) + "-mvn"; } @Override @@ -58,6 +58,19 @@ protected String getExecutableName() { } } + @Override + protected String getExecutablePathWithoutToolHome(File projectDir) { + String wrapperName = OperatingSystem.isWindows() + ? "mvnw.cmd" + : "mvnw"; + File wrapper = new File(projectDir, wrapperName); + if (wrapper.isFile()) { + return wrapper.getAbsolutePath(); + } else { + return super.getExecutablePathWithoutToolHome(projectDir); + } + } + @Override protected String getToolHomeEnvName() { return "MAVEN_HOME"; diff --git a/src/main/java/org/gradle/profiler/maven/MavenScenarioInvoker.java b/src/main/java/org/gradle/profiler/maven/MavenScenarioInvoker.java index e0e92133..2ab09c91 100644 --- a/src/main/java/org/gradle/profiler/maven/MavenScenarioInvoker.java +++ b/src/main/java/org/gradle/profiler/maven/MavenScenarioInvoker.java @@ -1,11 +1,15 @@ package org.gradle.profiler.maven; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import org.gradle.profiler.BuildToolCommandLineInvoker; -import org.gradle.profiler.GradleBuildConfiguration; +import org.gradle.profiler.InstrumentingProfiler; +import org.gradle.profiler.InstrumentingProfiler.SnapshotCapturingProfilerController; import org.gradle.profiler.InvocationSettings; -import org.gradle.profiler.Logging; +import org.gradle.profiler.ScenarioSettings; import org.gradle.profiler.result.BuildInvocationResult; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -13,13 +17,37 @@ public class MavenScenarioInvoker extends BuildToolCommandLineInvoker { @Override - public void run(MavenScenarioDefinition scenario, InvocationSettings settings, Consumer resultConsumer) { + public void run(MavenScenarioDefinition scenario, InvocationSettings settings, Consumer resultConsumer) throws IOException, InterruptedException { List commandLine = new ArrayList<>(); - commandLine.add(scenario.getExecutablePath()); + commandLine.add(scenario.getExecutablePath(settings.getProjectDir())); commandLine.addAll(scenario.getTargets()); scenario.getSystemProperties().forEach((key, value) -> commandLine.add(String.format("-D%s=%s", key, value))); - doRun(scenario, settings, resultConsumer, commandLine); + Map profileEnvironment; + SnapshotCapturingProfilerController controller; + // TODO This only works with Async profiler, since the only thing we call from the controller is stopSession() + // Capture this in the type hierarchy somehow + if (settings.getProfiler() instanceof InstrumentingProfiler) { + InstrumentingProfiler profiler = (InstrumentingProfiler) settings.getProfiler(); + ScenarioSettings scenarioSettings = new ScenarioSettings(settings, scenario); + List mavenOpts = new ArrayList<>(); + profiler.newInstrumentedBuildsJvmArgsCalculator(scenarioSettings) + .calculateJvmArgs(mavenOpts); + profileEnvironment = ImmutableMap.of( + "MAVEN_OPTS", String.join(" ", mavenOpts) + ); + + controller = profiler.newSnapshottingController(scenarioSettings); + } else { + profileEnvironment = ImmutableMap.of(); + controller = null; + } + + doRun(scenario, settings, resultConsumer, commandLine, ImmutableMap.of(), ImmutableList.of(), profileEnvironment); + + if (controller != null) { + controller.stopSession(); + } } } diff --git a/src/main/java/org/gradle/profiler/yourkit/YourKitProfiler.java b/src/main/java/org/gradle/profiler/yourkit/YourKitProfiler.java index 0b0ce528..666c8ea5 100644 --- a/src/main/java/org/gradle/profiler/yourkit/YourKitProfiler.java +++ b/src/main/java/org/gradle/profiler/yourkit/YourKitProfiler.java @@ -32,7 +32,7 @@ protected boolean canRestartRecording(ScenarioSettings settings) { } @Override - protected SnapshotCapturingProfilerController doNewController(ScenarioSettings settings) { + public SnapshotCapturingProfilerController newSnapshottingController(ScenarioSettings settings) { return new YourKitProfilerController(yourKitConfig); } diff --git a/src/test/groovy/org/gradle/profiler/ProfilerIntegrationTest.groovy b/src/test/groovy/org/gradle/profiler/ProfilerIntegrationTest.groovy index fc9bc9ae..d23580c6 100644 --- a/src/test/groovy/org/gradle/profiler/ProfilerIntegrationTest.groovy +++ b/src/test/groovy/org/gradle/profiler/ProfilerIntegrationTest.groovy @@ -1173,10 +1173,10 @@ class ProfilerIntegrationTest extends AbstractProfilerIntegrationTest { new Main().run("--project-dir", projectDir.absolutePath, "--output-dir", outputDir.absolutePath, "--buck", "--profile", "jfr", "--scenario-file", scenarios.absolutePath, "--gradle-version", minimalSupportedGradleVersion, "buildTarget") then: - thrown(IllegalArgumentException) + thrown(Exception) and: - output.contains("Can only profile scenario 'buildTarget' when building using Gradle.") + output.contains("Profiling is not supported for Buck builds") } def "can profile a scenario that contains buck build instructions when building with Gradle"() { @@ -1272,10 +1272,10 @@ class ProfilerIntegrationTest extends AbstractProfilerIntegrationTest { new Main().run("--project-dir", projectDir.absolutePath, "--output-dir", outputDir.absolutePath, "--bazel", "--profile", "jfr", "--scenario-file", scenarios.absolutePath, "--gradle-version", minimalSupportedGradleVersion, "buildTarget") then: - thrown(IllegalArgumentException) + thrown(Exception) and: - output.contains("Can only profile scenario 'buildTarget' when building using Gradle.") + output.contains("Profiling is not supported for Bazel builds") } def "can profile a scenario that contains bazel build instructions when building with Gradle"() {