├── settings.gradle.kts
├── demo.gif
├── .gitignore
├── src
└── main
│ ├── java
│ └── ru
│ │ └── artyushov
│ │ └── jmhPlugin
│ │ ├── action
│ │ ├── GenerateMicroBenchmarkAction.java
│ │ ├── BenchmarkMethodTemplateFactory.java
│ │ └── BenchmarkMethodHandler.java
│ │ ├── configuration
│ │ ├── JmhConfigurable.java
│ │ ├── JmhConfigurationType.java
│ │ ├── JmhEntryPoint.java
│ │ ├── JmhRunLineMarkerContributor.java
│ │ ├── JmhSettingsEditor.java
│ │ ├── ConfigurationUtils.java
│ │ ├── JmhBenchmarkCommandLineState.java
│ │ ├── JmhConfigurationProducer.java
│ │ └── JmhConfiguration.java
│ │ └── inspection
│ │ └── JmhInspections.java
│ └── resources
│ ├── inspectionDescriptions
│ └── JmhInspections.html
│ └── META-INF
│ ├── pluginIcon.svg
│ └── plugin.xml
├── .run
└── JMH-Plugin.run.xml
├── idea-jmh-plugin.iml
├── LICENSE.txt
├── research
└── results.md
└── README.md
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | rootProject.name = "idea-jmh-plugin"
--------------------------------------------------------------------------------
/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/artyushov/idea-jmh-plugin/HEAD/demo.gif
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.class
2 | out
3 |
4 | # Package Files #
5 | *.jar
6 | *.war
7 | *.ear
8 | .idea/*
9 | sandbox
10 | build
11 | .gradle
12 | /gradle
13 | /gradlew
14 | /gradlew.bat
15 |
--------------------------------------------------------------------------------
/src/main/java/ru/artyushov/jmhPlugin/action/GenerateMicroBenchmarkAction.java:
--------------------------------------------------------------------------------
1 | package ru.artyushov.jmhPlugin.action;
2 |
3 | import com.intellij.codeInsight.generation.actions.BaseGenerateAction;
4 |
5 | /**
6 | * User: nikart
7 | * Date: 10/03/14
8 | * Time: 16:43
9 | */
10 | public class GenerateMicroBenchmarkAction extends BaseGenerateAction {
11 |
12 | public GenerateMicroBenchmarkAction() {
13 | super(new BenchmarkMethodHandler());
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/.run/JMH-Plugin.run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/main/resources/inspectionDescriptions/JmhInspections.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Check that JMH benchmark has a proper structure.
4 |
5 |
6 |
7 | @Benchmark, @Setup, @TearDown methods should be public
8 | @Setup, @TearDown should be void and not return anything
9 | - Benchmark class should have package other than default
10 | - Benchmark class should not be final
11 | - @State annotation is missing
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/idea-jmh-plugin.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Nikita Artyushov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/main/java/ru/artyushov/jmhPlugin/action/BenchmarkMethodTemplateFactory.java:
--------------------------------------------------------------------------------
1 | package ru.artyushov.jmhPlugin.action;
2 |
3 | import com.intellij.codeInsight.template.Expression;
4 | import com.intellij.codeInsight.template.Template;
5 | import com.intellij.codeInsight.template.TemplateManager;
6 | import com.intellij.codeInsight.template.impl.ConstantNode;
7 | import com.intellij.psi.PsiClass;
8 |
9 | import static ru.artyushov.jmhPlugin.configuration.ConfigurationUtils.JMH_ANNOTATION_NAME;
10 |
11 | /**
12 | * User: nikart
13 | * Date: 18/03/14
14 | * Time: 13:01
15 | */
16 | public class BenchmarkMethodTemplateFactory {
17 |
18 | public static Template create(PsiClass psiClass) {
19 | Template template = TemplateManager.getInstance(psiClass.getProject()).createTemplate("", "");
20 | template.addTextSegment("@" + JMH_ANNOTATION_NAME + "\n");
21 | template.addTextSegment("public void measure");
22 | Expression nameExpr = new ConstantNode("Name");
23 | template.addVariable("name", nameExpr, nameExpr, true);
24 | template.addTextSegment("(org.openjdk.jmh.infra.Blackhole bh) {\n}");
25 |
26 | template.setToIndent(true);
27 | template.setToReformat(true);
28 | template.setToShortenLongNames(true);
29 |
30 | return template;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/ru/artyushov/jmhPlugin/configuration/JmhConfigurable.java:
--------------------------------------------------------------------------------
1 | package ru.artyushov.jmhPlugin.configuration;
2 |
3 | import com.intellij.execution.ui.CommonJavaParametersPanel;
4 | import com.intellij.openapi.options.ConfigurationException;
5 | import com.intellij.openapi.options.SettingsEditor;
6 | import org.jetbrains.annotations.NotNull;
7 |
8 | import javax.swing.*;
9 |
10 | /**
11 | * User: nikart
12 | * Date: 01/05/14
13 | * Time: 12:55
14 | */
15 | public class JmhConfigurable extends SettingsEditor {
16 |
17 | private JPanel editor = new JPanel();
18 |
19 | private final CommonJavaParametersPanel commonProgramParameters;
20 |
21 | public JmhConfigurable() {
22 | editor.setLayout(new BoxLayout(editor, BoxLayout.X_AXIS));
23 | commonProgramParameters = new CommonJavaParametersPanel();
24 | editor.add(commonProgramParameters);
25 | }
26 |
27 | @Override
28 | protected void resetEditorFrom(@NotNull JmhConfiguration jmhConfiguration) {
29 | commonProgramParameters.reset(jmhConfiguration);
30 | }
31 |
32 | @Override
33 | protected void applyEditorTo(@NotNull JmhConfiguration jmhConfiguration) throws ConfigurationException {
34 | commonProgramParameters.applyTo(jmhConfiguration);
35 | }
36 |
37 | @NotNull
38 | @Override
39 | protected JComponent createEditor() {
40 |
41 | return editor;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/ru/artyushov/jmhPlugin/configuration/JmhConfigurationType.java:
--------------------------------------------------------------------------------
1 | package ru.artyushov.jmhPlugin.configuration;
2 |
3 | import com.intellij.execution.configurations.ConfigurationTypeUtil;
4 | import com.intellij.execution.configurations.JavaRunConfigurationModule;
5 | import com.intellij.execution.configurations.RunConfiguration;
6 | import com.intellij.execution.configurations.SimpleConfigurationType;
7 | import com.intellij.openapi.project.Project;
8 | import com.intellij.openapi.util.NotNullLazyValue;
9 | import org.jetbrains.annotations.NotNull;
10 |
11 | import static com.intellij.icons.AllIcons.Actions.ProfileYellow;
12 |
13 | /**
14 | * User: nikart
15 | * Date: 01/05/14
16 | * Time: 13:46
17 | */
18 | public class JmhConfigurationType extends SimpleConfigurationType {
19 |
20 | public static final String TYPE_ID = "jmh-id";
21 |
22 | public JmhConfigurationType() {
23 | super(TYPE_ID, "Jmh", "Configuration to run a JMH benchmark", NotNullLazyValue.createValue(() -> ProfileYellow));
24 | }
25 |
26 | @Override
27 | public @NotNull RunConfiguration createTemplateConfiguration(@NotNull Project project) {
28 | JmhConfiguration configuration = new JmhConfiguration("jmh-configuration-name", new JavaRunConfigurationModule(project, false), this);
29 | configuration.setPassParentEnvs(System.getProperty("os.name").startsWith("Windows"));
30 | return configuration;
31 | }
32 |
33 | @NotNull
34 | public static JmhConfigurationType getInstance() {
35 | return ConfigurationTypeUtil.findConfigurationType(JmhConfigurationType.class);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/pluginIcon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/java/ru/artyushov/jmhPlugin/configuration/JmhEntryPoint.java:
--------------------------------------------------------------------------------
1 | package ru.artyushov.jmhPlugin.configuration;
2 |
3 | import com.intellij.codeInspection.reference.EntryPoint;
4 | import com.intellij.codeInspection.reference.RefElement;
5 | import com.intellij.openapi.util.InvalidDataException;
6 | import com.intellij.openapi.util.WriteExternalException;
7 | import com.intellij.psi.PsiElement;
8 | import com.intellij.psi.PsiMethod;
9 | import org.jdom.Element;
10 | import org.jetbrains.annotations.NotNull;
11 |
12 | import static ru.artyushov.jmhPlugin.configuration.ConfigurationUtils.hasSetupOrTearDownAnnotation;
13 | import static ru.artyushov.jmhPlugin.configuration.ConfigurationUtils.isBenchmarkEntryElement;
14 |
15 | /**
16 | * User: nikart
17 | * Date: 07/08/14
18 | * Time: 23:33
19 | */
20 | public class JmhEntryPoint extends EntryPoint {
21 |
22 | private boolean isSelected = true;
23 |
24 | @NotNull
25 | @Override
26 | public String getDisplayName() {
27 | return "jmhEntryPoint";
28 | }
29 |
30 | @Override
31 | public boolean isEntryPoint(@NotNull RefElement refElement, @NotNull PsiElement psiElement) {
32 | return isEntryPoint(psiElement);
33 | }
34 |
35 | @Override
36 | public boolean isEntryPoint(@NotNull PsiElement psiElement) {
37 | if (isSelected) {
38 | return isBenchmarkEntryElement(psiElement)
39 | || ((psiElement instanceof PsiMethod) && hasSetupOrTearDownAnnotation((PsiMethod) psiElement));
40 | }
41 | return false;
42 | }
43 |
44 | @Override
45 | public boolean isSelected() {
46 | return isSelected;
47 | }
48 |
49 | @Override
50 | public void setSelected(boolean isSelected) {
51 | this.isSelected = isSelected;
52 | }
53 |
54 | @Override
55 | public void readExternal(Element element) throws InvalidDataException {
56 | isSelected = Boolean.parseBoolean(element.getAttributeValue("isSelected"));
57 | }
58 |
59 | @Override
60 | public void writeExternal(Element element) throws WriteExternalException {
61 | element.setAttribute("isSelected", isSelected + "");
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/java/ru/artyushov/jmhPlugin/configuration/JmhRunLineMarkerContributor.java:
--------------------------------------------------------------------------------
1 | package ru.artyushov.jmhPlugin.configuration;
2 |
3 | import com.intellij.execution.lineMarker.ExecutorAction;
4 | import com.intellij.execution.lineMarker.RunLineMarkerContributor;
5 | import com.intellij.openapi.actionSystem.AnAction;
6 | import com.intellij.psi.PsiElement;
7 | import org.jetbrains.annotations.NotNull;
8 | import org.jetbrains.annotations.Nullable;
9 | import org.jetbrains.uast.UClass;
10 | import org.jetbrains.uast.UElement;
11 | import org.jetbrains.uast.UMethod;
12 | import org.jetbrains.uast.UastUtils;
13 |
14 | import static com.intellij.icons.AllIcons.Actions.ProfileYellow;
15 | import static ru.artyushov.jmhPlugin.configuration.ConfigurationUtils.isBenchmarkClass;
16 | import static ru.artyushov.jmhPlugin.configuration.ConfigurationUtils.isBenchmarkMethod;
17 |
18 | /**
19 | * @author Sergey Sitnikov
20 | */
21 | public class JmhRunLineMarkerContributor extends RunLineMarkerContributor {
22 |
23 | @Nullable
24 | @Override
25 | public Info getInfo(@NotNull PsiElement psiElement) {
26 | UElement uElement = UastUtils.getUParentForIdentifier(psiElement);
27 | if (uElement instanceof UMethod) {
28 | boolean isBenchmarkMethod = isBenchmarkMethod((UMethod) uElement);
29 | if (isBenchmarkMethod) {
30 | // FIXME use something similar to com.intellij.sh.run.ShRunFileAction
31 | // FIXME for some reason this doesn't work anymore https://github.com/artyushov/idea-jmh-plugin/issues/50
32 | // final AnAction[] actions = new AnAction[]{ActionManager.getInstance().getAction("RunClass")};
33 | AnAction[] actions = ExecutorAction.getActions(1);
34 | return new Info(ProfileYellow, actions, null);
35 | }
36 | } else if (uElement instanceof UClass) {
37 | boolean isBenchmarkClass = isBenchmarkClass((UClass) uElement);
38 | if (isBenchmarkClass) {
39 | // FIXME use something similar to com.intellij.sh.run.ShRunFileAction
40 | // FIXME for some reason this doesn't work anymore https://github.com/artyushov/idea-jmh-plugin/issues/50
41 | // final AnAction[] actions = new AnAction[]{ActionManager.getInstance().getAction("RunClass")};
42 | AnAction[] actions = ExecutorAction.getActions(1);
43 | return new Info(ProfileYellow, actions, null);
44 | }
45 | }
46 | return null;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/ru/artyushov/jmhPlugin/configuration/JmhSettingsEditor.java:
--------------------------------------------------------------------------------
1 | // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 | package ru.artyushov.jmhPlugin.configuration;
3 |
4 | import com.intellij.execution.ExecutionBundle;
5 | import com.intellij.execution.application.JavaSettingsEditorBase;
6 | import com.intellij.execution.ui.*;
7 | import com.intellij.openapi.application.ReadAction;
8 | import com.intellij.openapi.ui.LabeledComponent;
9 | import com.intellij.openapi.wm.IdeFocusManager;
10 | import com.intellij.psi.PsiJavaModule;
11 | import com.intellij.psi.search.FilenameIndex;
12 | import com.intellij.psi.search.GlobalSearchScope;
13 | import com.intellij.util.concurrency.NonUrgentExecutor;
14 |
15 | import javax.swing.*;
16 | import java.awt.*;
17 | import java.util.List;
18 | import java.util.Locale;
19 | import java.util.function.Supplier;
20 |
21 | public class JmhSettingsEditor extends JavaSettingsEditorBase {
22 |
23 | public JmhSettingsEditor(JmhConfiguration runConfiguration) {
24 | super(runConfiguration);
25 | }
26 |
27 | @Override
28 | protected void customizeFragments(List> fragments,
29 | SettingsEditorFragment moduleClasspath,
30 | CommonParameterFragments commonParameterFragments) {
31 | removeFragment(fragments, "runParallel");
32 |
33 | DefaultJreSelector jreSelector = DefaultJreSelector.fromModuleDependencies(moduleClasspath.component(), false);
34 | SettingsEditorFragment jrePath = CommonJavaFragments.createJrePath(jreSelector);
35 | fragments.add(jrePath);
36 | fragments.add(createShortenClasspath(moduleClasspath.component(), jrePath, false));
37 | fragments.add(commonParameterFragments.programArguments());
38 | if (!getProject().isDefault()) {
39 | SettingsEditorFragment fragment =
40 | SettingsEditorFragment.createTag("test.use.module.path",
41 | ExecutionBundle.message("do.not.use.module.path.tag"),
42 | ExecutionBundle.message("group.java.options"),
43 | configuration -> !configuration.isUseModulePath(),
44 | (configuration, value) -> configuration.setUseModulePath(!value));
45 | fragments.add(fragment);
46 | ReadAction.nonBlocking(() -> {
47 | GlobalSearchScope projectScope = GlobalSearchScope.projectScope(getProject());
48 | boolean noAnyModuleInfoFiles = FilenameIndex.getVirtualFilesByName(PsiJavaModule.MODULE_INFO_FILE, projectScope).isEmpty();
49 | fragment.setRemovable(noAnyModuleInfoFiles);
50 | }).expireWith(fragment).submit(NonUrgentExecutor.getInstance());
51 | }
52 | }
53 |
54 | @Override
55 | public boolean isInplaceValidationSupported() {
56 | return true;
57 | }
58 |
59 | private static void removeFragment(List> fragments, String fragmentId) {
60 | for (int i = 0; i < fragments.size(); i++) {
61 | if (fragments.get(i).getId().equals(fragmentId)) {
62 | fragments.remove(i);
63 | break;
64 | }
65 | }
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/research/results.md:
--------------------------------------------------------------------------------
1 | ### 1. [JMHSample_01_HelloWorld](https://github.com/openjdk/jmh/blob/master/jmh-samples/src/main/java/org/openjdk/jmh/samples/JMHSample_01_HelloWorld.java)
2 |
3 | Running with the following parameters: `-f 10 -wi 10 -i 20 -tu us`
4 | IDEA results:
5 | `average = 3061.918, stdev: 74.80403`
6 | Command line results:
7 | `average: 3111.455, stdev: 77.44337`
8 | As we see, the difference in means is just 1.5%. Let's check that these distributions are not completely different.
9 | After subtracting mean from each sample and running [Kolmogorov-Smirnov](http://en.wikipedia.org/wiki/Kolmogorov–Smirnov_test)
10 | test against them we do not reject the distribution equality hypothesis on significance level **0.05**.
11 |
12 | Other benchmarks were not analysed as thoroughly, so I'll just present the means.
13 |
14 | ### 2. [JMHSample_02_BenchmarkModes](https://github.com/openjdk/jmh/blob/master/jmh-samples/src/main/java/org/openjdk/jmh/samples/JMHSample_02_BenchmarkModes.java)
15 |
16 | ```
17 | Benchmark Mode Samples Score-Idea Score-Term (MAX - MIN)/MAX Units
18 | measureAll thrpt 5 0.000 0.000 - ops/us
19 | measureMultiple thrpt 5 0.000 0.000 - ops/us
20 | measureThroughput thrpt 5 9.969 9.926 0.004 ops/s
21 | measureAll avgt 5 100201.473 100864.473 0.006 us/op
22 | measureAvgTime avgt 5 100228.545 100779.818 0.005 us/op
23 | measureMultiple avgt 5 100257.764 100841.600 0.005 us/op
24 | measureAll sample 55 100086.579 100622.783 0.005 us/op
25 | measureMultiple sample 55 100117.560 100722.874 0.006 us/op
26 | measureSamples sample 55 100057.982 100684.744 0.006 us/op
27 | measureAll ss 5 100190.600 101086.000 0.008 us
28 | measureMultiple ss 5 100101.800 101094.600 0.009 us
29 | measureSingleShot ss 5 100184.000 100536.200 0.003 us
30 | ```
31 |
32 | ### 3. [JMHSample_03_States](https://github.com/openjdk/jmh/blob/master/jmh-samples/src/main/java/org/openjdk/jmh/samples/JMHSample_03_States.java)
33 | blob/master/
34 | ```
35 | Benchmark Mode Samples Score-Idea Score-Term (MAX - MIN)/MAX Units
36 | measureShared thrpt 25 644535828.753 647525817.305 0.004 ops/s
37 | measureUnshared thrpt 25 1276415578.342 1296907288.469 0.015 ops/s
38 | ```
39 |
40 | ### 4. [JMHSample_04_DefaultState](https://github.com/openjdk/jmh/blob/master/jmh-samples/src/main/java/org/openjdk/jmh/samples/JMHSample_04_DefaultState.java)
41 |
42 | ```
43 | Benchmark Mode Samples Score-Idea Score-Term (MAX - MIN)/MAX Units
44 | measure thrpt 25 341919594.645 344548681.629 0.007 ops/s
45 | ```
46 |
47 | ### 5. [JMHSample_05_StateFixtures](https://github.com/openjdk/jmh/blob/master/jmh-samples/src/main/java/org/openjdk/jmh/samples/JMHSample_05_StateFixtures.java)
48 |
49 | ```
50 | Benchmark Mode Samples Score-Idea Score-Term (MAX - MIN)/MAX Units
51 | measureRight thrpt 25 341193314.735 343132773.161 0.005 ops/s
52 | measureWrong thrpt 25 3008081495.542 3077264376.682 0.022 ops/s
53 | ```
54 | ### You may continue this list, if you want :)
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Intellij IDEA plugin for Java Microbenchmark Harness (JMH)
2 |
3 | This is a plugin that allows you to use [JMH](https://github.com/openjdk/jmh) in the same way as
4 | JUnit. Here are the features that are already implemented:
5 |
6 | 1. `@Benchmark` method generation
7 | 2. Running a separate `@Benchmark` method
8 | 3. Running all the benchmarks in a class
9 |
10 | 
11 |
12 | ## How do I use this?
13 |
14 | First of all, you must have `jmh-core` and `jmh-generator-annprocess` on the classpath of your module.
15 |
16 | After that install the plugin. You can do this directly from IDEA — search for `JMH` in plugin repositories.
17 |
18 | Then you can use the plugin the same way you use JUnit. To generate a new benchmark method run `Generate...` action.
19 | Press `Alt+Insert` or in MacOS `Ctrl + N`.
20 | Or just right click in your editor pane and select `Generate micro benchmark`.
21 |
22 | To run a separate benchmark method move the cursor to the method declaration and invoke `Run` action.
23 | Press `Ctrl + Shift + F10`.
24 | Do the same actions to run all the benchmarks in a class, just move your cursor to the class declaration.
25 |
26 | Invoking `Run` actions will create a new configuration with default parameters JMH provides.
27 | If you want to change these parameters just edit this configuration.
28 | To edit default parameters for all your benchmarks, modify the "JMH" run configuration template.
29 |
30 | Please, note that when running a benchmark Annotation processing must be enabled in your IDE.
31 |
32 | ### Doesn't it affect the quality of my benchmarks?
33 |
34 | A brief research shows that benchmark results *are* affected, but not that much. The whole research is described in
35 | [Research results](./research/results.md). Long story short, the maximum means difference observed was **2.2%**.
36 |
37 | ### Run Configuration
38 |
39 | The following screenshot configures JMH to run the benchmark [`org.openjdk.bench.java.util.UUIDBench.fromString`](https://github.com/openjdk/jdk/blob/master/test/micro/org/openjdk/bench/java/util/UUIDBench.java) using the [async-profiler](https://github.com/jvm-profiling-tools/async-profiler) from a customized `java.library.path`
40 |
41 | 
42 |
43 | ## Common problems
44 |
45 | Under Windows the following error might show up:
46 |
47 | ERROR: org.openjdk.jmh.runner.RunnerException:
48 | ERROR: Exception while trying to acquire the JMH lock (C:\WINDOWS\/jmh.lock):
49 |
50 | This is caused by running JMH benchmarks with an empty environment.
51 | To fix this error, define a `TMP` or `TEMP` environment variable which points to a writable directory.
52 | Alternatively, specify the JVM argument `java.io.tmpdir` and set it to a writable directory, for instance `-Djava.io.tmpdir=C:\temp`.
53 |
54 | ## Develop
55 | To understand the plugin sources please read
56 | * [Run Configurations Architectural Overview](https://jetbrains.org/intellij/sdk/docs/basics/run_configurations.html)
57 | * [Run Configuration Management](https://jetbrains.org/intellij/sdk/docs/basics/run_configurations/run_configuration_management.html)
58 |
59 | ## Related projects
60 |
61 | - [Gradle JMH Plugin](https://github.com/melix/jmh-gradle-plugin)
62 | - [Jenkins JMH Plugin](https://github.com/brianfromoregon/jmh-plugin)
63 | - [Teamcity JMH Plugin](https://github.com/presidentio/teamcity-plugin-jmh)
64 | - [IdeaJOL](https://github.com/stokito/IdeaJol) - Intellij IDEA plugin for [OpenJDK JOL](https://github.com/openjdk/jol/) (Java Object Layout) is the tool to analyze object layout in JVMs.
65 |
66 | ## Plugin on JetBrains Marketplace
67 |
68 | Please rate the plugin on [Marketplace](https://plugins.jetbrains.com/plugin/7529-jmh-java-microbenchmark-harness)
--------------------------------------------------------------------------------
/src/main/resources/META-INF/plugin.xml:
--------------------------------------------------------------------------------
1 |
2 | ru.artyushov
3 | JMH Java Microbenchmark Harness
4 | 1.6.0
5 | Nikita Artyushov
6 | Profiling
7 |
8 | Plugin for generating and running JMH benchmarks from your IDE
10 | Java Microbenchmark Harness (JMH) is an OpenJDK tool for building, running, and analysing low level benchmarks.
11 | Note: First, you must generate your own bench project from the JMH artifact and only then you can use the plugin
12 | Source code on GitHub
13 | ]]>
14 |
15 |
17 | v1.6.0 Add the "Program Arguments" option of the run configuration. Thanks to Luca Molteni @lucamolteni
18 | v1.5.0 Fix Empty run configuration for IDEA 2024.1.1
19 | v1.4.0 Remove usage of a deprecated API
20 | v1.3.0
21 |
22 | - Change gutter icons
23 | - Fixed an inspection error for Kotlin methods
24 |
25 |
26 | v1.2 Migrate to UAST
27 |
28 | - Basic Kotlin support
29 | - Code inspections for basic benchmarks errors
30 | - Rename benchmark support
31 |
32 |
33 | v1.1
34 |
35 | - Enable setting JVM options in runtime configuration
36 | - Add gutter icons for benchmark methods and containing class
37 | - Workaround for "Exception while trying to acquire the JMH lock (C:\WINDOWS\/jmh.lock)"
38 | - Ask user to enable annotation processor
39 | - Support of Modern IntelliJ, Android Studio and change icon for run configuration
40 | - Thanks to Sergey Sitnikov, Michal Vala, Daniel Knittl-Frank, joserobjr and Beck Chen for contribution
41 |
43 |
46 |
47 |
48 | com.intellij.modules.java
49 |
50 |
51 |
52 |
53 |
54 |
55 |
63 |
64 |
65 |
66 |
69 |
70 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/src/main/java/ru/artyushov/jmhPlugin/configuration/ConfigurationUtils.java:
--------------------------------------------------------------------------------
1 | package ru.artyushov.jmhPlugin.configuration;
2 |
3 | import com.intellij.psi.PsiClass;
4 | import com.intellij.psi.PsiElement;
5 | import com.intellij.psi.PsiMethod;
6 | import org.jetbrains.annotations.NotNull;
7 | import org.jetbrains.annotations.Nullable;
8 | import org.jetbrains.uast.UClass;
9 | import org.jetbrains.uast.UMethod;
10 |
11 | import static com.intellij.psi.PsiModifier.PUBLIC;
12 | import static org.jetbrains.uast.UastUtils.findContaining;
13 |
14 | /**
15 | * User: nikart
16 | * Date: 16/07/14
17 | * Time: 00:09
18 | */
19 | public class ConfigurationUtils {
20 |
21 | public static final String SETUP_ANNOTATION = "org.openjdk.jmh.annotations.Setup";
22 | public static final String TEAR_DOWN_ANNOTATION = "org.openjdk.jmh.annotations.TearDown";
23 | public static final String JMH_ANNOTATION_NAME = "org.openjdk.jmh.annotations.Benchmark";
24 | public static final String JMH_ANNOTATION_STATE = "org.openjdk.jmh.annotations.State";
25 |
26 | public static boolean hasStateAnnotation(@NotNull UClass aClass) {
27 | return aClass.hasAnnotation(JMH_ANNOTATION_STATE);
28 | }
29 |
30 | public static boolean hasBenchmarkAnnotation(@NotNull PsiMethod method) {
31 | return method.hasAnnotation(JMH_ANNOTATION_NAME);
32 | }
33 |
34 | public static boolean hasBenchmarkAnnotation(@NotNull UMethod method) {
35 | return method.hasAnnotation(JMH_ANNOTATION_NAME);
36 | }
37 |
38 | public static boolean hasSetupOrTearDownAnnotation(@NotNull PsiMethod method) {
39 | return method.hasAnnotation(SETUP_ANNOTATION) ||
40 | method.hasAnnotation(TEAR_DOWN_ANNOTATION);
41 | }
42 |
43 | public static boolean hasSetupOrTearDownAnnotation(@NotNull UMethod method) {
44 | return method.hasAnnotation(SETUP_ANNOTATION) ||
45 | method.hasAnnotation(TEAR_DOWN_ANNOTATION);
46 | }
47 |
48 | public static boolean isBenchmarkEntryElement(PsiElement element) {
49 | return element instanceof PsiMethod && hasBenchmarkAnnotation((PsiMethod) element)
50 | || (element instanceof PsiClass && isBenchmarkClass((PsiClass) element));
51 | }
52 |
53 | public static boolean isBenchmarkMethod(@NotNull PsiMethod method) {
54 | return method.hasModifierProperty(PUBLIC) && hasBenchmarkAnnotation(method);
55 | }
56 |
57 | public static boolean isBenchmarkMethod(@NotNull UMethod method) {
58 | return method.hasModifierProperty(PUBLIC) && hasBenchmarkAnnotation(method);
59 | }
60 |
61 | public static boolean isBenchmarkClass(@NotNull PsiClass aClass) {
62 | final PsiMethod[] methods = aClass.getMethods();
63 | for (final PsiMethod method : methods) {
64 | if (isBenchmarkMethod(method)) return true;
65 | }
66 | return false;
67 | }
68 |
69 | public static boolean isBenchmarkClass(@NotNull UClass aClass) {
70 | final UMethod[] methods = aClass.getMethods();
71 | for (final UMethod method : methods) {
72 | if (isBenchmarkMethod(method)) return true;
73 | }
74 | return false;
75 | }
76 |
77 | @Nullable
78 | static PsiElement findBenchmarkEntry(PsiElement locationElement) {
79 | UMethod method = findContaining(locationElement, UMethod.class);
80 | if (method != null && isBenchmarkMethod(method)) {
81 | return method;
82 | }
83 | UClass klass = findContaining(locationElement, UClass.class);
84 | if (klass != null && isBenchmarkClass(klass)) {
85 | return klass;
86 | }
87 | return null;
88 | }
89 |
90 | @NotNull
91 | public static String toRunParams(@NotNull PsiElement benchmarkEntry, boolean fqn) {
92 | if (benchmarkEntry instanceof PsiMethod) {
93 | PsiMethod benchmarkMethod = (PsiMethod) benchmarkEntry;
94 | PsiClass benchmarkClass = benchmarkMethod.getContainingClass();
95 | assert benchmarkClass != null;
96 | String benchmarkClassName = fqn ? benchmarkClass.getQualifiedName() : benchmarkClass.getName();
97 | return benchmarkClassName + '.' + benchmarkMethod.getName() + "$";
98 | } else if (benchmarkEntry instanceof PsiClass) {
99 | PsiClass benchmarkClass = (PsiClass) benchmarkEntry;
100 | String benchmarkClassName = fqn ? benchmarkClass.getQualifiedName() : benchmarkClass.getName();
101 | return benchmarkClassName + ".*";
102 | } else {
103 | return "";
104 | }
105 | }
106 |
107 | }
108 |
--------------------------------------------------------------------------------
/src/main/java/ru/artyushov/jmhPlugin/configuration/JmhBenchmarkCommandLineState.java:
--------------------------------------------------------------------------------
1 | package ru.artyushov.jmhPlugin.configuration;
2 |
3 | import com.intellij.compiler.CompilerConfiguration;
4 | import com.intellij.compiler.CompilerConfigurationImpl;
5 | import com.intellij.execution.ExecutionException;
6 | import com.intellij.execution.application.BaseJavaApplicationCommandLineState;
7 | import com.intellij.execution.configurations.JavaParameters;
8 | import com.intellij.execution.runners.ExecutionEnvironment;
9 | import com.intellij.execution.util.JavaParametersUtil;
10 | import com.intellij.openapi.compiler.CompileStatusNotification;
11 | import com.intellij.openapi.compiler.CompilerManager;
12 | import com.intellij.openapi.module.Module;
13 | import com.intellij.openapi.projectRoots.Sdk;
14 | import com.intellij.openapi.roots.ModuleRootManager;
15 | import com.intellij.openapi.roots.ProjectRootManager;
16 | import com.intellij.openapi.ui.Messages;
17 | import org.jetbrains.annotations.NotNull;
18 | import org.jetbrains.annotations.Nullable;
19 | import org.jetbrains.jps.model.java.compiler.ProcessorConfigProfile;
20 |
21 | import static com.intellij.execution.configurations.JavaParameters.CLASSES_AND_TESTS;
22 | import static com.intellij.execution.configurations.JavaParameters.CLASSES_ONLY;
23 | import static com.intellij.execution.configurations.JavaParameters.JDK_AND_CLASSES;
24 | import static com.intellij.execution.configurations.JavaParameters.JDK_AND_CLASSES_AND_TESTS;
25 | import static com.intellij.openapi.ui.Messages.showOkCancelDialog;
26 |
27 | /**
28 | * User: nikart
29 | * Date: 14/07/14
30 | * Time: 21:36
31 | */
32 | public class JmhBenchmarkCommandLineState extends BaseJavaApplicationCommandLineState {
33 |
34 | public JmhBenchmarkCommandLineState(final ExecutionEnvironment environment, @NotNull final JmhConfiguration configuration) {
35 | super(environment, configuration);
36 | automaticallyEnableAnnotationProcessor(configuration);
37 | }
38 |
39 | private void automaticallyEnableAnnotationProcessor(JmhConfiguration configuration) {
40 | Module module = configuration.getConfigurationModule().getModule();
41 | if (module != null) {
42 | CompilerConfigurationImpl compilerConfiguration =
43 | (CompilerConfigurationImpl) CompilerConfiguration.getInstance(module.getProject());
44 | ProcessorConfigProfile processorConfigProfile = compilerConfiguration.getAnnotationProcessingConfiguration(module);
45 | if (!processorConfigProfile.isEnabled()) {
46 | if (showOkCancelDialog(configuration.getProject(),
47 | "Annotation processing is not enabled but JMH needs to preprocess classes bytecode",
48 | "JMH benchmark should be processed by its Annotation Processor",
49 | "Enable Annotation processing and rebuild", "Skip",
50 | Messages.getQuestionIcon()) != Messages.OK) {
51 | return;
52 | }
53 | processorConfigProfile.setEnabled(true);
54 | // refresh compilerConfiguration
55 | compilerConfiguration.getState();
56 | rebuild(module);
57 | }
58 | }
59 | }
60 |
61 | private void rebuild(Module module) {
62 | @Nullable CompileStatusNotification notification = (aborted, errors, warnings, compileContext) -> {};
63 | CompilerManager.getInstance(module.getProject()).rebuild(notification);
64 | }
65 |
66 | @Override
67 | protected JavaParameters createJavaParameters() throws ExecutionException {
68 | JavaParameters parameters = new JavaParameters();
69 | JavaParametersUtil.configureConfiguration(parameters, myConfiguration);
70 |
71 | parameters.setMainClass(JmhConfiguration.JMH_START_CLASS);
72 |
73 | int classPathType = removeJdkClasspath(JavaParametersUtil.getClasspathType(myConfiguration.getConfigurationModule(),
74 | myConfiguration.getBenchmarkClass(), true));
75 | JavaParametersUtil.configureModule(myConfiguration.getConfigurationModule(), parameters, classPathType, null);
76 |
77 | Module module = myConfiguration.getConfigurationModule().getModule();
78 | if (parameters.getJdk() == null){
79 | Sdk jdk = module != null
80 | ? ModuleRootManager.getInstance(module).getSdk()
81 | : ProjectRootManager.getInstance(myConfiguration.getProject()).getProjectSdk();
82 | parameters.setJdk(jdk);
83 | }
84 | return parameters;
85 | }
86 |
87 | private int removeJdkClasspath(int classpathType) {
88 | switch (classpathType) {
89 | case JDK_AND_CLASSES:
90 | return CLASSES_ONLY;
91 | case JDK_AND_CLASSES_AND_TESTS:
92 | return CLASSES_AND_TESTS;
93 | default:
94 | return classpathType;
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/main/java/ru/artyushov/jmhPlugin/action/BenchmarkMethodHandler.java:
--------------------------------------------------------------------------------
1 | package ru.artyushov.jmhPlugin.action;
2 |
3 | import com.intellij.codeInsight.CodeInsightActionHandler;
4 | import com.intellij.codeInsight.CodeInsightUtilCore;
5 | import com.intellij.codeInsight.daemon.impl.quickfix.CreateFromUsageUtils;
6 | import com.intellij.codeInsight.generation.GenerateMembersUtil;
7 | import com.intellij.codeInsight.generation.OverrideImplementUtil;
8 | import com.intellij.codeInsight.generation.PsiGenerationInfo;
9 | import com.intellij.codeInsight.template.Template;
10 | import com.intellij.codeInsight.template.TemplateEditingAdapter;
11 | import com.intellij.codeInsight.template.TemplateManager;
12 | import com.intellij.openapi.application.ApplicationManager;
13 | import com.intellij.openapi.command.CommandProcessor;
14 | import com.intellij.openapi.command.WriteCommandAction;
15 | import com.intellij.openapi.editor.Editor;
16 | import com.intellij.openapi.editor.actionSystem.DocCommandGroupId;
17 | import com.intellij.openapi.project.Project;
18 | import com.intellij.openapi.util.TextRange;
19 | import com.intellij.psi.*;
20 | import com.intellij.psi.util.PsiTreeUtil;
21 | import com.intellij.testIntegration.TestIntegrationUtils;
22 | import com.intellij.util.IncorrectOperationException;
23 | import org.jetbrains.annotations.NotNull;
24 |
25 | import java.util.Collections;
26 |
27 | /**
28 | * Author: artyushov
29 | * Date: 2016-04-16 00:16
30 | */
31 | class BenchmarkMethodHandler implements CodeInsightActionHandler {
32 |
33 | @Override
34 | public void invoke(@NotNull Project project, @NotNull final Editor editor, @NotNull final PsiFile file) {
35 | PsiClass targetClass = findTargetClass(editor, file);
36 | if (targetClass == null) {
37 | return;
38 | }
39 | generate(editor, file, targetClass);
40 | }
41 |
42 | @Override
43 | public boolean startInWriteAction() {
44 | return false;
45 | }
46 |
47 | private void generate(final Editor editor, final PsiFile file, final PsiClass targetClass) {
48 |
49 | WriteCommandAction.runWriteCommandAction(file.getProject(), new Runnable() {
50 | @Override
51 | public void run() {
52 | PsiDocumentManager.getInstance(file.getProject()).commitAllDocuments();
53 | PsiMethod method = generateDummyMethod(editor, file);
54 | if (method == null) {
55 | return;
56 | }
57 |
58 | Template template = BenchmarkMethodTemplateFactory.create(targetClass);
59 | createSpaceForNewMethod(file.getProject(), editor, file);
60 | TemplateEditingAdapter adapter = createTemplateAdapter(new Runnable() {
61 | public void run() {
62 |
63 | PsiDocumentManager.getInstance(file.getProject()).commitDocument(editor.getDocument());
64 | PsiFile psi = PsiDocumentManager.getInstance(file.getProject()).getPsiFile(editor.getDocument());
65 | if (psi == null) {
66 | return;
67 | }
68 | PsiElement el = PsiTreeUtil.findElementOfClassAtOffset(psi, editor.getCaretModel().getOffset() - 1, PsiMethod.class, false);
69 |
70 | if (el == null) {
71 | return;
72 | }
73 |
74 | PsiMethod method = PsiTreeUtil.getParentOfType(el, PsiMethod.class, false);
75 | if (method == null) {
76 | return;
77 | }
78 |
79 | if (method.findDeepestSuperMethods().length > 0) {
80 | GenerateMembersUtil.setupGeneratedMethod(method);
81 | }
82 | CreateFromUsageUtils.setupEditor(method, editor);
83 | }
84 | });
85 | TemplateManager.getInstance(file.getProject()).startTemplate(editor, template, adapter);
86 | }
87 | });
88 | }
89 |
90 | private static PsiClass findTargetClass(@NotNull Editor editor, @NotNull PsiFile file) {
91 | int offset = editor.getCaretModel().getOffset();
92 | PsiElement element = file.findElementAt(offset);
93 | return PsiTreeUtil.getParentOfType(element, PsiClass.class, false) == null ? null : TestIntegrationUtils.findOuterClass(element);
94 | }
95 |
96 | private static PsiMethod generateDummyMethod(Editor editor, PsiFile file) throws IncorrectOperationException {
97 | PsiMethod method = TestIntegrationUtils.createDummyMethod(file);
98 | PsiGenerationInfo info = OverrideImplementUtil.createGenerationInfo(method);
99 |
100 | int offset = findOffsetToInsertMethodTo(editor, file);
101 | GenerateMembersUtil.insertMembersAtOffset(file, offset, Collections.singletonList(info));
102 |
103 | PsiMethod member = info.getPsiMember();
104 | return member != null ? CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(member) : null;
105 | }
106 |
107 | private static void createSpaceForNewMethod(Project project, final Editor editor, final PsiFile psiFile) {
108 | CommandProcessor.getInstance().executeCommand(project, new Runnable() {
109 | @Override
110 | public void run() {
111 |
112 | ApplicationManager.getApplication().runWriteAction(new Runnable() {
113 | @Override
114 | public void run() {
115 | PsiMethod method = generateDummyMethod(editor, psiFile);
116 | if (method == null) {
117 | return;
118 | }
119 |
120 | TextRange range = method.getTextRange();
121 | editor.getDocument().replaceString(range.getStartOffset(), range.getEndOffset(), "");
122 | editor.getCaretModel().moveToOffset(range.getStartOffset());
123 | }
124 | });
125 |
126 | }
127 | }, "", DocCommandGroupId.noneGroupId(editor.getDocument()));
128 | }
129 |
130 | private static TemplateEditingAdapter createTemplateAdapter(final Runnable runnable) {
131 | return new TemplateEditingAdapter() {
132 | @Override
133 | public void templateFinished(@NotNull Template template, boolean brokenOff) {
134 | ApplicationManager.getApplication().runWriteAction(runnable);
135 | }
136 | };
137 | }
138 |
139 | private static int findOffsetToInsertMethodTo(Editor editor, PsiFile file) {
140 | int result = editor.getCaretModel().getOffset();
141 |
142 | PsiClass classAtCursor = PsiTreeUtil.getParentOfType(file.findElementAt(result), PsiClass.class, false);
143 |
144 | while (classAtCursor != null && !(classAtCursor.getParent() instanceof PsiFile)) {
145 | result = classAtCursor.getTextRange().getEndOffset();
146 | classAtCursor = PsiTreeUtil.getParentOfType(classAtCursor, PsiClass.class);
147 | }
148 |
149 | return result;
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/src/main/java/ru/artyushov/jmhPlugin/configuration/JmhConfigurationProducer.java:
--------------------------------------------------------------------------------
1 | package ru.artyushov.jmhPlugin.configuration;
2 |
3 | import com.intellij.execution.JavaExecutionUtil;
4 | import com.intellij.execution.Location;
5 | import com.intellij.execution.actions.ConfigurationContext;
6 | import com.intellij.execution.configurations.ConfigurationFactory;
7 | import com.intellij.execution.junit.JavaRunConfigurationProducerBase;
8 | import com.intellij.openapi.module.Module;
9 | import com.intellij.openapi.util.Ref;
10 | import com.intellij.psi.PsiClass;
11 | import com.intellij.psi.PsiElement;
12 | import com.intellij.psi.PsiMethod;
13 | import org.jetbrains.annotations.NotNull;
14 |
15 | import java.util.Objects;
16 |
17 | import static com.intellij.openapi.util.text.StringUtil.isEmpty;
18 | import static ru.artyushov.jmhPlugin.configuration.ConfigurationUtils.findBenchmarkEntry;
19 | import static ru.artyushov.jmhPlugin.configuration.ConfigurationUtils.toRunParams;
20 | import static ru.artyushov.jmhPlugin.configuration.JmhConfiguration.Type.CLASS;
21 | import static ru.artyushov.jmhPlugin.configuration.JmhConfiguration.Type.METHOD;
22 |
23 | /**
24 | * Supports creating run configurations from context (by right-clicking a code element in the source editor or the project view).
25 | */
26 | public class JmhConfigurationProducer extends JavaRunConfigurationProducerBase implements Cloneable {
27 |
28 | @NotNull
29 | @Override
30 | public ConfigurationFactory getConfigurationFactory() {
31 | return JmhConfigurationType.getInstance().getConfigurationFactories()[0];
32 | }
33 |
34 | /**
35 | * Sets up a configuration based on the specified context.
36 | *
37 | * @param configuration a clone of the template run configuration of the specified type
38 | * @param context contains the information about a location in the source code.
39 | * @param sourceElement a reference to the source element for the run configuration (by default contains the element at caret,
40 | * can be updated by the producer to point to a higher-level element in the tree).
41 | * @return true if the context is applicable to this run configuration producer, false if the context is not applicable and the
42 | * configuration should be discarded.
43 | */
44 | @Override
45 | protected boolean setupConfigurationFromContext(@NotNull JmhConfiguration configuration, ConfigurationContext context, @NotNull Ref sourceElement) {
46 | Location locationFromContext = context.getLocation();
47 | if (locationFromContext == null) {
48 | return false;
49 | }
50 | PsiElement benchmarkEntry = findBenchmarkEntry(locationFromContext.getPsiElement());
51 |
52 | final JmhConfiguration.Type runType;
53 | final PsiClass benchmarkClass;
54 | if (benchmarkEntry instanceof PsiClass) {
55 | runType = CLASS;
56 | benchmarkClass = (PsiClass) benchmarkEntry;
57 | } else if (benchmarkEntry instanceof PsiMethod) {
58 | runType = METHOD;
59 | benchmarkClass = benchmarkClassOfMethod(benchmarkEntry);
60 | } else {
61 | return false;
62 | }
63 | configuration.setBenchmarkClass(benchmarkClass.getQualifiedName());
64 |
65 | sourceElement.set(benchmarkEntry);
66 | setupConfigurationModule(context, configuration);
67 | final Module originalModule = configuration.getConfigurationModule().getModule();
68 | configuration.restoreOriginalModule(originalModule);
69 | String generatedParams = toRunParams(benchmarkEntry, true);
70 | configuration.setProgramParameters(createProgramParameters(generatedParams, configuration.getProgramParameters()));
71 | if (isEmpty(configuration.getWorkingDirectory())) { // respect default working directory if set
72 | configuration.setWorkingDirectory("$MODULE_WORKING_DIR$");
73 | }
74 | configuration.setName(getNameForConfiguration(benchmarkEntry));
75 | configuration.setType(runType);
76 | return true;
77 | }
78 |
79 | /**
80 | * Checks if the specified configuration was created from the specified context.
81 | *
82 | * @param configuration a configuration instance.
83 | * @param context contains the information about a location in the source code.
84 | * @return true if this configuration was created from the specified context, false otherwise.
85 | */
86 | @Override
87 | public boolean isConfigurationFromContext(@NotNull JmhConfiguration configuration, @NotNull ConfigurationContext context) {
88 | Location locationFromContext = context.getLocation();
89 | if (locationFromContext == null) {
90 | return false;
91 | }
92 | PsiElement benchmarkEntry = findBenchmarkEntry(locationFromContext.getPsiElement());
93 | PsiClass benchmarkClass;
94 | if (benchmarkEntry instanceof PsiMethod) {
95 | benchmarkClass = benchmarkClassOfMethod(benchmarkEntry);
96 | // if the config is for a whole benchmark class then ignore the method and use its class as an entry
97 | if (configuration.getBenchmarkType() == CLASS) {
98 | benchmarkEntry = benchmarkClass;
99 | } else if (configuration.getBenchmarkType() != METHOD) {
100 | // unexpected BenchmarkType, must be METHOD
101 | return false;
102 | }
103 | } else if (benchmarkEntry instanceof PsiClass) {
104 | // if the config is for a specific method but we are on a class then the config can't be applied
105 | if (configuration.getBenchmarkType() == METHOD) {
106 | return false;
107 | } else if (configuration.getBenchmarkType() != CLASS) {
108 | // unexpected BenchmarkType, must be CLASS
109 | return false;
110 | }
111 | benchmarkClass = (PsiClass) benchmarkEntry;
112 | } else {
113 | return false;
114 | }
115 | //TODO: this check may be skipped because we'll then check ProgramParameters, but it still faster to filter out
116 | if (!Objects.equals(benchmarkClass.getQualifiedName(), configuration.getBenchmarkClass())) {
117 | return false;
118 | }
119 | String generatedParams = toRunParams(benchmarkEntry, true);
120 | if (isEmpty(configuration.getProgramParameters())
121 | || !configuration.getProgramParameters().startsWith(generatedParams)) {
122 | return false;
123 | }
124 | Location location = JavaExecutionUtil.stepIntoSingleClass(locationFromContext);
125 | final Module originalModule = configuration.getConfigurationModule().getModule();
126 | if (location.getModule() == null || !location.getModule().equals(originalModule)) {
127 | return false;
128 | }
129 | setupConfigurationModule(context, configuration);
130 | configuration.restoreOriginalModule(originalModule);
131 |
132 | return true;
133 | }
134 |
135 | private String getNameForConfiguration(@NotNull PsiElement benchmarkEntry) {
136 | return toRunParams(benchmarkEntry, false);
137 | }
138 |
139 | private String createProgramParameters(String generatedParams, String defaultParams) {
140 | return isEmpty(defaultParams) ? generatedParams : generatedParams + ' ' + defaultParams;
141 | }
142 |
143 | private PsiClass benchmarkClassOfMethod(PsiElement benchmarkEntry) {
144 | PsiMethod benchmarkMethod = (PsiMethod) benchmarkEntry;
145 | return benchmarkMethod.getContainingClass();
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/src/main/java/ru/artyushov/jmhPlugin/inspection/JmhInspections.java:
--------------------------------------------------------------------------------
1 | package ru.artyushov.jmhPlugin.inspection;
2 |
3 | import com.intellij.codeInsight.intention.AddAnnotationFix;
4 | import com.intellij.codeInsight.intention.QuickFixFactory;
5 | import com.intellij.codeInspection.AbstractBaseUastLocalInspectionTool;
6 | import com.intellij.codeInspection.InspectionManager;
7 | import com.intellij.codeInspection.LocalQuickFix;
8 | import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement;
9 | import com.intellij.codeInspection.ProblemDescriptor;
10 | import com.intellij.psi.PsiMethod;
11 | import com.intellij.psi.PsiTypes;
12 | import org.jetbrains.annotations.NotNull;
13 | import org.jetbrains.annotations.Nullable;
14 | import org.jetbrains.uast.UClass;
15 | import org.jetbrains.uast.UField;
16 | import org.jetbrains.uast.UMethod;
17 |
18 | import java.util.Objects;
19 |
20 | import static com.intellij.codeInspection.ProblemHighlightType.ERROR;
21 | import static com.intellij.psi.PsiModifier.ABSTRACT;
22 | import static com.intellij.psi.PsiModifier.FINAL;
23 | import static com.intellij.psi.PsiModifier.PUBLIC;
24 | import static com.intellij.psi.PsiModifier.STATIC;
25 | import static ru.artyushov.jmhPlugin.configuration.ConfigurationUtils.JMH_ANNOTATION_STATE;
26 | import static ru.artyushov.jmhPlugin.configuration.ConfigurationUtils.hasBenchmarkAnnotation;
27 | import static ru.artyushov.jmhPlugin.configuration.ConfigurationUtils.hasSetupOrTearDownAnnotation;
28 | import static ru.artyushov.jmhPlugin.configuration.ConfigurationUtils.hasStateAnnotation;
29 |
30 | public class JmhInspections extends AbstractBaseUastLocalInspectionTool {
31 | private static com.intellij.psi.PsiType VOID = PsiTypes.voidType();
32 |
33 | /**
34 | * For performance reasons all checks are executed in one method
35 | */
36 | @Override
37 | public @Nullable
38 | ProblemDescriptor[] checkClass(@NotNull UClass klass, @NotNull InspectionManager manager, boolean isOnTheFly) {
39 | boolean isBenchmarkClass = false;
40 | UMethod[] methods = klass.getMethods();
41 | for (UMethod method : methods) {
42 | boolean hasBenchmarkAnnotation = hasBenchmarkAnnotation(method);
43 | boolean hasSetupOrTearDownAnnotation = false;
44 | if (!hasBenchmarkAnnotation) {
45 | hasSetupOrTearDownAnnotation = hasSetupOrTearDownAnnotation(method);
46 | if (hasSetupOrTearDownAnnotation) {
47 | // Check that Setup or TearDown is void
48 | if ((method.getReturnType() == null || !method.getReturnType().equals(VOID))) {
49 | LocalQuickFixAndIntentionActionOnPsiElement fix = QuickFixFactory.getInstance().createMethodReturnFix(method, VOID, false);
50 | ProblemDescriptor problem = manager.createProblemDescriptor(method.getIdentifyingElement(), "@Setup or @TearDown method should not return anything", fix, ERROR, isOnTheFly);
51 | return new ProblemDescriptor[]{problem};
52 | }
53 | }
54 | }
55 | if (hasBenchmarkAnnotation || hasSetupOrTearDownAnnotation) {
56 | isBenchmarkClass = true;
57 | if (!method.hasModifierProperty(PUBLIC)) {
58 | LocalQuickFixAndIntentionActionOnPsiElement fix = QuickFixFactory.getInstance().createModifierListFix(method, PUBLIC, true, false);
59 | ProblemDescriptor problem = manager.createProblemDescriptor(method.getIdentifyingElement(), "@Benchmark method should be public", fix, ERROR, isOnTheFly);
60 | return new ProblemDescriptor[]{problem};
61 | }
62 | if (method.hasModifierProperty(ABSTRACT)) {
63 | LocalQuickFixAndIntentionActionOnPsiElement fix = QuickFixFactory.getInstance().createModifierListFix(method, ABSTRACT, false, false);
64 | ProblemDescriptor problem = manager.createProblemDescriptor(method.getIdentifyingElement(), "@Benchmark method can not be abstract", fix, ERROR, isOnTheFly);
65 | return new ProblemDescriptor[]{problem};
66 | }
67 | }
68 | }
69 |
70 | boolean explicitState = hasStateAnnotation(klass);
71 | // validate if enclosing class is implicit @State
72 | if (explicitState) {
73 | ProblemDescriptor problem = validateState(klass, manager, isOnTheFly);
74 | if (problem != null) {
75 | return new ProblemDescriptor[]{problem};
76 | }
77 | }
78 |
79 | if (!isBenchmarkClass) {
80 | return null;
81 | }
82 | // validate against rogue fields
83 | if (!explicitState || klass.hasModifierProperty(ABSTRACT)) {
84 | for (UField field : klass.getFields()) {
85 | // allow static fields
86 | if (field.isStatic()) continue;
87 | AddAnnotationFix addAnnotationFix = new AddAnnotationFix(JMH_ANNOTATION_STATE, klass);
88 | ProblemDescriptor problem = manager.createProblemDescriptor(field, "Field is declared within the class not having @State annotation", addAnnotationFix, ERROR, isOnTheFly);
89 | return new ProblemDescriptor[]{problem};
90 | }
91 | }
92 |
93 | // if this is a default package
94 | if (Objects.equals(klass.getName(), klass.getQualifiedName())) {
95 | //TODO QuickFixFactory.getInstance().createCreateClassOrPackageFix(aClass, "Create package", )
96 | ProblemDescriptor problem = manager.createProblemDescriptor(klass, "Benchmark class should have package other than default", (LocalQuickFix) null, ERROR, isOnTheFly);
97 | return new ProblemDescriptor[]{problem};
98 | }
99 |
100 | if (klass.isFinal()) {
101 | LocalQuickFixAndIntentionActionOnPsiElement fix = QuickFixFactory.getInstance().createModifierListFix(klass, FINAL, false, true);
102 | ProblemDescriptor problem = manager.createProblemDescriptor(klass, "Benchmark classes should not be final", fix, ERROR, isOnTheFly);
103 | return new ProblemDescriptor[]{problem};
104 | }
105 |
106 | return null;
107 | }
108 |
109 | private ProblemDescriptor validateState(UClass stateClass, InspectionManager manager, boolean isOnTheFly) {
110 | if (!stateClass.hasModifierProperty(PUBLIC)) {
111 | LocalQuickFixAndIntentionActionOnPsiElement fix = QuickFixFactory.getInstance().createModifierListFix(stateClass, PUBLIC, true, true);
112 | return manager.createProblemDescriptor(stateClass, "The instantiated @State annotation only supports public classes", fix, ERROR, isOnTheFly);
113 | }
114 | if (stateClass.isFinal()) {
115 | LocalQuickFixAndIntentionActionOnPsiElement fix = QuickFixFactory.getInstance().createModifierListFix(stateClass, FINAL, false, true);
116 | return manager.createProblemDescriptor(stateClass, "The instantiated @State annotation does not support final classes", fix, ERROR, isOnTheFly);
117 | }
118 | // is inner class
119 | if (stateClass.getContainingClass() != null && !stateClass.isStatic()) {
120 | LocalQuickFixAndIntentionActionOnPsiElement fix = QuickFixFactory.getInstance().createModifierListFix(stateClass, STATIC, true, true);
121 | return manager.createProblemDescriptor(stateClass, "The instantiated @State annotation does not support inner classes, make sure your class is static", fix, ERROR, isOnTheFly);
122 | }
123 | if (stateClass.hasModifierProperty(ABSTRACT)) {
124 | LocalQuickFixAndIntentionActionOnPsiElement fix = QuickFixFactory.getInstance().createModifierListFix(stateClass, ABSTRACT, false, true);
125 | return manager.createProblemDescriptor(stateClass, "The instantiated @State class cannot be abstract", fix, ERROR, isOnTheFly);
126 | }
127 | PsiMethod[] constructors = stateClass.getConstructors();
128 | // if no any constructors then implicit default constructor exists
129 | boolean hasDefaultConstructor = constructors.length == 0;
130 | if (!hasDefaultConstructor) {
131 | for (PsiMethod constructor : constructors) {
132 | if (constructor.getParameterList().isEmpty()) {
133 | hasDefaultConstructor = true;
134 | if (!constructor.hasModifierProperty(PUBLIC)) {
135 | LocalQuickFixAndIntentionActionOnPsiElement fix = QuickFixFactory.getInstance().createModifierListFix(constructor, PUBLIC, true, true);
136 | return manager.createProblemDescriptor(constructor, "For @State class the default constructor must be public", fix, ERROR, isOnTheFly);
137 | }
138 | break;
139 | }
140 | }
141 | }
142 | if (!hasDefaultConstructor) {
143 | LocalQuickFixAndIntentionActionOnPsiElement fix = QuickFixFactory.getInstance().createAddDefaultConstructorFix(stateClass);
144 | return manager.createProblemDescriptor(stateClass, "The @State annotation can only be applied to the classes having the default public constructor", fix, ERROR, isOnTheFly);
145 | }
146 | return null;
147 | }
148 |
149 | }
150 |
--------------------------------------------------------------------------------
/src/main/java/ru/artyushov/jmhPlugin/configuration/JmhConfiguration.java:
--------------------------------------------------------------------------------
1 | package ru.artyushov.jmhPlugin.configuration;
2 |
3 | import com.intellij.execution.*;
4 | import com.intellij.execution.application.ApplicationConfiguration;
5 | import com.intellij.execution.configuration.CompatibilityAwareRunProfile;
6 | import com.intellij.execution.configuration.EnvironmentVariablesComponent;
7 | import com.intellij.execution.configurations.*;
8 | import com.intellij.execution.runners.ExecutionEnvironment;
9 | import com.intellij.execution.util.JavaParametersUtil;
10 | import com.intellij.execution.util.ProgramParametersUtil;
11 | import com.intellij.openapi.module.Module;
12 | import com.intellij.openapi.module.ModuleManager;
13 | import com.intellij.openapi.options.SettingsEditor;
14 | import com.intellij.openapi.options.SettingsEditorGroup;
15 | import com.intellij.openapi.project.Project;
16 | import com.intellij.openapi.util.InvalidDataException;
17 | import com.intellij.openapi.util.WriteExternalException;
18 | import com.intellij.openapi.util.registry.Registry;
19 | import com.intellij.openapi.util.text.StringUtil;
20 | import com.intellij.psi.PsiClass;
21 | import com.intellij.psi.PsiElement;
22 | import com.intellij.refactoring.listeners.RefactoringElementAdapter;
23 | import com.intellij.refactoring.listeners.RefactoringElementListener;
24 | import org.jdom.Element;
25 | import org.jetbrains.annotations.NotNull;
26 | import org.jetbrains.annotations.Nullable;
27 |
28 | import java.util.Arrays;
29 | import java.util.Collection;
30 | import java.util.HashMap;
31 | import java.util.Map;
32 | import java.util.Objects;
33 |
34 | import static ru.artyushov.jmhPlugin.configuration.ConfigurationUtils.isBenchmarkEntryElement;
35 | import static ru.artyushov.jmhPlugin.configuration.ConfigurationUtils.toRunParams;
36 |
37 | /**
38 | * User: nikart
39 | * Date: 09/04/14
40 | * Time: 18:46
41 | */
42 | public class JmhConfiguration extends JavaRunConfigurationBase
43 | implements CommonJavaRunConfigurationParameters, CompatibilityAwareRunProfile, RefactoringListenerProvider {
44 |
45 | public static final String ATTR_VM_PARAMETERS = "vm-parameters";
46 | public static final String ATTR_PROGRAM_PARAMETERS = "program-parameters";
47 | public static final String ATTR_WORKING_DIR = "working-dir";
48 | public static final String ATTR_BENCHMARK_TYPE = "benchmark-type";
49 | public static final String ATTR_BENCHMARK_CLASS = "benchmark-class";
50 | public static final String USE_CLASS_PATH_ONLY = "useClassPathOnly";
51 | public static final String ATTR_ALTERNATIVE_JRE_PATH = "alternativeJrePath";
52 | public static final String ATTR_IS_ALTERNATIVE_JRE_PATH_ENABLED = "isAlternativeJrePathEnabled";
53 |
54 | public enum Type {
55 | METHOD, CLASS
56 | }
57 |
58 | public static final String JMH_START_CLASS = "org.openjdk.jmh.Main";
59 |
60 | private String vmParameters;
61 | private boolean isAlternativeJrePathEnabled = false;
62 | private String alternativeJrePath;
63 | private String programParameters;
64 | private String workingDirectory;
65 | private Map envs = new HashMap<>(0, 1.0f);
66 | private boolean passParentEnvs;
67 | private boolean myUseModulePath = true;
68 | private ShortenCommandLine myShortenCommandLine;
69 | private String benchmarkClass;
70 |
71 | private Type type;
72 |
73 | public JmhConfiguration(String name, @NotNull JavaRunConfigurationModule configurationModule, @NotNull ConfigurationFactory factory) {
74 | super(name, configurationModule, factory);
75 | }
76 |
77 | public JmhConfiguration(@NotNull JavaRunConfigurationModule configurationModule, @NotNull ConfigurationFactory factory) {
78 | super(configurationModule, factory);
79 | }
80 |
81 | @Override
82 | public void setVMParameters(String s) {
83 | this.vmParameters = s;
84 | }
85 |
86 | @Override
87 | public String getVMParameters() {
88 | return vmParameters;
89 | }
90 |
91 | @Override
92 | public boolean isAlternativeJrePathEnabled() {
93 | return isAlternativeJrePathEnabled;
94 | }
95 |
96 | @Override
97 | public void setAlternativeJrePathEnabled(boolean enabled) {
98 | boolean changed = isAlternativeJrePathEnabled != enabled;
99 | isAlternativeJrePathEnabled = enabled;
100 | ApplicationConfiguration.onAlternativeJreChanged(changed, getProject());
101 | }
102 |
103 | @Override
104 | public String getAlternativeJrePath() {
105 | return alternativeJrePath != null ? new AlternativeJrePathConverter().fromString(alternativeJrePath) : null;
106 | }
107 |
108 | @Override
109 | public void setAlternativeJrePath(String path) {
110 | String collapsedPath = path != null ? new AlternativeJrePathConverter().toString(path) : null;
111 | boolean changed = !Objects.equals(alternativeJrePath, collapsedPath);
112 | alternativeJrePath = collapsedPath;
113 | ApplicationConfiguration.onAlternativeJreChanged(changed, getProject());
114 | }
115 |
116 | @Nullable
117 | @Override
118 | public String getRunClass() {
119 | return JMH_START_CLASS;
120 | }
121 |
122 | @Nullable
123 | @Override
124 | public String getPackage() {
125 | return null;
126 | }
127 |
128 | @Override
129 | public void setProgramParameters(@Nullable String s) {
130 | this.programParameters = s;
131 | }
132 |
133 | @Nullable
134 | @Override
135 | public String getProgramParameters() {
136 | return programParameters;
137 | }
138 |
139 | @Override
140 | public void setWorkingDirectory(@Nullable String s) {
141 | this.workingDirectory = s;
142 | }
143 |
144 | @Nullable
145 | @Override
146 | public String getWorkingDirectory() {
147 | return workingDirectory;
148 | }
149 |
150 | @Override
151 | public void setEnvs(@NotNull Map map) {
152 | envs = new HashMap<>(map);
153 | }
154 |
155 | @NotNull
156 | @Override
157 | public Map getEnvs() {
158 | return new HashMap<>(envs);
159 | }
160 |
161 | @Override
162 | public void setPassParentEnvs(boolean b) {
163 | this.passParentEnvs = b;
164 | }
165 |
166 | @Override
167 | public boolean isPassParentEnvs() {
168 | return passParentEnvs;
169 | }
170 |
171 | public boolean isUseModulePath() {
172 | return myUseModulePath;
173 | }
174 |
175 | public void setUseModulePath(boolean useModulePath) {
176 | myUseModulePath = useModulePath;
177 | }
178 |
179 | @Nullable
180 | @Override
181 | public ShortenCommandLine getShortenCommandLine() {
182 | return myShortenCommandLine;
183 | }
184 |
185 | @Override
186 | public void setShortenCommandLine(@Nullable ShortenCommandLine shortenCommandLine) {
187 | myShortenCommandLine = shortenCommandLine;
188 | }
189 |
190 | public void setType(Type type) {
191 | this.type = type;
192 | }
193 |
194 | public Type getBenchmarkType() {
195 | return type;
196 | }
197 |
198 | public void setBenchmarkClass(String benchmarkClass) {
199 | this.benchmarkClass = benchmarkClass;
200 | }
201 |
202 | public String getBenchmarkClass() {
203 | return benchmarkClass;
204 | }
205 |
206 | @Override
207 | public void checkConfiguration() throws RuntimeConfigurationException{
208 | JavaParametersUtil.checkAlternativeJRE(this);
209 | ProgramParametersUtil.checkWorkingDirectoryExist(this, getProject(), getConfigurationModule().getModule());
210 | }
211 |
212 | @Override
213 | public Collection getValidModules() {
214 | try {
215 | checkConfiguration();
216 | } catch (RuntimeConfigurationError e) {
217 | return Arrays.asList(ModuleManager.getInstance(getProject()).getModules());
218 | } catch (RuntimeConfigurationException ignored) {
219 | }
220 | return JavaRunConfigurationModule.getModulesForClass(getProject(), getRunClass());
221 | }
222 |
223 | @NotNull
224 | @Override
225 | public SettingsEditor extends RunConfiguration> getConfigurationEditor() {
226 | if (Registry.is("ide.new.run.config", true)) {
227 | return new JmhSettingsEditor(this);
228 | }
229 | SettingsEditorGroup group = new SettingsEditorGroup();
230 | group.addEditor(ExecutionBundle.message("run.configuration.configuration.tab.title"), new JmhConfigurable());
231 | JavaRunConfigurationExtensionManager.getInstance().appendEditors(this, group);
232 | return group;
233 | }
234 |
235 | @Nullable
236 | @Override
237 | public RunProfileState getState(@NotNull Executor executor, @NotNull ExecutionEnvironment executionEnvironment) throws ExecutionException {
238 | return new JmhBenchmarkCommandLineState(executionEnvironment, this);
239 | }
240 |
241 | @Override
242 | public void writeExternal(@NotNull Element element) throws WriteExternalException {
243 | super.writeExternal(element);
244 | if (vmParameters != null) {
245 | element.setAttribute(ATTR_VM_PARAMETERS, vmParameters);
246 | }
247 | if (programParameters != null) {
248 | element.setAttribute(ATTR_PROGRAM_PARAMETERS, programParameters);
249 | }
250 | if (workingDirectory != null) {
251 | element.setAttribute(ATTR_WORKING_DIR, workingDirectory);
252 | }
253 | if (type != null) {
254 | element.setAttribute(ATTR_BENCHMARK_TYPE, type.name());
255 | }
256 | if (benchmarkClass != null) {
257 | element.setAttribute(ATTR_BENCHMARK_CLASS, benchmarkClass);
258 | }
259 | element.setAttribute(ATTR_IS_ALTERNATIVE_JRE_PATH_ENABLED, String.valueOf(isAlternativeJrePathEnabled));
260 | if (alternativeJrePath != null) {
261 | element.setAttribute(ATTR_ALTERNATIVE_JRE_PATH, alternativeJrePath);
262 | }
263 | if (!myUseModulePath) {
264 | element.addContent(new Element(USE_CLASS_PATH_ONLY));
265 | }
266 | }
267 |
268 | @Override
269 | public void readExternal(@NotNull Element element) throws InvalidDataException {
270 | super.readExternal(element);
271 | setVMParameters(element.getAttributeValue(ATTR_VM_PARAMETERS));
272 | setProgramParameters(element.getAttributeValue(ATTR_PROGRAM_PARAMETERS));
273 | setWorkingDirectory(element.getAttributeValue(ATTR_WORKING_DIR));
274 | setBenchmarkClass(element.getAttributeValue(ATTR_BENCHMARK_CLASS));
275 | String typeString = element.getAttributeValue(ATTR_BENCHMARK_TYPE);
276 | if (typeString != null) {
277 | setType(Type.valueOf(typeString));
278 | }
279 | setAlternativeJrePathEnabled(Boolean.parseBoolean(element.getAttributeValue(ATTR_IS_ALTERNATIVE_JRE_PATH_ENABLED)));
280 | setAlternativeJrePath(element.getAttributeValue(ATTR_ALTERNATIVE_JRE_PATH));
281 | readModule(element);
282 | myUseModulePath = element.getChild(USE_CLASS_PATH_ONLY) == null;
283 | // EnvironmentVariablesComponent.readExternal(element, getPersistentData().getEnvs());
284 |
285 | }
286 |
287 | @Override
288 | public boolean mustBeStoppedToRun(@NotNull RunConfiguration configuration) {
289 | return JmhConfigurationType.TYPE_ID.equals(configuration.getType().getId());
290 | }
291 |
292 |
293 | @Nullable
294 | @Override
295 | public RefactoringElementListener getRefactoringElementListener(PsiElement element) {
296 | if (StringUtil.isEmpty(getProgramParameters())) {
297 | return null;
298 | }
299 | if (!isBenchmarkEntryElement(element)) {
300 | return null;
301 | }
302 | if (!getProgramParameters().startsWith(toRunParams(element, true))) {
303 | return null;
304 | }
305 | return new RefactoringElementAdapter() {
306 | @Override
307 | protected void elementRenamedOrMoved(@NotNull PsiElement newElement) {
308 | // replace benchmark class name at beginning of program params
309 | String newRunParams = toRunParams(newElement, true);
310 | int firstSpace = getProgramParameters().indexOf(' ');
311 | if (firstSpace == -1) {
312 | setProgramParameters(newRunParams);
313 | } else {
314 | setProgramParameters(newRunParams + getProgramParameters().substring(firstSpace));
315 | }
316 | //TODO smart update name to change only bench name. But we can't do this because we don't know old class name
317 | setName(toRunParams(newElement, false));
318 | if (newElement instanceof PsiClass) {
319 | setBenchmarkClass(((PsiClass)newElement).getQualifiedName());
320 | }
321 | }
322 |
323 | @Override
324 | public void undoElementMovedOrRenamed(@NotNull PsiElement newElement, @NotNull String oldQualifiedName) {
325 | elementRenamedOrMoved(newElement);
326 | }
327 | };
328 | }
329 |
330 | }
331 |
--------------------------------------------------------------------------------