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
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import com.smplio.gradle.build.insights.modules.graph.GraphBuilder
import com.smplio.gradle.build.insights.modules.load.SystemLoadModule
import com.smplio.gradle.build.insights.modules.timing.ExecutionTimeMeasurementModule
import com.smplio.gradle.build.insights.report.CompositeReportBuildService
import com.smplio.gradle.build.insights.report.IReporter
import com.smplio.gradle.build.insights.report.impl.html.HTMLReporter
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.plugins.ReportingBasePlugin
import org.gradle.api.provider.Provider
import org.gradle.build.event.BuildEventsListenerRegistry
import javax.inject.Inject

Expand Down Expand Up @@ -39,17 +41,17 @@ class GradleInsightsPlugin @Inject constructor(private val registry: BuildEvents
CompositeReportBuildService::class.java.simpleName,
CompositeReportBuildService::class.java,
) { buildServiceSpec ->
buildServiceSpec.parameters.reporters.set(mutableListOf(
pluginConfig.getExecutionTimeMeasurementConfiguration().executionTimeReporter.get(),
buildServiceSpec.parameters.reporters.set(mutableListOf<Provider<IReporter>>(
pluginConfig.getExecutionTimeMeasurementConfiguration().executionStatsReporter as Provider<IReporter>,
).also { list ->
if (pluginConfig.gatherHtmlReport.get()) {
list.add(HTMLReporter(project))
list.add(project.provider { HTMLReporter(project) })
}
})
executionTimeMeasurementModule.getConfigurationTimeReportProvider().let {
buildServiceSpec.parameters.configurationTimeReportProvider.set(it)
executionTimeMeasurementModule.getConfigurationTimeTimeMeasurementService()?.let {
buildServiceSpec.parameters.configurationTimeReportService.set(it)
}
executionTimeMeasurementModule.getExecutionTimeReportProvider()?.let {
executionTimeMeasurementModule.getExecutionTimeTimeMeasurementService()?.let {
buildServiceSpec.parameters.executionTimeReportService.set(it)
}
systemLoadModule.getSystemLoadReportProvider()?.let {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.smplio.gradle.build.insights.modules.load
import com.codahale.metrics.Gauge
import com.codahale.metrics.MetricRegistry
import java.lang.management.ManagementFactory
import java.time.Duration

sealed class SystemLoadMetric<T: Number>(val name: String, val metricProvider: () -> T): Gauge<T> {

Expand All @@ -22,6 +23,96 @@ sealed class SystemLoadMetric<T: Number>(val name: String, val metricProvider: (
val memoryBean = ManagementFactory.getMemoryMXBean()
memoryBean.heapMemoryUsage.used
})

// CPU usage (percent) for everything running inside the current Gradle JVM (100% equals one fully utilized core)
class GradleJvmCpuPercentMetric : SystemLoadMetric<Double>("gradleJvmCpuPercent", {
CpuLoadSampler.sampleJvmProcessCpuPercent()
})

// CPU usage (percent) aggregated across all descendant processes started by Gradle that run outside this JVM (100% equals one fully utilized core)
class GradleDescendantsCpuPercentMetric : SystemLoadMetric<Double>("gradleDescendantsCpuPercent", {
CpuLoadSampler.sampleChildrenCpuPercent()
})

private object CpuLoadSampler {
private val processors: Int = Runtime.getRuntime().availableProcessors().coerceAtLeast(1)

// JVM process sampling state
private var lastWallTimeJvmNanos: Long = System.nanoTime()
private var lastCpuTimeJvmNanos: Long = currentJvmProcessCpuTimeNanos()

// Children sampling state
private var lastWallTimeChildrenNanos: Long = System.nanoTime()
private var lastCpuTimeChildrenNanos: Long = currentChildrenCpuTimeNanos()

@Synchronized
fun sampleJvmProcessCpuPercent(): Double {
val now = System.nanoTime()
val cpuNow = currentJvmProcessCpuTimeNanos()
val deltaCpu = (cpuNow - lastCpuTimeJvmNanos).coerceAtLeast(0L)
val deltaWall = (now - lastWallTimeJvmNanos).coerceAtLeast(1L)
lastCpuTimeJvmNanos = cpuNow
lastWallTimeJvmNanos = now
val pct = (deltaCpu.toDouble() / deltaWall.toDouble()) * 100.0
return pct.coerceIn(0.0, 100.0 * processors)
}

@Synchronized
fun sampleChildrenCpuPercent(): Double {
val now = System.nanoTime()
val cpuNow = currentChildrenCpuTimeNanos()
val deltaCpu = (cpuNow - lastCpuTimeChildrenNanos).coerceAtLeast(0L)
val deltaWall = (now - lastWallTimeChildrenNanos).coerceAtLeast(1L)
lastCpuTimeChildrenNanos = cpuNow
lastWallTimeChildrenNanos = now
val pct = (deltaCpu.toDouble() / deltaWall.toDouble()) * 100.0
// With 100% meaning one full core, allow up to cores*100%
return pct.coerceIn(0.0, 100.0 * processors)
}

private fun currentJvmProcessCpuTimeNanos(): Long {
// Prefer com.sun.management.OperatingSystemMXBean for precise JVM CPU time
val osBean = ManagementFactory.getOperatingSystemMXBean()
val cpuTimeFromOsBean = try {
val sunBean = osBean as? com.sun.management.OperatingSystemMXBean
sunBean?.processCpuTime ?: -1L
} catch (_: Throwable) {
-1L
}
if (cpuTimeFromOsBean >= 0L) return cpuTimeFromOsBean

// Fallback to ProcessHandle info
return try {
val duration: Duration? = ProcessHandle.current().info().totalCpuDuration().orElse(null)
duration?.toNanos() ?: 0L
} catch (_: Throwable) {
0L
}
}

private fun currentChildrenCpuTimeNanos(): Long {
return try {
var sum = 0L
val current = ProcessHandle.current()
// Use descendants to include nested children
current.descendants().forEach { descendant ->
try {
val d: Duration? = descendant.info().totalCpuDuration().orElse(null)
if (d != null) {
sum += d.toNanos()
}
} catch (e: Throwable) {
// ignore processes we cannot inspect
e.printStackTrace()
}
}
sum
} catch (e: Throwable) {
e.printStackTrace()
0L
}
}
}
}

fun <T: Number> MetricRegistry.register(metric: SystemLoadMetric<T>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ abstract class SystemLoadService: BuildService<BuildServiceParameters.None>,
registry.register(SystemLoadMetric.HeapUsedMetric())
registry.register(SystemLoadMetric.HeapMaxMetric())

registry.register(SystemLoadMetric.GradleJvmCpuPercentMetric())
registry.register(SystemLoadMetric.GradleDescendantsCpuPercentMetric())

metricsReporter = LocalCacheReporter(
registry,
"LocalCacheRegistry",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.smplio.gradle.build.insights.modules.timing

import com.smplio.gradle.build.insights.modules.timing.report.ConsoleExecutionTimeReporter
import com.smplio.gradle.build.insights.report.execution.IExecutionStatsReceiver
import com.smplio.gradle.build.insights.report.timing.ITaskExecutionTimeReportReceiver
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
Expand All @@ -9,9 +10,9 @@ import javax.inject.Inject
abstract class ExecutionTimeMeasurementConfiguration @Inject constructor(objects: ObjectFactory) {
val enabled: Property<Boolean> = objects.property(Boolean::class.java).convention(true)

val executionTimeReporter: Property<ITaskExecutionTimeReportReceiver> = (objects.property(
ITaskExecutionTimeReportReceiver::class.java
) as Property<ITaskExecutionTimeReportReceiver>).convention(
val executionStatsReporter: Property<IExecutionStatsReceiver> = (objects.property(
IExecutionStatsReceiver::class.java
) as Property<IExecutionStatsReceiver>).convention(
ConsoleExecutionTimeReporter()
)
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package com.smplio.gradle.build.insights.modules.timing

import com.smplio.gradle.build.insights.report.timing.IConfigurationTimeReportProvider
import com.smplio.gradle.build.insights.modules.timing.report_providers.ConfigurationTimeMeasurementService
import org.gradle.api.Project
import org.gradle.build.event.BuildEventsListenerRegistry
import com.smplio.gradle.build.insights.modules.timing.report_providers.ConfigurationTimeReportProvider
import com.smplio.gradle.build.insights.modules.timing.report_providers.TaskTaskExecutionTimeMeasurementService
import com.smplio.gradle.build.insights.modules.timing.report_providers.TaskExecutionTimeMeasurementService
import org.gradle.api.provider.Provider

class ExecutionTimeMeasurementModule(
Expand All @@ -13,24 +12,34 @@ class ExecutionTimeMeasurementModule(
private val configuration: ExecutionTimeMeasurementConfiguration,
) {

private var taskExecutionTimeMeasurementService: Provider<TaskTaskExecutionTimeMeasurementService>? = null
private val configurationTimeReportProvider = ConfigurationTimeReportProvider()
private var configurationTimeMeasurementService: Provider<ConfigurationTimeMeasurementService>? = null
private var taskExecutionTimeMeasurementService: Provider<TaskExecutionTimeMeasurementService>? = null

fun initialize() {
val buildStartTime = System.currentTimeMillis()

project.gradle.beforeProject {
configurationTimeReportProvider.onBeforeProject(it)
}
if (configuration.enabled.get()) {
configurationTimeMeasurementService = project.gradle.sharedServices.registerIfAbsent(
ConfigurationTimeMeasurementService::class.java.simpleName,
ConfigurationTimeMeasurementService::class.java,
) {}.also {
registry.onTaskCompletion(it)
}

project.gradle.afterProject {
configurationTimeReportProvider.onAfterProject(it)
configurationTimeMeasurementService?.get()?.onBeforeProject(project)

project.gradle.beforeProject {
configurationTimeMeasurementService?.get()?.onBeforeProject(it)
}
project.gradle.afterProject {
configurationTimeMeasurementService?.get()?.onAfterProject(it)
}
}

if (configuration.enabled.get()) {
taskExecutionTimeMeasurementService = project.gradle.sharedServices.registerIfAbsent(
TaskTaskExecutionTimeMeasurementService::class.java.simpleName,
TaskTaskExecutionTimeMeasurementService::class.java,
TaskExecutionTimeMeasurementService::class.java.simpleName,
TaskExecutionTimeMeasurementService::class.java,
) {
it.parameters.buildStartTime.set(buildStartTime)
}.also {
Expand All @@ -39,11 +48,11 @@ class ExecutionTimeMeasurementModule(
}
}

fun getConfigurationTimeReportProvider(): IConfigurationTimeReportProvider {
return configurationTimeReportProvider
fun getConfigurationTimeTimeMeasurementService(): Provider<ConfigurationTimeMeasurementService>? {
return configurationTimeMeasurementService
}

fun getExecutionTimeReportProvider(): Provider<TaskTaskExecutionTimeMeasurementService>? {
fun getExecutionTimeTimeMeasurementService(): Provider<TaskExecutionTimeMeasurementService>? {
return taskExecutionTimeMeasurementService
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
package com.smplio.gradle.build.insights.modules.timing.report
import com.smplio.gradle.build.insights.report.execution.IExecutionStatsReceiver
import com.smplio.gradle.build.insights.report.timing.IConfigurationTimeReportReceiver
import com.smplio.gradle.build.insights.report.timing.ITaskExecutionTimeReportReceiver
import java.time.Duration

class ConsoleExecutionTimeReporter: IConfigurationTimeReportReceiver, ITaskExecutionTimeReportReceiver {
class ConsoleExecutionTimeReporter:
IExecutionStatsReceiver,
IConfigurationTimeReportReceiver,
ITaskExecutionTimeReportReceiver
{

private var configurationTimeReport: ConfigurationTimeReport? = null
private var taskExecutionTimeReport: TaskExecutionTimeReport? = null

override fun reportExecutionStats(stats: ExecutionStats) {
stats.configurationTimeline?.let { reportConfigurationTime(it) }
stats.taskExecutionTimeline?.let { reportTaskExecutionTime(it) }
}

override fun reportConfigurationTime(configurationTimeReport: ConfigurationTimeReport) {
this.configurationTimeReport = configurationTimeReport
}
Expand All @@ -24,7 +34,7 @@ class ConsoleExecutionTimeReporter: IConfigurationTimeReportReceiver, ITaskExecu
override fun submitReport() {
var firstConfigurationStartTime = 0L
var lastTaskEndTime = 0L
configurationTimeReport?.let { report ->
configurationTimeReport?.takeIf { it.isNotEmpty() }?.let { report ->
println("Configuration time:")
firstConfigurationStartTime = report.minOf { it.startTime }
val lastConfigurationEndTime = report.maxOf { it.endTime }
Expand All @@ -40,7 +50,7 @@ class ConsoleExecutionTimeReporter: IConfigurationTimeReportReceiver, ITaskExecu
}
}

taskExecutionTimeReport?.let { report ->
taskExecutionTimeReport?.takeIf { it.isNotEmpty() }?.let { report ->
println("Task execution time:")
val firstTaskStartTime = report.minOf { it.startTime }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@ import com.smplio.gradle.build.insights.modules.timing.models.Measured
import com.smplio.gradle.build.insights.modules.timing.report.ConfigurationTimeReport
import com.smplio.gradle.build.insights.report.timing.IConfigurationTimeReportProvider
import org.gradle.api.Project
import org.gradle.api.services.BuildService
import org.gradle.api.services.BuildServiceParameters
import org.gradle.tooling.events.FinishEvent
import org.gradle.tooling.events.OperationCompletionListener
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentLinkedQueue

class ConfigurationTimeReportProvider: IConfigurationTimeReportProvider {

abstract class ConfigurationTimeMeasurementService : BuildService<BuildServiceParameters.None>,
IConfigurationTimeReportProvider,
OperationCompletionListener
{
private val configurationStartTimes = ConcurrentHashMap<String, Long>()
private val configurationTimeline = mutableListOf<Measured<ConfigurationInfo>>()

override fun provideConfigurationTimeReport(): ConfigurationTimeReport? {
return configurationTimeline
}
private val configurationTimeline = ConcurrentLinkedQueue<Measured<ConfigurationInfo>>()

fun onBeforeProject(project: Project) {
configurationStartTimes[project.displayName] = System.currentTimeMillis()
Expand All @@ -30,4 +33,10 @@ class ConfigurationTimeReportProvider: IConfigurationTimeReportProvider {
endTime = System.currentTimeMillis(),
))
}

override fun onFinish(event: FinishEvent) {}

override fun provideConfigurationTimeReport(): ConfigurationTimeReport? {
return configurationTimeline.toList()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import org.gradle.tooling.events.task.TaskSkippedResult
import org.gradle.tooling.events.task.TaskSuccessResult
import java.util.concurrent.ConcurrentLinkedQueue

abstract class TaskTaskExecutionTimeMeasurementService : BuildService<TaskTaskExecutionTimeMeasurementService.Parameters>,
abstract class TaskExecutionTimeMeasurementService : BuildService<TaskExecutionTimeMeasurementService.Parameters>,
OperationCompletionListener,
ITaskExecutionTimeReportProvider,
IReportProvider
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ import com.smplio.gradle.build.insights.modules.timing.models.Measured
import com.smplio.gradle.build.insights.modules.timing.models.TaskInfo
import com.smplio.gradle.build.insights.modules.timing.report.BuildHostInfo
import com.smplio.gradle.build.insights.modules.timing.report.ExecutionStats
import com.smplio.gradle.build.insights.report.timing.IConfigurationTimeReportProvider
import com.smplio.gradle.build.insights.modules.timing.report_providers.ConfigurationTimeMeasurementService
import com.smplio.gradle.build.insights.report.timing.IConfigurationTimeReportReceiver
import com.smplio.gradle.build.insights.report.timing.ITaskExecutionTimeReportReceiver
import com.smplio.gradle.build.insights.modules.timing.report_providers.TaskTaskExecutionTimeMeasurementService
import com.smplio.gradle.build.insights.modules.timing.report_providers.TaskExecutionTimeMeasurementService
import com.smplio.gradle.build.insights.report.execution.IExecutionStatsReceiver
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.services.BuildService
import org.gradle.api.services.BuildServiceParameters
import org.gradle.tooling.events.FinishEvent
Expand All @@ -26,17 +27,17 @@ abstract class CompositeReportBuildService : BuildService<CompositeReportBuildSe
AutoCloseable
{
interface Parameters: BuildServiceParameters {
val reporters: ListProperty<IReporter>
val configurationTimeReportProvider: Property<IConfigurationTimeReportProvider>
val reporters: ListProperty<Provider<IReporter>>
val systemLoadReportService: Property<SystemLoadService>
val executionTimeReportService: Property<TaskTaskExecutionTimeMeasurementService>
val configurationTimeReportService: Property<ConfigurationTimeMeasurementService>
val executionTimeReportService: Property<TaskExecutionTimeMeasurementService>
}

override fun close() {
val reporters = parameters.reporters.orNull ?: return
val reporters = parameters.reporters.orNull?.map { it.get() } ?: return
val systemLoadService = parameters.systemLoadReportService.orNull ?: return
val executionTimeReportService = parameters.executionTimeReportService.orNull ?: return
val configurationTimeReportProvider = parameters.configurationTimeReportProvider.orNull ?: return
val configurationTimeReportProvider = parameters.configurationTimeReportService.orNull ?: return

reporters.filterIsInstance<ISystemLoadReportReceiver>().forEach { reporter ->
systemLoadService.provideSystemLoadReport()?.let {
Expand All @@ -59,6 +60,7 @@ abstract class CompositeReportBuildService : BuildService<CompositeReportBuildSe
reporters.filterIsInstance<IExecutionStatsReceiver>().forEach { reporter ->
val configurationTimeline = configurationTimeReportProvider.provideConfigurationTimeReport()
val taskExecutionTimeline = executionTimeReportService.provideTaskExecutionTimeReport()

reporter.reportExecutionStats(ExecutionStats(
buildHostInfo = BuildHostInfo(),
buildInfo = Measured(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ package com.smplio.gradle.build.insights.report
import java.io.Serializable

interface IReporter: Serializable {
fun submitReport()
fun submitReport() {}
}
Loading
Loading