Skip to content

Commit 88db604

Browse files
authored
Simplify tasks partitioning UX (#10158)
* chore: simpler UX to mention a slot * chore: Show slot distribution * chore: Simple warning when this DSL plugin is applied on non-root * chore: Use node index rather than normalized node index * chore: Avoid unnecessary slot validation, for non-run tasks. * chore: Handle no value when gitlab job is not in a matrix
1 parent 44c4979 commit 88db604

File tree

3 files changed

+71
-35
lines changed

3 files changed

+71
-35
lines changed

.gitlab-ci.yml

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,7 @@ default:
109109
.normalize_node_index: &normalize_node_index
110110
- if [ "$CI_NO_SPLIT" == "true" ] ; then CI_NODE_INDEX=1; CI_NODE_TOTAL=1; fi # A job uses parallel but doesn't intend to split by index
111111
- if [ -n "$CI_SPLIT" ]; then CI_NODE_INDEX="${CI_SPLIT%%/*}"; CI_NODE_TOTAL="${CI_SPLIT##*/}"; fi
112-
- echo "CI_NODE_TOTAL=${CI_NODE_TOTAL}, CI_NODE_INDEX=$CI_NODE_INDEX"
113-
- export NORMALIZED_NODE_TOTAL=${CI_NODE_TOTAL:-1}
114-
- ONE_INDEXED_NODE_INDEX=${CI_NODE_INDEX:-1}; export NORMALIZED_NODE_INDEX=$((ONE_INDEXED_NODE_INDEX - 1))
115-
- echo "NORMALIZED_NODE_TOTAL=${NORMALIZED_NODE_TOTAL}, NORMALIZED_NODE_INDEX=$NORMALIZED_NODE_INDEX"
112+
- echo "CI_NODE_INDEX=$CI_NODE_INDEX, CI_NODE_TOTAL=${CI_NODE_TOTAL}"
116113

117114
.cgroup_info: &cgroup_info
118115
- source .gitlab/gitlab-utils.sh
@@ -420,7 +417,7 @@ test_published_artifacts:
420417
script:
421418
- *gitlab_base_ref_params
422419
- ./gradlew --version
423-
- ./gradlew $GRADLE_TARGET -x spotlessCheck $GRADLE_PARAMS -PskipTests -PrunBuildSrcTests -PtaskPartitionCount=$NORMALIZED_NODE_TOTAL -PtaskPartition=$NORMALIZED_NODE_INDEX $GRADLE_ARGS
420+
- ./gradlew $GRADLE_TARGET -x spotlessCheck $GRADLE_PARAMS -PskipTests -PrunBuildSrcTests -Pslot=$CI_NODE_INDEX/$CI_NODE_TOTAL $GRADLE_ARGS
424421
after_script:
425422
- *cgroup_info
426423
- source .gitlab/gitlab-utils.sh
@@ -488,7 +485,7 @@ muzzle:
488485
script:
489486
- export SKIP_BUILDSCAN="true"
490487
- ./gradlew --version
491-
- ./gradlew :runMuzzle -PtaskPartitionCount=$NORMALIZED_NODE_TOTAL -PtaskPartition=$NORMALIZED_NODE_INDEX $GRADLE_ARGS
488+
- ./gradlew :runMuzzle -Pslot=$CI_NODE_INDEX/$CI_NODE_TOTAL $GRADLE_ARGS
492489
after_script:
493490
- *cgroup_info
494491
- source .gitlab/gitlab-utils.sh
@@ -570,7 +567,7 @@ muzzle-dep-report:
570567
- *prepare_test_env
571568
- export GRADLE_OPTS="-Dorg.gradle.jvmargs='-Xms$GRADLE_MEM -Xmx$GRADLE_MEM $PROFILER_COMMAND -XX:ErrorFile=/tmp/hs_err_pid%p.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp' -Ddatadog.forkedMaxHeapSize=1024M -Ddatadog.forkedMinHeapSize=128M"
572569
- ./gradlew --version
573-
- ./gradlew $GRADLE_TARGET $GRADLE_PARAMS -PtestJvm=$testJvm -PtaskPartitionCount=$NORMALIZED_NODE_TOTAL -PtaskPartition=$NORMALIZED_NODE_INDEX $GRADLE_ARGS --continue || $CONTINUE_ON_FAILURE
570+
- ./gradlew $GRADLE_TARGET $GRADLE_PARAMS -PtestJvm=$testJvm -Pslot=$CI_NODE_INDEX/$CI_NODE_TOTAL $GRADLE_ARGS --continue || $CONTINUE_ON_FAILURE
574571
after_script:
575572
- *restore_pretest_env
576573
- *set_datadog_api_keys

buildSrc/src/main/kotlin/datadog/gradle/plugin/ci/CIJobsExtensions.kt

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,54 @@ package datadog.gradle.plugin.ci
22

33
import org.gradle.api.Project
44
import org.gradle.api.Task
5+
import org.gradle.api.provider.Provider
56
import org.gradle.kotlin.dsl.extra
7+
import kotlin.math.abs
8+
9+
/**
10+
* Determines if the current project is in the selected slot.
11+
*
12+
* The "slot" property should be provided in the format "X/Y", where X is the selected slot (1-based)
13+
* and Y is the total number of slots.
14+
*
15+
* If the "slot" property is not provided, all projects are considered to be in the selected slot.
16+
*/
17+
val Project.isInSelectedSlot: Provider<Boolean>
18+
get() = rootProject.providers.gradleProperty("slot").map { slot ->
19+
val parts = slot.split("/")
20+
if (parts.size != 2) {
21+
project.logger.warn("Invalid slot format '{}', expected 'X/Y'. Treating all projects as selected.", slot)
22+
return@map true
23+
}
24+
25+
val selectedSlot = parts[0].toIntOrNull()
26+
val totalSlots = parts[1].toIntOrNull()
27+
28+
if (selectedSlot == null || totalSlots == null || totalSlots <= 0) {
29+
project.logger.warn("Invalid slot values '{}', expected numeric 'X/Y' with Y > 0. Treating all projects as selected.", slot)
30+
return@map true
31+
}
32+
33+
// Distribution numbers when running on rootProject.allprojects indicates
34+
// bucket sizes are reasonably balanced:
35+
//
36+
// * size 4 distribution: {2=146, 0=143, 1=157, 3=145}
37+
// * size 6 distribution: {4=100, 0=92, 3=97, 2=97, 1=108, 5=97}
38+
// * size 8 distribution: {2=62, 4=72, 0=71, 5=70, 7=78, 6=84, 1=87, 3=67}
39+
// * size 10 distribution: {8=62, 0=65, 5=70, 9=59, 3=54, 1=56, 6=63, 4=47, 2=52, 7=63}
40+
// * size 12 distribution: {10=55, 0=47, 4=45, 9=46, 8=51, 3=51, 2=46, 1=59, 5=52, 7=49, 11=45, 6=45}
41+
val projectSlot = abs(project.path.hashCode() % totalSlots) + 1 // Convert to 1-based
42+
43+
project.logger.info(
44+
"Project {} assigned to slot {}/{}, active slot is {}",
45+
project.path,
46+
projectSlot,
47+
totalSlots,
48+
selectedSlot,
49+
)
50+
51+
projectSlot == selectedSlot
52+
}.orElse(true)
653

754
/**
855
* Returns the task's path, given affected projects, if this task or its dependencies are affected by git changes.
@@ -46,9 +93,8 @@ private fun Project.createRootTask(
4693
val coverage = forceCoverage || rootProject.providers.gradleProperty("checkCoverage").isPresent
4794
tasks.register(rootTaskName) {
4895
subprojects.forEach { subproject ->
49-
val activePartition = subproject.extra.get("activePartition") as Boolean
5096
if (
51-
activePartition &&
97+
isInSelectedSlot.get() &&
5298
includePrefixes.any { subproject.path.startsWith(it) } &&
5399
!excludePrefixes.any { subproject.path.startsWith(it) }
54100
) {

buildSrc/src/main/kotlin/dd-trace-java.ci-jobs.gradle.kts

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,25 @@
1-
/*
2-
* This plugin defines a set of tasks to be used in CI. These aggregate tasks support partitioning (to parallelize
3-
* jobs) with -PtaskPartitionCount and -PtaskPartition, and limiting tasks to those affected by git changes
4-
* with -PgitBaseRef.
5-
*/
6-
1+
import datadog.gradle.plugin.ci.isInSelectedSlot
72
import org.gradle.api.tasks.testing.Test
83
import java.io.File
9-
import kotlin.math.abs
104

11-
// Set up activePartition property on all projects
12-
allprojects {
13-
extra.set("activePartition", true)
14-
15-
val taskPartitionCountProvider = rootProject.providers.gradleProperty("taskPartitionCount")
16-
val taskPartitionProvider = rootProject.providers.gradleProperty("taskPartition")
17-
if (taskPartitionCountProvider.isPresent && taskPartitionProvider.isPresent) {
18-
val taskPartitionCount = taskPartitionCountProvider.get()
19-
val taskPartition = taskPartitionProvider.get()
20-
val currentTaskPartition = abs(project.path.hashCode() % taskPartitionCount.toInt())
21-
extra.set("activePartition", currentTaskPartition == taskPartition.toInt())
22-
}
5+
/*
6+
* This plugin defines a set of tasks to be used in CI.
7+
*
8+
* These aggregate tasks support partitioning (to parallelize jobs) with
9+
* `-Pslot=x/y`, and limiting tasks to those affected by git changes with
10+
* `-PgitBaseRef`.
11+
*/
2312

24-
// Disable test tasks if not in active partition
25-
val activePartitionProvider = providers.provider {
26-
project.extra.properties["activePartition"] as? Boolean ?: true
27-
}
13+
if (project != rootProject) {
14+
logger.error("This plugin has been applied on a non-root project: ${project.path}")
15+
}
2816

17+
allprojects {
18+
// Enable tests only on the selected slot (if -Pslot=n/t is provided)
2919
tasks.withType<Test>().configureEach {
30-
enabled = activePartitionProvider.get()
20+
onlyIf("Project is in selected slot") {
21+
project.isInSelectedSlot.get()
22+
}
3123
}
3224
}
3325

@@ -132,8 +124,9 @@ if (gitBaseRefProvider.isPresent) {
132124

133125
tasks.register("runMuzzle") {
134126
val muzzleSubprojects = subprojects.filter { p ->
135-
val activePartition = p.extra.get("activePartition") as Boolean
136-
activePartition && p.plugins.hasPlugin("java") && p.plugins.hasPlugin("dd-trace-java.muzzle")
127+
p.isInSelectedSlot.get()
128+
&& p.plugins.hasPlugin("java")
129+
&& p.plugins.hasPlugin("dd-trace-java.muzzle")
137130
}
138131
dependsOn(muzzleSubprojects.map { p -> "${p.path}:muzzle" })
139132
}

0 commit comments

Comments
 (0)