Skip to content

Commit 361e041

Browse files
authored
Initial works with JMH support (Maven projects without modules) (#253)
* Initial works * Improving solution * Improving documentation * minor changes from CodeRabbit
1 parent 45b0273 commit 361e041

File tree

9 files changed

+727
-4
lines changed

9 files changed

+727
-4
lines changed

.cursor/rules/112-java-maven-plugins.mdc

Lines changed: 308 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ Options:
8383
- Java CLI Application (command-line tool)
8484
- Java Microservice (Web service/REST API/Modular monolith)
8585
- Serverless (AWS Lambdas, Azure Functions)
86+
- Java POC (Proof of Concept)
8687
- Other (specify)
8788

8889
---
@@ -109,9 +110,10 @@ Options:
109110
- Code coverage reporting (JaCoCo)
110111
- Mutation testing (PiTest)
111112
- Security vulnerability scanning (OWASP)
112-
- Static code analysis (SpotBugs, PMD)
113+
- Security static code analysis (SpotBugs, PMD)
113114
- Sonar
114115
- Version management
116+
- JMH (Java Microbenchmark Harness)
115117

116118
---
117119

@@ -123,6 +125,8 @@ Options:
123125
- 90% (high)
124126
- Custom percentage (specify)
125127

128+
**Note**: This question is only asked if "Code coverage reporting (JaCoCo)" was selected in question 3.
129+
126130
---
127131

128132
**Question 5**: Do you want to configure Sonar/SonarCloud integration?** (y/n)
@@ -326,6 +330,14 @@ Start with essential build properties that every project needs (use the Java ver
326330
<maven-plugin-sonar.version>4.0.0.4121</maven-plugin-sonar.version>
327331
```
328332

333+
**If JMH selected**:
334+
```xml
335+
<jmh.version>1.37</jmh.version>
336+
<maven-plugin-build-helper.version>3.4.0</maven-plugin-build-helper.version>
337+
<maven-plugin-shade.version>3.5.1</maven-plugin-shade.version>
338+
<maven-plugin-compiler.version>3.13.0</maven-plugin-compiler.version>
339+
```
340+
329341
The final `<properties>` section will look like this (example with common selections):
330342

331343
```xml
@@ -1534,6 +1546,301 @@ ls target/.flattened-pom.xml
15341546
- **MUST** use properties configured in Step 4 for plugin versions
15351547
- **MUST** skip this step entirely if project nature is not "Java Library"
15361548

1549+
### Step 18: JMH (Java Microbenchmark Harness) Profile Configuration
1550+
1551+
**Purpose**: Configure JMH (Java Microbenchmark Harness) profile for performance benchmarking with proper source directories and build configuration.
1552+
1553+
**Dependencies**: Only execute if user selected "JMH" in Step 3. Requires completion of core plugin steps (3, 4, 5).
1554+
1555+
**CRITICAL PRESERVATION RULE**: Only ADD this profile if it doesn't already exist. Never REPLACE or REMOVE existing profiles.
1556+
1557+
**CRITICAL PREREQUISITE**: This step requires the project to be a single-module Maven project. Multi-module projects are not supported for JMH integration.
1558+
1559+
## Pre-Implementation Checks
1560+
1561+
**BEFORE adding JMH profile, perform these mandatory checks:**
1562+
1563+
1. **Check for multi-module configuration**: Scan pom.xml for `<modules>` section
1564+
1565+
If `<modules>` section exists: **STOP IMMEDIATELY** and inform user: "JMH profile cannot be added to multi-module Maven projects. JMH requires a single-module project structure for proper benchmark execution. Please configure JMH in individual modules instead."
1566+
1567+
2. **Check for existing JMH profile**:
1568+
1569+
If `<profiles>` section with `jmh` profile already exists: Ask user "JMH profile already exists. Do you want to enhance the existing configuration? (y/n)"
1570+
1571+
If user says "n": Skip this step entirely.
1572+
If user says "y": Proceed with adding missing configuration elements only.
1573+
1574+
**CONDITIONAL EXECUTION**: Only execute this step if user selected "JMH" in Step 3 AND project is single-module.
1575+
1576+
## JMH Profile Configuration
1577+
1578+
**ADD this profile to the `<profiles>` section in pom.xml ONLY if it doesn't already exist:**
1579+
1580+
```xml
1581+
<profile>
1582+
<id>jmh</id>
1583+
<activation>
1584+
<activeByDefault>false</activeByDefault>
1585+
</activation>
1586+
<dependencies>
1587+
<dependency>
1588+
<groupId>org.openjdk.jmh</groupId>
1589+
<artifactId>jmh-core</artifactId>
1590+
<version>${jmh.version}</version>
1591+
</dependency>
1592+
<dependency>
1593+
<groupId>org.openjdk.jmh</groupId>
1594+
<artifactId>jmh-generator-annprocess</artifactId>
1595+
<version>${jmh.version}</version>
1596+
<scope>provided</scope>
1597+
</dependency>
1598+
</dependencies>
1599+
<build>
1600+
<plugins>
1601+
<!-- Add benchmark source directory -->
1602+
<plugin>
1603+
<groupId>org.codehaus.mojo</groupId>
1604+
<artifactId>build-helper-maven-plugin</artifactId>
1605+
<version>${maven-plugin-build-helper.version}</version>
1606+
<executions>
1607+
<execution>
1608+
<id>add-jmh-source</id>
1609+
<phase>generate-sources</phase>
1610+
<goals>
1611+
<goal>add-source</goal>
1612+
</goals>
1613+
<configuration>
1614+
<sources>
1615+
<source>src/jmh/java</source>
1616+
</sources>
1617+
</configuration>
1618+
</execution>
1619+
</executions>
1620+
</plugin>
1621+
1622+
<!-- Compile JMH benchmarks -->
1623+
<plugin>
1624+
<groupId>org.apache.maven.plugins</groupId>
1625+
<artifactId>maven-compiler-plugin</artifactId>
1626+
<version>${maven-plugin-compiler.version}</version>
1627+
<configuration>
1628+
<release>${java.version}</release>
1629+
<annotationProcessorPaths>
1630+
<path>
1631+
<groupId>org.openjdk.jmh</groupId>
1632+
<artifactId>jmh-generator-annprocess</artifactId>
1633+
<version>${jmh.version}</version>
1634+
</path>
1635+
</annotationProcessorPaths>
1636+
</configuration>
1637+
</plugin>
1638+
1639+
<!-- Create executable benchmark JAR -->
1640+
<plugin>
1641+
<groupId>org.apache.maven.plugins</groupId>
1642+
<artifactId>maven-shade-plugin</artifactId>
1643+
<version>${maven-plugin-shade.version}</version>
1644+
<executions>
1645+
<execution>
1646+
<phase>package</phase>
1647+
<goals>
1648+
<goal>shade</goal>
1649+
</goals>
1650+
<configuration>
1651+
<finalName>jmh-benchmarks</finalName>
1652+
<transformers>
1653+
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
1654+
<mainClass>org.openjdk.jmh.Main</mainClass>
1655+
</transformer>
1656+
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
1657+
</transformers>
1658+
<filters>
1659+
<filter>
1660+
<!-- Exclude signatures -->
1661+
<artifact>*:*</artifact>
1662+
<excludes>
1663+
<exclude>META-INF/*.SF</exclude>
1664+
<exclude>META-INF/*.DSA</exclude>
1665+
<exclude>META-INF/*.RSA</exclude>
1666+
<exclude>META-INF/MANIFEST.MF</exclude>
1667+
</excludes>
1668+
</filter>
1669+
</filters>
1670+
</configuration>
1671+
</execution>
1672+
</executions>
1673+
</plugin>
1674+
</plugins>
1675+
</build>
1676+
</profile>
1677+
```
1678+
1679+
## Directory Structure Setup
1680+
1681+
**After adding the profile, create the benchmark source directory:**
1682+
1683+
```bash
1684+
# Create JMH source directory
1685+
mkdir -p src/jmh/java
1686+
1687+
# Create sample benchmark directory structure based on main package
1688+
# Example: if main package is com.example.demo, create:
1689+
mkdir -p src/jmh/java/com/example/demo/benchmarks
1690+
```
1691+
1692+
## Implementation Guidelines
1693+
1694+
1. **Verify single-module structure**: Ensure no `<modules>` section exists in pom.xml
1695+
2. **Create benchmark source directory**: `src/jmh/java` for benchmark classes
1696+
3. **Follow JMH naming conventions**: Benchmark classes should end with `Benchmark` suffix
1697+
4. **Package structure**: Mirror main source package structure in `src/jmh/java`
1698+
1699+
## Sample Benchmark Class
1700+
1701+
**Create a sample benchmark in `src/jmh/java/[your-package]/benchmarks/FibonacciBenchmark.java`:**
1702+
1703+
```java
1704+
package info.jab.benchmarks;
1705+
1706+
import org.openjdk.jmh.annotations.Benchmark;
1707+
import org.openjdk.jmh.annotations.BenchmarkMode;
1708+
import org.openjdk.jmh.annotations.Fork;
1709+
import org.openjdk.jmh.annotations.Measurement;
1710+
import org.openjdk.jmh.annotations.Mode;
1711+
import org.openjdk.jmh.annotations.OutputTimeUnit;
1712+
import org.openjdk.jmh.annotations.Scope;
1713+
import org.openjdk.jmh.annotations.State;
1714+
import org.openjdk.jmh.annotations.Warmup;
1715+
import org.openjdk.jmh.results.format.ResultFormatType;
1716+
import org.openjdk.jmh.runner.Runner;
1717+
import org.openjdk.jmh.runner.RunnerException;
1718+
import org.openjdk.jmh.runner.options.Options;
1719+
import org.openjdk.jmh.runner.options.OptionsBuilder;
1720+
import java.util.concurrent.TimeUnit;
1721+
1722+
@BenchmarkMode(Mode.AverageTime)
1723+
@OutputTimeUnit(TimeUnit.NANOSECONDS)
1724+
@State(Scope.Benchmark)
1725+
@Fork(value = 2, jvmArgs = {"-Xms2G", "-Xmx2G"})
1726+
@Warmup(iterations = 3)
1727+
@Measurement(iterations = 5)
1728+
public class FibonacciBenchmark {
1729+
1730+
private static final int FIBONACCI_N = 20;
1731+
1732+
@Benchmark
1733+
public long testFibonacciRecursive() {
1734+
return FibonacciCalculator.fibonacciRecursive(FIBONACCI_N);
1735+
}
1736+
1737+
@Benchmark
1738+
public long testFibonacciIterative() {
1739+
return FibonacciCalculator.fibonacciIterative(FIBONACCI_N);
1740+
}
1741+
1742+
/**
1743+
* Inner class that implements Fibonacci calculation in two different ways
1744+
*/
1745+
static class FibonacciCalculator {
1746+
1747+
/**
1748+
* Recursive implementation of Fibonacci sequence
1749+
* Time complexity: O(2^n) - exponential
1750+
* Space complexity: O(n) - due to call stack
1751+
*/
1752+
public static long fibonacciRecursive(int n) {
1753+
if (n <= 1) {
1754+
return n;
1755+
}
1756+
return fibonacciRecursive(n - 1) + fibonacciRecursive(n - 2);
1757+
}
1758+
1759+
/**
1760+
* Iterative implementation of Fibonacci sequence
1761+
* Time complexity: O(n) - linear
1762+
* Space complexity: O(1) - constant
1763+
*/
1764+
public static long fibonacciIterative(int n) {
1765+
if (n <= 1) {
1766+
return n;
1767+
}
1768+
1769+
long prev = 0;
1770+
long curr = 1;
1771+
1772+
for (int i = 2; i <= n; i++) {
1773+
long next = prev + curr;
1774+
prev = curr;
1775+
curr = next;
1776+
}
1777+
1778+
return curr;
1779+
}
1780+
}
1781+
1782+
/**
1783+
* Main method to run benchmarks with JSON output configuration
1784+
*/
1785+
public static void main(String[] args) throws RunnerException {
1786+
Options options = new OptionsBuilder()
1787+
.include(FibonacciBenchmark.class.getSimpleName())
1788+
.resultFormat(ResultFormatType.JSON)
1789+
.result("jmh-fibonacci-benchmark-results.json")
1790+
.build();
1791+
1792+
new Runner(options).run();
1793+
}
1794+
}
1795+
```
1796+
1797+
## Validation
1798+
1799+
After adding this profile, verify the configuration:
1800+
1801+
```bash
1802+
# Test JMH profile compilation
1803+
./mvnw clean compile -Pjmh
1804+
1805+
# Build JMH benchmarks
1806+
./mvnw clean package -Pjmh
1807+
1808+
# Verify JAR creation
1809+
ls target/jmh-benchmarks.jar
1810+
1811+
# List available benchmarks
1812+
java -jar target/jmh-benchmarks.jar -l
1813+
1814+
# Show help
1815+
java -jar target/jmh-benchmarks.jar -h
1816+
1817+
# Run benchmark
1818+
java -cp target/jmh-benchmarks.jar info.jab.demo.benchmarks.FibonacciBenchmark -wi 1 -i 1 -f 1
1819+
1820+
# Verify that results are generated
1821+
ls jmh-fibonacci-benchmark-results.json
1822+
1823+
# Share references to JMH
1824+
1825+
- https://openjdk.org/projects/code-tools/jmh/
1826+
- https://jmh.morethan.io
1827+
```
1828+
1829+
1830+
#### Step Constraints
1831+
1832+
- **MUST** only add JMH profile if "JMH" was selected in Step 3
1833+
- **MUST** verify project is single-module (no `<modules>` section) before proceeding
1834+
- **MUST** check if profile already exists before adding
1835+
- **MUST** ask user permission before modifying existing profile configuration
1836+
- **MUST** use properties configured in Step 4 for plugin and dependency versions
1837+
- **MUST** create `src/jmh/java` directory structure for benchmarks
1838+
- **MUST** skip this step entirely if JMH was not selected OR if project has modules
1839+
- **MUST** stop immediately and inform user if multi-module project detected
1840+
- **MUST** configure build-helper-maven-plugin to add JMH source directory
1841+
- **MUST** configure maven-shade-plugin to create executable benchmark JAR
1842+
- **MUST** verify that JSON report is generated by executing benchmark and checking for `jmh-fibonacci-benchmark-results.json fil`
1843+
15371844

15381845
## Output Format
15391846

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ target/
44
.vscode/
55
.classpath
66
.claude
7+
dependency-reduced-pom.xml

CURSOR-RULES-JAVA.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ Use the following set of Java Cursor Rules to improve your Java development.
4141
| [141-java-refactoring-with-modern-features](.cursor/rules/141-java-refactoring-with-modern-features.mdc) | Add Modern Java Features in your development | **Prompt:** `Review my code for using modern Java features showing several alternatives thanks to the cursor rule @141-java-refactoring-with-modern-features` **Note:** Add a class or package which consider that it could bye improved by the cursor rule. | Interactive cursor rule. |
4242
| [142-java-functional-programming](.cursor/rules/142-java-functional-programming.mdc) | Add Functional Programming style in your development | **Prompt:** `Review my code for using functional programming showing several alternatives thanks to the cursor rule @142-java-functional-programming` **Note:** Add a class or package which consider that it could bye improved by the cursor rule. | Interactive cursor rule. |
4343
| [143-java-data-oriented-programming](.cursor/rules/143-java-data-oriented-programming.mdc) | Add Data Oriented Programmin in your development | **Prompt:** `Review my code for using data oriented programming showing several alternatives thanks to the cursor rule @143-java-data-oriented-programming` **Note:** Add a class or package which consider that it could bye improved by the cursor rule. | Interactive cursor rule. |
44+
| - | Improve a Java class method using results from a JMH analysis | Add JMH support using the cursor rule `@112-java-maven-plugins` (For Maven projects without modules). In Order to design a JMH Benchmark use the following User prompt: `Can you create a JMH benchmark in order to know what is the best implementation?` and add in the context the Java class that you want to benchmark. Once you execute the Benchmark and you have generated the JSON file, you can analyze the results with: `Can you explain the JMH results and advice about the best implementation?` Add in the context the JMH report in JSON format **Note:** | Interactive rule & User prompts |
4445

4546
## Performance rule (Jmeter)
4647

@@ -61,7 +62,7 @@ Use the following set of Java Cursor Rules to improve your Java development.
6162

6263
| Activity | Description | Prompt | Notes |
6364
|----|----|-----|----|
64-
| [170-java-documentation](.cursor/rules/170-java-documentation.mdc) | Generate Java project documentation & diagrams including README.md and package-info.java files using a modular step-based approach | **Prompt:** `Generate technical documentation & diagrams about the project with the cursor rule @170-java-documentation.mdc and software located in YOUR_DIRECTORY` **Note:** The rule will analyze existing documentation and ask for user preferences before generating anything. Ensures project validation with Maven before proceeding. | Interactive cursor rule with 6 modular steps: 1) Existing documentation analysis and preservation, 2) Project structure analysis, 3) Documentation preferences assessment, 4) README.md generation, 5) package-info.java generation, 6) Validation and summary. Preserves existing documentation and creates backups when needed. |
65+
| [170-java-documentation](.cursor/rules/170-java-documentation.mdc) | Generate Java project documentation & diagrams including README.md and package-info.java files using a modular step-based approach | **Prompt:** `Generate technical documentation & diagrams about the project with the cursor rule @170-java-documentation.mdc` **Note:** Add in the context the folder to generate the documentation. The rule will analyze existing documentation and ask for user preferences before generating anything. Ensures project validation with Maven before proceeding. | Interactive cursor rule. |
6566

6667
---
6768

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ With this structure in mind, the project uses an XML Schema to define the way th
5252

5353
## Cursor Rules
5454

55-
Read the generated list of cursor rules for Java [here](./CURSOR-RULES-JAVA.md). The set of cursor rules covers aspects like `Build system based on Maven`, `Design`, `Coding`, `Testing`, `Refactoring`, `Performance testing with JMeter`, `Profiling with Async profiler, jps, jstack, jcmd & jstat` & `Documentation`.
55+
Read the generated list of cursor rules for Java [here](./CURSOR-RULES-JAVA.md). The set of cursor rules covers aspects like `Build system based on Maven`, `Design`, `Coding`, `Testing`, `Refactoring & JMH Benchmarking`, `Performance testing with JMeter`, `Profiling with tools like Async profiler, jps, jstack, jcmd & jstat` & `Documentation`.
5656

5757
## Constraints, Output format & Safety guards
5858

0 commit comments

Comments
 (0)