Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ dependencies {
implementation("com.fasterxml.jackson.core:jackson-core")

compileOnly(libs.develocity)

testImplementation("me.champeau.jmh:jmh-gradle-plugin:0.7.3")
}

tasks.compileKotlin {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ import org.gradle.testing.jacoco.plugins.JacocoTaskExtension

class TestJvmConstraintsPlugin : Plugin<Project> {
override fun apply(project: Project) {
if (project.extensions.findByName(TEST_JVM_CONSTRAINTS) != null) {
return
}

project.pluginManager.apply(JavaPlugin::class.java)

val projectExtension = project.extensions.create<TestJvmConstraintsExtension>(TEST_JVM_CONSTRAINTS)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import datadog.gradle.plugin.testJvmConstraints.TestJvmSpec
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property

/*
* Applies JMH with defaults from `-PtestJvm` and `-Pjmh.*`. Modules can override them in their
* `jmh {}` block.
*/
// This plugin is produced by the same buildSrc build, so it cannot be resolved from this
// precompiled script's `plugins {}` block. Apply it by ID once both plugins are available at runtime.
pluginManager.apply("dd-trace-java.test-jvm-constraints")

// JMH is versioned in the root build with `apply false`, not added to buildSrc's implementation
// classpath. Applying it by ID here reuses the consuming build's plugin classpath.
pluginManager.apply("me.champeau.jmh")

val testJvmSpec = TestJvmSpec(project)
val jmh = extensions.getByName("jmh")


jmhProperty<String>("getJvm").convention(testJvmSpec.javaTestLauncher.map { it.executablePath.asFile.absolutePath })
providers.gradleProperty("jmh.includes").map(::commaSeparated).let {
if (it.isPresent) {
jmhListProperty("getIncludes").convention(it.map { includes -> listOf(includes.joinToString("|")) })
}
}
providers.gradleProperty("jmh.profilers").map(::commaSeparated).let {
if (it.isPresent) {
jmhListProperty("getProfilers").convention(it)
}
}
providers.gradleProperty("jmh.forks").map(String::toInt).let {
if (it.isPresent) {
jmhProperty<Int>("getFork").convention(it)
}
}
providers.gradleProperty("jmh.threads").map(String::toInt).let {
if (it.isPresent) {
jmhProperty<Int>("getThreads").convention(it)
}
}

// JMH types are not on buildSrc's compile classpath, so access its extension through Gradle's public
// property types.
@Suppress("UNCHECKED_CAST")
fun <T : Any> jmhProperty(getterName: String): Property<T> =
jmh.javaClass.getMethod(getterName).invoke(jmh) as Property<T>

@Suppress("UNCHECKED_CAST")
fun jmhListProperty(getterName: String): ListProperty<String> =
jmh.javaClass.getMethod(getterName).invoke(jmh) as ListProperty<String>

fun commaSeparated(value: String): List<String> =
value.split(",").map(String::trim).filter(String::isNotEmpty)
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package datadog.gradle.plugin.jmh

import datadog.gradle.plugin.testJvmConstraints.TestJvmConstraintsExtension.Companion.TEST_JVM_CONSTRAINTS
import datadog.gradle.plugin.testJvmConstraints.TestJvmSpec
import me.champeau.jmh.JmhParameters
import org.assertj.core.api.Assertions.assertThat
import org.gradle.api.JavaVersion
import org.gradle.testfixtures.ProjectBuilder
import org.junit.jupiter.api.Test

class JmhConventionsPluginTest {
@Test
fun `plugin applies jmh and test-jvm-constraints`() {
val project = ProjectBuilder.builder().build()

project.pluginManager.apply("dd-trace-java.jmh-conventions")

assertThat(project.plugins.hasPlugin("me.champeau.jmh")).isTrue()
assertThat(project.extensions.findByName(TEST_JVM_CONSTRAINTS)).isNotNull()
}

@Test
fun `plugin provides the test jvm as an overridable default`() {
val propertyName = "org.gradle.project.${TestJvmSpec.TEST_JVM}"
val previousValue = System.setProperty(propertyName, JavaVersion.current().majorVersion)

try {
val project = ProjectBuilder.builder().build()

project.pluginManager.apply("dd-trace-java.jmh-conventions")

val jmh = project.extensions.getByType(JmhParameters::class.java)
val expectedExecutable = TestJvmSpec(project).javaTestLauncher.get().executablePath.asFile.absolutePath
assertThat(jmh.jvm.get()).isEqualTo(expectedExecutable)

jmh.jvm.set("module-jvm")
assertThat(jmh.jvm.get()).isEqualTo("module-jvm")
} finally {
if (previousValue == null) {
System.clearProperty(propertyName)
} else {
System.setProperty(propertyName, previousValue)
}
}
}

@Test
fun `plugin provides jmh project properties as defaults`() {
withGradleProperties(
"jmh.includes" to "FooBenchmark, BarBenchmark",
"jmh.profilers" to "stack, gc",
"jmh.forks" to "1",
"jmh.threads" to "1",
) {
val project = ProjectBuilder.builder().build()

project.pluginManager.apply("dd-trace-java.jmh-conventions")

val jmh = project.extensions.getByType(JmhParameters::class.java)
assertThat(jmh.includes.get()).containsExactly("FooBenchmark|BarBenchmark")
assertThat(jmh.profilers.get()).containsExactly("stack", "gc")
assertThat(jmh.fork.get()).isEqualTo(1)
assertThat(jmh.threads.get()).isEqualTo(1)
}
}

@Test
fun `jmh properties are absent when the project properties are not set`() {
val project = ProjectBuilder.builder().build()

project.pluginManager.apply("dd-trace-java.jmh-conventions")

val jmh = project.extensions.getByType(JmhParameters::class.java)
assertThat(jmh.includes.get()).isEmpty()
assertThat(jmh.profilers.get()).isEmpty()
assertThat(jmh.fork.isPresent).isFalse()
assertThat(jmh.threads.isPresent).isFalse()
}

@Test
fun `module jmh settings override project property defaults`() {
withGradleProperties(
"jmh.profilers" to "async",
"jmh.forks" to "1",
) {
val project = ProjectBuilder.builder().build()

project.pluginManager.apply("dd-trace-java.jmh-conventions")

val jmh = project.extensions.getByType(JmhParameters::class.java)
jmh.profilers.set(listOf("gc"))
jmh.fork.set(4)

assertThat(jmh.profilers.get()).containsExactly("gc")
assertThat(jmh.fork.get()).isEqualTo(4)
}
}

@Test
fun `applying test-jvm-constraints before jmh-conventions is idempotent`() {
val project = ProjectBuilder.builder().build()

project.pluginManager.apply("dd-trace-java.test-jvm-constraints")
project.pluginManager.apply("dd-trace-java.jmh-conventions")

assertThat(project.extensions.findByName(TEST_JVM_CONSTRAINTS)).isNotNull()
}

private fun withGradleProperties(vararg properties: Pair<String, String>, assertions: () -> Unit) {
val systemProperties = properties.associate { (name, value) -> "org.gradle.project.$name" to value }
val previousValues = systemProperties.keys.associateWith(System::getProperty)

try {
systemProperties.forEach(System::setProperty)
assertions()
} finally {
previousValues.forEach { (name, value) ->
if (value == null) {
System.clearProperty(name)
} else {
System.setProperty(name, value)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@ class TestJvmConstraintsPluginTest {
assertThat(testTask.extensions.findByName(TEST_JVM_CONSTRAINTS)).isInstanceOf(TestJvmConstraintsExtension::class.java)
}

@Test
fun `plugin is idempotent when applied more than once`() {
val project = ProjectBuilder.builder().build()

project.pluginManager.apply("dd-trace-java.test-jvm-constraints")
TestJvmConstraintsPlugin().apply(project)

val testTask = project.tasks.named("test", GradleTest::class.java).get()

assertThat(project.extensions.findByName(TEST_JVM_CONSTRAINTS)).isInstanceOf(TestJvmConstraintsExtension::class.java)
assertThat(testTask.extensions.findByName(TEST_JVM_CONSTRAINTS)).isInstanceOf(TestJvmConstraintsExtension::class.java)
}

@Test
fun `jacoco is disabled for additional test jvm when coverage is not checked`() {
val testTask = testTaskWithJacoco()
Expand Down
2 changes: 1 addition & 1 deletion components/json/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
plugins {
id("me.champeau.jmh")
id("dd-trace-java.jmh-conventions")
}

apply(from = "$rootDir/gradle/java.gradle")
Expand Down
2 changes: 1 addition & 1 deletion dd-java-agent/agent-bootstrap/build.gradle
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// The shadowJar of this project will be injected into the JVM's bootstrap classloader
plugins {
id 'com.gradleup.shadow'
id 'me.champeau.jmh'
id 'dd-trace-java.jmh-conventions'
}

apply from: "$rootDir/gradle/java.gradle"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,8 @@
* short-circuits on the first check.
*
* <pre>
* # agent-bootstrap has no -Pjmh.includes wiring yet (a generalization is in flight), so for now
* # either run the whole module (only a handful of benchmarks) ...
* ./gradlew :dd-java-agent:agent-bootstrap:jmh
* # ... or hack a temporary filter into agent-bootstrap/build.gradle: jmh { includes = ['SharedDBCommenter.*'] }
* # add -prof gc (gc.alloc.rate.norm) to corroborate the allocation delta.
* ./gradlew :dd-java-agent:agent-bootstrap:jmh -Pjmh.includes=SharedDBCommenterBenchmark -Pjmh.profilers=gc
* # gc.alloc.rate.norm (B/op) corroborates the allocation delta.
* </pre>
*
* <p><b>Results</b> (JDK 17, MacBook M-series, {@code @Threads(8)}, {@code @Fork(5)}, {@code -prof
Expand Down
2 changes: 1 addition & 1 deletion dd-java-agent/agent-iast/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import net.ltgt.gradle.errorprone.CheckSeverity

plugins {
id 'com.gradleup.shadow'
id 'me.champeau.jmh'
id 'dd-trace-java.jmh-conventions'
id 'com.google.protobuf' version '0.10.0'
id 'net.ltgt.errorprone' version '3.1.0'
id 'dd-trace-java.version-file'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
plugins {
id 'com.gradleup.shadow'
id 'me.champeau.jmh'
id 'dd-trace-java.jmh-conventions'
}

ext {
Expand Down
2 changes: 1 addition & 1 deletion dd-java-agent/agent-tooling/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
plugins {
id 'me.champeau.jmh'
id 'dd-trace-java.jmh-conventions'
id 'java-test-fixtures'
}
apply from: "$rootDir/gradle/java.gradle"
Expand Down
2 changes: 1 addition & 1 deletion dd-java-agent/appsec/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar

plugins {
id 'com.gradleup.shadow'
id 'me.champeau.jmh'
id 'dd-trace-java.jmh-conventions'
id 'dd-trace-java.version-file'
}

Expand Down
2 changes: 1 addition & 1 deletion dd-java-agent/benchmark/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
plugins {
id 'me.champeau.jmh'
id 'dd-trace-java.jmh-conventions'
}

apply from: "$rootDir/gradle/java.gradle"
Expand Down
2 changes: 1 addition & 1 deletion dd-java-agent/instrumentation/jdbc/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
plugins {
id 'java-test-fixtures'
id 'me.champeau.jmh'
id 'dd-trace-java.jmh-conventions'
}

muzzle {
Expand Down
14 changes: 1 addition & 13 deletions dd-trace-core/build.gradle
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import datadog.gradle.plugin.testJvmConstraints.TestJvmSpec

plugins {
id 'me.champeau.jmh'
id 'dd-trace-java.jmh-conventions'
id 'dd-trace-java.version-file'
}

Expand Down Expand Up @@ -129,14 +127,4 @@ dependencies {
jmh {
jmhVersion = libs.versions.jmh.get()
duplicateClassesStrategy = DuplicatesStrategy.EXCLUDE
if (project.hasProperty('jmh.includes')) {
includes = [project.property('jmh.includes').replace(',', '|')]
}
if (project.hasProperty('jmh.profilers')) {
profilers = project.property('jmh.profilers').tokenize(',')
}
if (project.hasProperty('testJvm')) {
def testJvmSpec = new TestJvmSpec(project)
jvm = testJvmSpec.javaTestLauncher.map { it.executablePath.asFile.absolutePath }
}
}
2 changes: 1 addition & 1 deletion dd-trace-ot/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import groovy.lang.Closure
plugins {
`java-library`
id("com.gradleup.shadow")
id("me.champeau.jmh")
id("dd-trace-java.jmh-conventions")
}

description = "dd-trace-ot"
Expand Down
44 changes: 43 additions & 1 deletion docs/how_to_work_with_gradle.md
Original file line number Diff line number Diff line change
Expand Up @@ -971,6 +971,49 @@ tasks.named<Test>("latestDepTest") {
./gradlew allTests -PtestJvm=zulu11
```

### JMH Benchmarks (`dd-trace-java.jmh-conventions` Plugin)

The convention uses the JVM selected by the `dd-trace-java.test-jvm-constraints` plugin via `-PtestJvm` as the
JMH launcher. It also maps optional `-Pjmh.*` project properties to JMH parameters, allowing an individual run
to be adjusted without editing benchmark annotations.
Explicit settings in a module’s `jmh {}` block take precedence (i.e. the managed property won't apply).

The new properties enable to override the defaults, in other words

* without `jmh.forks` or `jmh.threads`, forks and threads come from `@Fork` and `@Threads`; otherwise JMH defaults apply,
* without `jmh.includes`, JMH runs all discovered benchmarks,
* without `jmh.profilers`, no profiler is attached unless configured elsewhere.

```Gradle Kotlin DSL
plugins {
id("dd-trace-java.jmh-conventions")
}

jmh {
jmhVersion = libs.versions.jmh.get()
duplicateClassesStrategy = DuplicatesStrategy.EXCLUDE

// jvm, fork, profilers are managed by the jmh convention plugin
threads = 10 // Threads is enforced and jmh.threads won't be applied
}
```

| Property | Effect |
|-----------------|---------------------------------------------------------------|
| `jmh.includes` | Comma-separated benchmark name patterns to run (a subset run) |
| `jmh.profilers` | Comma-separated JMH profilers to attach (e.g. `stack`, `gc`) |
| `jmh.forks` | Overrides the fork count for a spot-check run |
| `jmh.threads` | Overrides the thread count for a spot-check run |

```bash
./gradlew :dd-trace-core:jmh -Pjmh.includes=SpanCreationBenchmark -Pjmh.forks=1 -Pjmh.threads=1 -PtestJvm=21
```

**Tips:**

* Keep the benchmark's fork and thread settings aligned with what it measures.
* Use `-Pjmh.profilers=gc` when investigating allocations.

### `tracerJava` Extension

Manages multi-version Java source sets, allowing a single project to compile code targeting different JVM versions.
Expand Down Expand Up @@ -1576,4 +1619,3 @@ The report shows exactly which code paths capture disallowed references.
# Validate build logic without running tasks
./gradlew help --scan
```

Loading