From 90952ca14c18976a4e4fe9dacc0705940e49f059 Mon Sep 17 00:00:00 2001 From: Dmytro Rodionov Date: Fri, 5 Sep 2025 14:58:19 +0200 Subject: [PATCH 1/5] Add more information to reports --- .../build/insights/GradleInsightsPlugin.kt | 5 +- .../insights/modules/load/SystemLoadMetric.kt | 91 +++++++++++++++++++ .../modules/load/SystemLoadService.kt | 3 + .../ExecutionTimeMeasurementConfiguration.kt | 7 +- .../timing/ExecutionTimeMeasurementModule.kt | 10 +- .../report/ConsoleExecutionTimeReporter.kt | 12 ++- .../ConfigurationTimeReportProvider.kt | 8 +- ...=> TaskExecutionTimeMeasurementService.kt} | 2 +- .../report/CompositeReportBuildService.kt | 8 +- .../gradle/build/insights/report/IReporter.kt | 2 +- .../execution/IExecutionStatsReceiver.kt | 3 +- .../insights/report/impl/html/HTMLReporter.kt | 14 ++- .../IConfigurationTimeReportReceiver.kt | 3 +- src/main/resources/build_charts.js | 33 +++++-- src/main/resources/script.js | 20 +--- 15 files changed, 174 insertions(+), 47 deletions(-) rename src/main/kotlin/com/smplio/gradle/build/insights/modules/timing/report_providers/{TaskTaskExecutionTimeMeasurementService.kt => TaskExecutionTimeMeasurementService.kt} (96%) diff --git a/src/main/kotlin/com/smplio/gradle/build/insights/GradleInsightsPlugin.kt b/src/main/kotlin/com/smplio/gradle/build/insights/GradleInsightsPlugin.kt index b536cb8..0b2fb45 100644 --- a/src/main/kotlin/com/smplio/gradle/build/insights/GradleInsightsPlugin.kt +++ b/src/main/kotlin/com/smplio/gradle/build/insights/GradleInsightsPlugin.kt @@ -35,12 +35,15 @@ class GradleInsightsPlugin @Inject constructor(private val registry: BuildEvents ) systemLoadModule.initialize() + val executionStatsReporter = pluginConfig.getExecutionTimeMeasurementConfiguration().executionStatsReporter.get() + println("Execution stats reporter is ${executionStatsReporter.javaClass.simpleName}") + val compositeReportBuildService = project.gradle.sharedServices.registerIfAbsent( CompositeReportBuildService::class.java.simpleName, CompositeReportBuildService::class.java, ) { buildServiceSpec -> buildServiceSpec.parameters.reporters.set(mutableListOf( - pluginConfig.getExecutionTimeMeasurementConfiguration().executionTimeReporter.get(), + executionStatsReporter, ).also { list -> if (pluginConfig.gatherHtmlReport.get()) { list.add(HTMLReporter(project)) diff --git a/src/main/kotlin/com/smplio/gradle/build/insights/modules/load/SystemLoadMetric.kt b/src/main/kotlin/com/smplio/gradle/build/insights/modules/load/SystemLoadMetric.kt index 1ae1841..da8023d 100644 --- a/src/main/kotlin/com/smplio/gradle/build/insights/modules/load/SystemLoadMetric.kt +++ b/src/main/kotlin/com/smplio/gradle/build/insights/modules/load/SystemLoadMetric.kt @@ -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(val name: String, val metricProvider: () -> T): Gauge { @@ -22,6 +23,96 @@ sealed class SystemLoadMetric(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("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("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 MetricRegistry.register(metric: SystemLoadMetric) { diff --git a/src/main/kotlin/com/smplio/gradle/build/insights/modules/load/SystemLoadService.kt b/src/main/kotlin/com/smplio/gradle/build/insights/modules/load/SystemLoadService.kt index 3d85c85..4052f83 100644 --- a/src/main/kotlin/com/smplio/gradle/build/insights/modules/load/SystemLoadService.kt +++ b/src/main/kotlin/com/smplio/gradle/build/insights/modules/load/SystemLoadService.kt @@ -21,6 +21,9 @@ abstract class SystemLoadService: BuildService, registry.register(SystemLoadMetric.HeapUsedMetric()) registry.register(SystemLoadMetric.HeapMaxMetric()) + registry.register(SystemLoadMetric.GradleJvmCpuPercentMetric()) + registry.register(SystemLoadMetric.GradleDescendantsCpuPercentMetric()) + metricsReporter = LocalCacheReporter( registry, "LocalCacheRegistry", diff --git a/src/main/kotlin/com/smplio/gradle/build/insights/modules/timing/ExecutionTimeMeasurementConfiguration.kt b/src/main/kotlin/com/smplio/gradle/build/insights/modules/timing/ExecutionTimeMeasurementConfiguration.kt index 0d10f8d..50a975a 100644 --- a/src/main/kotlin/com/smplio/gradle/build/insights/modules/timing/ExecutionTimeMeasurementConfiguration.kt +++ b/src/main/kotlin/com/smplio/gradle/build/insights/modules/timing/ExecutionTimeMeasurementConfiguration.kt @@ -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 @@ -9,9 +10,9 @@ import javax.inject.Inject abstract class ExecutionTimeMeasurementConfiguration @Inject constructor(objects: ObjectFactory) { val enabled: Property = objects.property(Boolean::class.java).convention(true) - val executionTimeReporter: Property = (objects.property( - ITaskExecutionTimeReportReceiver::class.java - ) as Property).convention( + val executionStatsReporter: Property = (objects.property( + IExecutionStatsReceiver::class.java + ) as Property).convention( ConsoleExecutionTimeReporter() ) } \ No newline at end of file diff --git a/src/main/kotlin/com/smplio/gradle/build/insights/modules/timing/ExecutionTimeMeasurementModule.kt b/src/main/kotlin/com/smplio/gradle/build/insights/modules/timing/ExecutionTimeMeasurementModule.kt index 0aae5df..c6bb16f 100644 --- a/src/main/kotlin/com/smplio/gradle/build/insights/modules/timing/ExecutionTimeMeasurementModule.kt +++ b/src/main/kotlin/com/smplio/gradle/build/insights/modules/timing/ExecutionTimeMeasurementModule.kt @@ -4,7 +4,7 @@ import com.smplio.gradle.build.insights.report.timing.IConfigurationTimeReportPr 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( @@ -13,7 +13,7 @@ class ExecutionTimeMeasurementModule( private val configuration: ExecutionTimeMeasurementConfiguration, ) { - private var taskExecutionTimeMeasurementService: Provider? = null + private var taskExecutionTimeMeasurementService: Provider? = null private val configurationTimeReportProvider = ConfigurationTimeReportProvider() fun initialize() { @@ -29,8 +29,8 @@ class ExecutionTimeMeasurementModule( 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 { @@ -43,7 +43,7 @@ class ExecutionTimeMeasurementModule( return configurationTimeReportProvider } - fun getExecutionTimeReportProvider(): Provider? { + fun getExecutionTimeReportProvider(): Provider? { return taskExecutionTimeMeasurementService } } diff --git a/src/main/kotlin/com/smplio/gradle/build/insights/modules/timing/report/ConsoleExecutionTimeReporter.kt b/src/main/kotlin/com/smplio/gradle/build/insights/modules/timing/report/ConsoleExecutionTimeReporter.kt index 878edaf..3f2dd05 100644 --- a/src/main/kotlin/com/smplio/gradle/build/insights/modules/timing/report/ConsoleExecutionTimeReporter.kt +++ b/src/main/kotlin/com/smplio/gradle/build/insights/modules/timing/report/ConsoleExecutionTimeReporter.kt @@ -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 } diff --git a/src/main/kotlin/com/smplio/gradle/build/insights/modules/timing/report_providers/ConfigurationTimeReportProvider.kt b/src/main/kotlin/com/smplio/gradle/build/insights/modules/timing/report_providers/ConfigurationTimeReportProvider.kt index a935c57..cbecb5a 100644 --- a/src/main/kotlin/com/smplio/gradle/build/insights/modules/timing/report_providers/ConfigurationTimeReportProvider.kt +++ b/src/main/kotlin/com/smplio/gradle/build/insights/modules/timing/report_providers/ConfigurationTimeReportProvider.kt @@ -6,14 +6,17 @@ import com.smplio.gradle.build.insights.modules.timing.report.ConfigurationTimeR import com.smplio.gradle.build.insights.report.timing.IConfigurationTimeReportProvider import org.gradle.api.Project import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ConcurrentLinkedQueue class ConfigurationTimeReportProvider: IConfigurationTimeReportProvider { private val configurationStartTimes = ConcurrentHashMap() - private val configurationTimeline = mutableListOf>() + private val configurationTimeline = ConcurrentLinkedQueue>() override fun provideConfigurationTimeReport(): ConfigurationTimeReport? { - return configurationTimeline + println("${this}: Number of reports ${configurationTimeline.size}") + + return configurationTimeline.toList() } fun onBeforeProject(project: Project) { @@ -29,5 +32,6 @@ class ConfigurationTimeReportProvider: IConfigurationTimeReportProvider { startTime = startTime, endTime = System.currentTimeMillis(), )) + println("${this}: Added new configuration, currentSize: ${configurationTimeline.size}") } } \ No newline at end of file diff --git a/src/main/kotlin/com/smplio/gradle/build/insights/modules/timing/report_providers/TaskTaskExecutionTimeMeasurementService.kt b/src/main/kotlin/com/smplio/gradle/build/insights/modules/timing/report_providers/TaskExecutionTimeMeasurementService.kt similarity index 96% rename from src/main/kotlin/com/smplio/gradle/build/insights/modules/timing/report_providers/TaskTaskExecutionTimeMeasurementService.kt rename to src/main/kotlin/com/smplio/gradle/build/insights/modules/timing/report_providers/TaskExecutionTimeMeasurementService.kt index ded1bc3..937c834 100644 --- a/src/main/kotlin/com/smplio/gradle/build/insights/modules/timing/report_providers/TaskTaskExecutionTimeMeasurementService.kt +++ b/src/main/kotlin/com/smplio/gradle/build/insights/modules/timing/report_providers/TaskExecutionTimeMeasurementService.kt @@ -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, +abstract class TaskExecutionTimeMeasurementService : BuildService, OperationCompletionListener, ITaskExecutionTimeReportProvider, IReportProvider diff --git a/src/main/kotlin/com/smplio/gradle/build/insights/report/CompositeReportBuildService.kt b/src/main/kotlin/com/smplio/gradle/build/insights/report/CompositeReportBuildService.kt index 43b00ba..41829ce 100644 --- a/src/main/kotlin/com/smplio/gradle/build/insights/report/CompositeReportBuildService.kt +++ b/src/main/kotlin/com/smplio/gradle/build/insights/report/CompositeReportBuildService.kt @@ -10,7 +10,7 @@ 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.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 @@ -29,7 +29,7 @@ abstract class CompositeReportBuildService : BuildService val configurationTimeReportProvider: Property val systemLoadReportService: Property - val executionTimeReportService: Property + val executionTimeReportService: Property } override fun close() { @@ -52,6 +52,7 @@ abstract class CompositeReportBuildService : BuildService().forEach { reporter -> configurationTimeReportProvider.provideConfigurationTimeReport()?.let { + println("Configuration time report size (IConfigurationTimeReportReceiver): ${it.size}") reporter.reportConfigurationTime(it) } } @@ -59,6 +60,9 @@ abstract class CompositeReportBuildService : BuildService().forEach { reporter -> val configurationTimeline = configurationTimeReportProvider.provideConfigurationTimeReport() val taskExecutionTimeline = executionTimeReportService.provideTaskExecutionTimeReport() + + println("Configuration time report size (IExecutionStatsReceiver): ${configurationTimeline?.size}") + reporter.reportExecutionStats(ExecutionStats( buildHostInfo = BuildHostInfo(), buildInfo = Measured( diff --git a/src/main/kotlin/com/smplio/gradle/build/insights/report/IReporter.kt b/src/main/kotlin/com/smplio/gradle/build/insights/report/IReporter.kt index d4cca61..cf00d2a 100644 --- a/src/main/kotlin/com/smplio/gradle/build/insights/report/IReporter.kt +++ b/src/main/kotlin/com/smplio/gradle/build/insights/report/IReporter.kt @@ -3,5 +3,5 @@ package com.smplio.gradle.build.insights.report import java.io.Serializable interface IReporter: Serializable { - fun submitReport() + fun submitReport() {} } \ No newline at end of file diff --git a/src/main/kotlin/com/smplio/gradle/build/insights/report/execution/IExecutionStatsReceiver.kt b/src/main/kotlin/com/smplio/gradle/build/insights/report/execution/IExecutionStatsReceiver.kt index 84d84e8..b62f6be 100644 --- a/src/main/kotlin/com/smplio/gradle/build/insights/report/execution/IExecutionStatsReceiver.kt +++ b/src/main/kotlin/com/smplio/gradle/build/insights/report/execution/IExecutionStatsReceiver.kt @@ -1,7 +1,8 @@ package com.smplio.gradle.build.insights.report.execution import com.smplio.gradle.build.insights.modules.timing.report.ExecutionStats +import com.smplio.gradle.build.insights.report.IReporter -interface IExecutionStatsReceiver { +interface IExecutionStatsReceiver: IReporter { fun reportExecutionStats(stats: ExecutionStats) } \ No newline at end of file diff --git a/src/main/kotlin/com/smplio/gradle/build/insights/report/impl/html/HTMLReporter.kt b/src/main/kotlin/com/smplio/gradle/build/insights/report/impl/html/HTMLReporter.kt index 1215b1f..0bebb0b 100644 --- a/src/main/kotlin/com/smplio/gradle/build/insights/report/impl/html/HTMLReporter.kt +++ b/src/main/kotlin/com/smplio/gradle/build/insights/report/impl/html/HTMLReporter.kt @@ -2,8 +2,10 @@ package com.smplio.gradle.build.insights.report.impl.html import com.smplio.gradle.build.insights.report.load.ISystemLoadReportReceiver import com.smplio.gradle.build.insights.modules.timing.report.ConfigurationTimeReport +import com.smplio.gradle.build.insights.modules.timing.report.ExecutionStats import com.smplio.gradle.build.insights.report.timing.IConfigurationTimeReportReceiver import com.smplio.gradle.build.insights.modules.timing.report.TaskExecutionTimeReport +import com.smplio.gradle.build.insights.report.execution.IExecutionStatsReceiver import com.smplio.gradle.build.insights.report.timing.ITaskExecutionTimeReportReceiver import org.gradle.api.Project import org.json.JSONArray @@ -17,7 +19,11 @@ import kotlin.io.path.absolutePathString class HTMLReporter( project: Project, -): IConfigurationTimeReportReceiver, ITaskExecutionTimeReportReceiver, ISystemLoadReportReceiver { +) : IExecutionStatsReceiver, + IConfigurationTimeReportReceiver, + ITaskExecutionTimeReportReceiver, + ISystemLoadReportReceiver +{ private val uniqueReportFolder = project.layout.buildDirectory.get().dir("build-report").dir(UUID.randomUUID().toString()).asFile private val styleCssPath = uniqueReportFolder.toPath().resolve("style.css").absolutePathString() private val reportHtmlFile = uniqueReportFolder.toPath().resolve("index.html").toFile() @@ -26,6 +32,11 @@ class HTMLReporter( private var executionTimeJson: String? = null private var systemLoadJson: String? = null + override fun reportExecutionStats(stats: ExecutionStats) { + stats.configurationTimeline?.let { reportConfigurationTime(it) } + stats.taskExecutionTimeline?.let { reportTaskExecutionTime(it) } + } + override fun reportTaskExecutionTime(taskExecutionTimeReport: TaskExecutionTimeReport) { val tasks = JSONArray() for (measuredTaskInfo in taskExecutionTimeReport) { @@ -43,6 +54,7 @@ class HTMLReporter( } override fun reportConfigurationTime(configurationTimeReport: ConfigurationTimeReport) { + println("Configuration time (HTMLReporter): ${configurationTimeReport.size}") val projects = JSONArray() for (measuredConfigurationInfo in configurationTimeReport) { val configurationInfo = measuredConfigurationInfo.measuredInstance diff --git a/src/main/kotlin/com/smplio/gradle/build/insights/report/timing/IConfigurationTimeReportReceiver.kt b/src/main/kotlin/com/smplio/gradle/build/insights/report/timing/IConfigurationTimeReportReceiver.kt index a0fbcda..dd493cf 100644 --- a/src/main/kotlin/com/smplio/gradle/build/insights/report/timing/IConfigurationTimeReportReceiver.kt +++ b/src/main/kotlin/com/smplio/gradle/build/insights/report/timing/IConfigurationTimeReportReceiver.kt @@ -1,7 +1,8 @@ package com.smplio.gradle.build.insights.report.timing import com.smplio.gradle.build.insights.modules.timing.report.ConfigurationTimeReport +import com.smplio.gradle.build.insights.report.IReporter -interface IConfigurationTimeReportReceiver { +interface IConfigurationTimeReportReceiver: IReporter { fun reportConfigurationTime(configurationTimeReport: ConfigurationTimeReport) } \ No newline at end of file diff --git a/src/main/resources/build_charts.js b/src/main/resources/build_charts.js index 5cad825..9c04c02 100644 --- a/src/main/resources/build_charts.js +++ b/src/main/resources/build_charts.js @@ -7,14 +7,15 @@ const minTime = Math.min(...labels); const heapMaxMetrics = systemStats.map(value => value.heapMax); const heapUsedMetrics = systemStats.map(value => value.heapUsed); -const systemLoadAverageMetrics = systemStats.map(value => value.systemLoadAverage); +const gradleJvmCpuPercentMetrics = systemStats.map(value => value.gradleJvmCpuPercent); +const gradleDescendantsCpuPercentMetrics = systemStats.map(value => value.gradleDescendantsCpuPercent); function formatBytes(bytes, decimals = 2) { - if (!+bytes) return '0 Bytes' + if (!+bytes) return '0 B' const k = 1024 const dm = decimals < 0 ? 0 : decimals - const sizes = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'] + const sizes = ['B', 'KB', 'MB', 'GB', 'TB'] const i = Math.floor(Math.log(bytes) / Math.log(k)) @@ -27,12 +28,21 @@ new Chart(cpuChart, { labels: labels.map(value => Math.round((value - minTime) / 1000) + 's'), datasets: [ { - label: 'CPU usage', - data: systemLoadAverageMetrics, - fill: false, - borderColor: 'rgb(75, 192, 192)', + label: 'Gradle CPU usage', + data: gradleJvmCpuPercentMetrics, + fill: true, + borderColor: 'rgb(85,160,223)', + backgroundColor: 'rgb(85,180,223)', tension: 0.1 - } + }, + { + label: 'Children CPU usage', + data: gradleDescendantsCpuPercentMetrics, + fill: true, + borderColor: 'rgb(84,105,220)', + backgroundColor: 'rgb(84,125,220)', + tension: 0.1, + }, ], }, options: { @@ -45,9 +55,13 @@ new Chart(cpuChart, { } }, plugins: { + stacked: true, legend: { align: 'start', - } + }, + tooltip: { + mode: 'index', + }, } } }); @@ -86,6 +100,7 @@ new Chart(memoryChart, { align: 'start', }, tooltip: { + mode: 'index', callbacks: { label: function(context) { let label = context.dataset.label || ''; diff --git a/src/main/resources/script.js b/src/main/resources/script.js index c6144b1..52eb26a 100644 --- a/src/main/resources/script.js +++ b/src/main/resources/script.js @@ -126,25 +126,7 @@ function calculateRows(tasks) { ]); sweepLineTasks.sort((a, b) => { - if (a.time !== b.time) { - return a.time - b.time; // Sort by time - } - - // If times are equal, end events come before start events - if (a.isStart && !b.isStart) { - return -1; - } - if (!a.isStart && b.isStart) { - return 1; - } - - // If both are start events at the same time, the one with longer duration comes first - if (a.isStart && b.isStart) { - return (b.task.end - b.task.start) - (a.task.end - a.task.start); - } - - // If both are end events at the same time, maintain order - return 0; + return a.time - b.time; // Sort by time }) let maxHeight = 0; From 0f47e579d0e0ac48b89b4c45da24dbce41b8347d Mon Sep 17 00:00:00 2001 From: Dmytro Rodionov Date: Mon, 8 Sep 2025 10:18:25 +0200 Subject: [PATCH 2/5] Configuration measurement works again --- .../build/insights/GradleInsightsPlugin.kt | 6 ++-- .../timing/ExecutionTimeMeasurementModule.kt | 31 ++++++++++++------- .../report/ConsoleExecutionTimeReporter.kt | 4 +-- ...=> ConfigurationTimeMeasurementService.kt} | 23 ++++++++------ .../report/CompositeReportBuildService.kt | 9 ++---- .../insights/report/impl/html/HTMLReporter.kt | 3 +- 6 files changed, 43 insertions(+), 33 deletions(-) rename src/main/kotlin/com/smplio/gradle/build/insights/modules/timing/report_providers/{ConfigurationTimeReportProvider.kt => ConfigurationTimeMeasurementService.kt} (76%) diff --git a/src/main/kotlin/com/smplio/gradle/build/insights/GradleInsightsPlugin.kt b/src/main/kotlin/com/smplio/gradle/build/insights/GradleInsightsPlugin.kt index 0b2fb45..bb0a8da 100644 --- a/src/main/kotlin/com/smplio/gradle/build/insights/GradleInsightsPlugin.kt +++ b/src/main/kotlin/com/smplio/gradle/build/insights/GradleInsightsPlugin.kt @@ -49,10 +49,10 @@ class GradleInsightsPlugin @Inject constructor(private val registry: BuildEvents list.add(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 { diff --git a/src/main/kotlin/com/smplio/gradle/build/insights/modules/timing/ExecutionTimeMeasurementModule.kt b/src/main/kotlin/com/smplio/gradle/build/insights/modules/timing/ExecutionTimeMeasurementModule.kt index c6bb16f..2944140 100644 --- a/src/main/kotlin/com/smplio/gradle/build/insights/modules/timing/ExecutionTimeMeasurementModule.kt +++ b/src/main/kotlin/com/smplio/gradle/build/insights/modules/timing/ExecutionTimeMeasurementModule.kt @@ -1,9 +1,8 @@ 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.TaskExecutionTimeMeasurementService import org.gradle.api.provider.Provider @@ -13,18 +12,28 @@ class ExecutionTimeMeasurementModule( private val configuration: ExecutionTimeMeasurementConfiguration, ) { + private var configurationTimeMeasurementService: Provider? = null private var taskExecutionTimeMeasurementService: Provider? = null - private val configurationTimeReportProvider = ConfigurationTimeReportProvider() 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()) { @@ -39,11 +48,11 @@ class ExecutionTimeMeasurementModule( } } - fun getConfigurationTimeReportProvider(): IConfigurationTimeReportProvider { - return configurationTimeReportProvider + fun getConfigurationTimeTimeMeasurementService(): Provider? { + return configurationTimeMeasurementService } - fun getExecutionTimeReportProvider(): Provider? { + fun getExecutionTimeTimeMeasurementService(): Provider? { return taskExecutionTimeMeasurementService } } diff --git a/src/main/kotlin/com/smplio/gradle/build/insights/modules/timing/report/ConsoleExecutionTimeReporter.kt b/src/main/kotlin/com/smplio/gradle/build/insights/modules/timing/report/ConsoleExecutionTimeReporter.kt index 3f2dd05..1601f4b 100644 --- a/src/main/kotlin/com/smplio/gradle/build/insights/modules/timing/report/ConsoleExecutionTimeReporter.kt +++ b/src/main/kotlin/com/smplio/gradle/build/insights/modules/timing/report/ConsoleExecutionTimeReporter.kt @@ -34,7 +34,7 @@ class ConsoleExecutionTimeReporter: 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 } @@ -50,7 +50,7 @@ class ConsoleExecutionTimeReporter: } } - taskExecutionTimeReport?.let { report -> + taskExecutionTimeReport?.takeIf { it.isNotEmpty() }?.let { report -> println("Task execution time:") val firstTaskStartTime = report.minOf { it.startTime } diff --git a/src/main/kotlin/com/smplio/gradle/build/insights/modules/timing/report_providers/ConfigurationTimeReportProvider.kt b/src/main/kotlin/com/smplio/gradle/build/insights/modules/timing/report_providers/ConfigurationTimeMeasurementService.kt similarity index 76% rename from src/main/kotlin/com/smplio/gradle/build/insights/modules/timing/report_providers/ConfigurationTimeReportProvider.kt rename to src/main/kotlin/com/smplio/gradle/build/insights/modules/timing/report_providers/ConfigurationTimeMeasurementService.kt index cbecb5a..6004ab6 100644 --- a/src/main/kotlin/com/smplio/gradle/build/insights/modules/timing/report_providers/ConfigurationTimeReportProvider.kt +++ b/src/main/kotlin/com/smplio/gradle/build/insights/modules/timing/report_providers/ConfigurationTimeMeasurementService.kt @@ -5,20 +5,20 @@ 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, + IConfigurationTimeReportProvider, + OperationCompletionListener +{ private val configurationStartTimes = ConcurrentHashMap() private val configurationTimeline = ConcurrentLinkedQueue>() - override fun provideConfigurationTimeReport(): ConfigurationTimeReport? { - println("${this}: Number of reports ${configurationTimeline.size}") - - return configurationTimeline.toList() - } - fun onBeforeProject(project: Project) { configurationStartTimes[project.displayName] = System.currentTimeMillis() } @@ -32,6 +32,11 @@ class ConfigurationTimeReportProvider: IConfigurationTimeReportProvider { startTime = startTime, endTime = System.currentTimeMillis(), )) - println("${this}: Added new configuration, currentSize: ${configurationTimeline.size}") + } + + override fun onFinish(event: FinishEvent) {} + + override fun provideConfigurationTimeReport(): ConfigurationTimeReport? { + return configurationTimeline.toList() } } \ No newline at end of file diff --git a/src/main/kotlin/com/smplio/gradle/build/insights/report/CompositeReportBuildService.kt b/src/main/kotlin/com/smplio/gradle/build/insights/report/CompositeReportBuildService.kt index 41829ce..a42a04f 100644 --- a/src/main/kotlin/com/smplio/gradle/build/insights/report/CompositeReportBuildService.kt +++ b/src/main/kotlin/com/smplio/gradle/build/insights/report/CompositeReportBuildService.kt @@ -7,7 +7,7 @@ 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.TaskExecutionTimeMeasurementService @@ -27,8 +27,8 @@ abstract class CompositeReportBuildService : BuildService - val configurationTimeReportProvider: Property val systemLoadReportService: Property + val configurationTimeReportService: Property val executionTimeReportService: Property } @@ -36,7 +36,7 @@ abstract class CompositeReportBuildService : BuildService().forEach { reporter -> systemLoadService.provideSystemLoadReport()?.let { @@ -52,7 +52,6 @@ abstract class CompositeReportBuildService : BuildService().forEach { reporter -> configurationTimeReportProvider.provideConfigurationTimeReport()?.let { - println("Configuration time report size (IConfigurationTimeReportReceiver): ${it.size}") reporter.reportConfigurationTime(it) } } @@ -61,8 +60,6 @@ abstract class CompositeReportBuildService : BuildService Date: Mon, 8 Sep 2025 10:55:31 +0200 Subject: [PATCH 3/5] Provided reporter used again --- .../gradle/build/insights/GradleInsightsPlugin.kt | 12 ++++++------ .../insights/report/CompositeReportBuildService.kt | 5 +++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/com/smplio/gradle/build/insights/GradleInsightsPlugin.kt b/src/main/kotlin/com/smplio/gradle/build/insights/GradleInsightsPlugin.kt index bb0a8da..457f53f 100644 --- a/src/main/kotlin/com/smplio/gradle/build/insights/GradleInsightsPlugin.kt +++ b/src/main/kotlin/com/smplio/gradle/build/insights/GradleInsightsPlugin.kt @@ -4,10 +4,13 @@ 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.execution.IExecutionStatsReceiver 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 @@ -35,18 +38,15 @@ class GradleInsightsPlugin @Inject constructor(private val registry: BuildEvents ) systemLoadModule.initialize() - val executionStatsReporter = pluginConfig.getExecutionTimeMeasurementConfiguration().executionStatsReporter.get() - println("Execution stats reporter is ${executionStatsReporter.javaClass.simpleName}") - val compositeReportBuildService = project.gradle.sharedServices.registerIfAbsent( CompositeReportBuildService::class.java.simpleName, CompositeReportBuildService::class.java, ) { buildServiceSpec -> - buildServiceSpec.parameters.reporters.set(mutableListOf( - executionStatsReporter, + buildServiceSpec.parameters.reporters.set(mutableListOf>( + pluginConfig.getExecutionTimeMeasurementConfiguration().executionStatsReporter as Provider, ).also { list -> if (pluginConfig.gatherHtmlReport.get()) { - list.add(HTMLReporter(project)) + list.add(project.provider { HTMLReporter(project) }) } }) executionTimeMeasurementModule.getConfigurationTimeTimeMeasurementService()?.let { diff --git a/src/main/kotlin/com/smplio/gradle/build/insights/report/CompositeReportBuildService.kt b/src/main/kotlin/com/smplio/gradle/build/insights/report/CompositeReportBuildService.kt index a42a04f..7e96143 100644 --- a/src/main/kotlin/com/smplio/gradle/build/insights/report/CompositeReportBuildService.kt +++ b/src/main/kotlin/com/smplio/gradle/build/insights/report/CompositeReportBuildService.kt @@ -14,6 +14,7 @@ import com.smplio.gradle.build.insights.modules.timing.report_providers.TaskExec 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 @@ -26,14 +27,14 @@ abstract class CompositeReportBuildService : BuildService + val reporters: ListProperty> val systemLoadReportService: Property val configurationTimeReportService: Property val executionTimeReportService: Property } 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.configurationTimeReportService.orNull ?: return From ef44452af38939fa9979a762d404f3626f9d0dff Mon Sep 17 00:00:00 2001 From: Dmytro Rodionov Date: Mon, 8 Sep 2025 10:57:18 +0200 Subject: [PATCH 4/5] Fix issues --- .../build/insights/GradleInsightsPlugin.kt | 1 - .../insights/report/impl/html/HTMLReporter.kt | 16 +++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/com/smplio/gradle/build/insights/GradleInsightsPlugin.kt b/src/main/kotlin/com/smplio/gradle/build/insights/GradleInsightsPlugin.kt index 457f53f..2cf8b5f 100644 --- a/src/main/kotlin/com/smplio/gradle/build/insights/GradleInsightsPlugin.kt +++ b/src/main/kotlin/com/smplio/gradle/build/insights/GradleInsightsPlugin.kt @@ -5,7 +5,6 @@ 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.execution.IExecutionStatsReceiver import com.smplio.gradle.build.insights.report.impl.html.HTMLReporter import org.gradle.api.Plugin import org.gradle.api.Project diff --git a/src/main/kotlin/com/smplio/gradle/build/insights/report/impl/html/HTMLReporter.kt b/src/main/kotlin/com/smplio/gradle/build/insights/report/impl/html/HTMLReporter.kt index b522a49..32f0025 100644 --- a/src/main/kotlin/com/smplio/gradle/build/insights/report/impl/html/HTMLReporter.kt +++ b/src/main/kotlin/com/smplio/gradle/build/insights/report/impl/html/HTMLReporter.kt @@ -94,15 +94,17 @@ class HTMLReporter( buildChartsJsText, ) - reportHtmlFile.bufferedWriter(Charsets.UTF_8).use { - it.write(html) + reportHtmlFile.bufferedWriter(Charsets.UTF_8).use { writer -> + html?.let { writer.write(it) } } - Files.copy( - javaClass.getResourceAsStream("/style.css"), - Path(styleCssPath), - StandardCopyOption.REPLACE_EXISTING, - ) + javaClass.getResourceAsStream("/style.css")?.let { resourceStream -> + Files.copy( + resourceStream, + Path(styleCssPath), + StandardCopyOption.REPLACE_EXISTING, + ) + } configurationTimeJson = null executionTimeJson = null From 0c669d06e5c04533f4a2a64d62b73d710e9e2128 Mon Sep 17 00:00:00 2001 From: Dmytro Rodionov Date: Mon, 8 Sep 2025 11:25:30 +0200 Subject: [PATCH 5/5] Add percents to graph --- src/main/resources/build_charts.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/main/resources/build_charts.js b/src/main/resources/build_charts.js index 9c04c02..ef24df0 100644 --- a/src/main/resources/build_charts.js +++ b/src/main/resources/build_charts.js @@ -61,6 +61,20 @@ new Chart(cpuChart, { }, tooltip: { mode: 'index', + callbacks: { + label: function(context) { + let label = context.dataset.label || ''; + + if (label) { + label += ': '; + } + if (context.parsed.y !== null) { + label += context.parsed.y; + } + label += '%'; + return label; + } + } }, } }