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
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ profile-out*

build
!.github/actions/build
!*/src/**/build
!*/*/src/**/build
!**/src/**/build
.ipynb_checkpoints

.venv
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ A Kotlin library to access the [Develocity API][1], easy to use from:
- [Jupyter notebooks with the Kotlin kernel][29]
- [Kotlin scripts (`kts`)][27]
- [Kotlin projects][28]
- [Gradle tasks][36]

```kotlin
val api = DevelocityApi.newInstance()
Expand Down Expand Up @@ -232,3 +233,4 @@ For general discussions or questions, feel free to reach out to maintainers on t
[33]: https://github.com/gradle/develocity-api-samples
[34]: https://github.com/gabrielfeo/develocity-api-kotlin/blob/main/build-logic/src/functionalTest/kotlin/com/gabrielfeo/task/PostProcessGeneratedApiTest.kt#L21
[35]: https://community.gradle.org/#community-channels
[36]: ./examples/example-gradle-task/
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ kotlin {

val examples = fileTree(rootDir) {
include("examples/**")
exclude("**/build", "**/.*")
exclude {
it.isDirectory
&& (it.name == "build" || it.name.startsWith("."))
&& !it.path.endsWith("build-logic/src/main/kotlin/build")
}
}

tasks.named("processExamplesTestResources", ProcessResources::class) {
Expand Down
20 changes: 20 additions & 0 deletions examples/example-gradle-task/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@


# Example usage in build logic

This example shows how to create a reusable Gradle plugin that adds a `userBuildPerformanceMetrics` task to fetch and print build performance metrics for a specific user, using the Develocity API Kotlin client.

## Core files

- [`build-logic/src/main/kotlin/build/logic/PerformanceMetricsTask.kt`](./build-logic/src/main/kotlin/build/logic/PerformanceMetricsTask.kt): Implements a custom Gradle task that fetches and prints build performance metrics of a given user.
- [`build-logic/src/main/kotlin/build/logic/DevelocityApiService.kt`](./build-logic/src/main/kotlin/build/logic/DevelocityApiService.kt): Defines a shared build service containing the Develocity API client, which could be used by multiple tasks while ensuring a singleton client per build.
- [`build-logic/performance-metrics-plugin.gradle.kts`](./build-logic/performance-metrics-plugin.gradle.kts): A plugin which registers the task where it's applied.
- [`build.gradle.kts`](./build.gradle.kts): Applies `performance-metrics-plugin`, making the task available for this build.

## Usage

Run:

```sh
./gradlew userBuildPerformanceMetrics [--user=foo] --period=[-1d|-7d|-14d|-30d|...]
```
8 changes: 8 additions & 0 deletions examples/example-gradle-task/build-logic/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
plugins {
`kotlin-dsl`
}

dependencies {
implementation("com.gabrielfeo:develocity-api-kotlin:2024.3.0")
implementation("org.apache.commons:commons-math3:3.6.1")
}
8 changes: 8 additions & 0 deletions examples/example-gradle-task/build-logic/settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
rootProject.name = "dak-example-gradle-task-build-logic"

dependencyResolutionManagement {
repositories {
mavenCentral()
gradlePluginPortal()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package build.logic

import com.gabrielfeo.develocity.api.Config
import com.gabrielfeo.develocity.api.DevelocityApi
import org.gradle.api.services.BuildService
import org.gradle.api.services.BuildServiceParameters
import okhttp3.OkHttpClient

abstract class DevelocityApiService
: DevelocityApi by DevelocityApi.newInstance(config()),
BuildService<BuildServiceParameters.None>,
AutoCloseable {

override fun close() {
shutdown()
}
}

private fun config() = Config(
// Necessary to accomodate Gradle's build service lifecycle because library
// uses a singleton OkHttpClient.Builder unless one is provided.
// See https://github.com/gabrielfeo/develocity-api-kotlin/issues/451
clientBuilder = OkHttpClient.Builder(),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package build.logic

import com.gabrielfeo.develocity.api.DevelocityApi
import kotlinx.coroutines.runBlocking
import com.gabrielfeo.develocity.api.model.BuildModelName
import com.gabrielfeo.develocity.api.model.Build
import com.gabrielfeo.develocity.api.model.GradleBuildCachePerformance
import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics
import org.gradle.api.DefaultTask
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.options.Option
import org.gradle.api.services.ServiceReference
import java.time.Duration


abstract class PerformanceMetricsTask(

) : DefaultTask() {

@get:Optional
@get:Input
@get:Option(
option = "user",
description = "The user to query builds for. Defaults to the current OS username."
)
abstract val user: Property<String>

@get:Optional
@get:Input
@get:Option(
option = "period",
description = "The period to query builds for (e.g. -14d, -30d, etc). Default: -14d."
)
abstract val period: Property<String>

@get:ServiceReference
abstract val api: Property<DevelocityApiService>

@TaskAction
fun run() {
val user = user.getOrElse(System.getProperty("user.name"))
val startTime = period.getOrElse("-14d")
val metrics = runBlocking {
getUserBuildsPerformanceMetrics(api.get(), user, startTime)
}
logger.quiet(metrics)
}

suspend fun getUserBuildsPerformanceMetrics(
api: DevelocityApi,
user: String,
startTime: String,
): String {
val query = """user:"$user" buildStartTime>$startTime"""
val buildsPerformanceData = fetchBuildsPerformanceData(api, query)
val serializationFactors = buildsPerformanceData
.map { it.serializationFactor }
.let { DescriptiveStatistics(it.toDoubleArray()) }
val avoidanceSavings = buildsPerformanceData
.map { it.workUnitAvoidanceSavingsSummary.ratio }
.let { DescriptiveStatistics(it.toDoubleArray()) }
val heading = "Build performance overview for $user since $startTime (powered by Develocity®)"
return """
|
|${"\u001B[1;36m"}$heading${"\u001B[0m"}
| ▶︎ Serialization factor: %.1fx
| (Gradle's parallel execution)
| ⏩︎ Avoidance savings: %.1f%% (mean) ~ %.1f%% (p95)
| (Gradle and Develocity's mechanisms, incl. incremental build and remote cache)
""".trimMargin().format(
serializationFactors.mean,
avoidanceSavings.mean,
avoidanceSavings.getPercentile(95.0),
)
}

private suspend fun fetchBuildsPerformanceData(
api: DevelocityApi,
query: String,
): List<GradleBuildCachePerformance> {
return api.buildsApi.getBuilds(
fromInstant = 0,
query = query,
models = listOf(BuildModelName.gradleBuildCachePerformance),
).mapNotNull { build ->
build.models?.gradleBuildCachePerformance?.model
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package build.logic

gradle.sharedServices.registerIfAbsent("develocityApiService", DevelocityApiService::class)

tasks.register<PerformanceMetricsTask>("userBuildPerformanceMetrics") {
group = "Develocity"
description = "Retrieves performance metrics for the user's builds from Develocity API."
}
3 changes: 3 additions & 0 deletions examples/example-gradle-task/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
plugins {
id("build.logic.performance-metrics-plugin")
}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=bd71102213493060956ec229d946beee57158dbd89d0e62b91bca0fa2c5f3531
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Loading
Loading