@@ -3,6 +3,7 @@ package com.smplio.gradle.build.insights.modules.load
33import com.codahale.metrics.Gauge
44import com.codahale.metrics.MetricRegistry
55import java.lang.management.ManagementFactory
6+ import java.time.Duration
67
78sealed class SystemLoadMetric <T : Number >(val name : String , val metricProvider : () -> T ): Gauge<T> {
89
@@ -22,6 +23,96 @@ sealed class SystemLoadMetric<T: Number>(val name: String, val metricProvider: (
2223 val memoryBean = ManagementFactory .getMemoryMXBean()
2324 memoryBean.heapMemoryUsage.used
2425 })
26+
27+ // CPU usage (percent) for everything running inside the current Gradle JVM (100% equals one fully utilized core)
28+ class GradleJvmCpuPercentMetric : SystemLoadMetric <Double >(" gradleJvmCpuPercent" , {
29+ CpuLoadSampler .sampleJvmProcessCpuPercent()
30+ })
31+
32+ // CPU usage (percent) aggregated across all descendant processes started by Gradle that run outside this JVM (100% equals one fully utilized core)
33+ class GradleDescendantsCpuPercentMetric : SystemLoadMetric <Double >(" gradleDescendantsCpuPercent" , {
34+ CpuLoadSampler .sampleChildrenCpuPercent()
35+ })
36+
37+ private object CpuLoadSampler {
38+ private val processors: Int = Runtime .getRuntime().availableProcessors().coerceAtLeast(1 )
39+
40+ // JVM process sampling state
41+ private var lastWallTimeJvmNanos: Long = System .nanoTime()
42+ private var lastCpuTimeJvmNanos: Long = currentJvmProcessCpuTimeNanos()
43+
44+ // Children sampling state
45+ private var lastWallTimeChildrenNanos: Long = System .nanoTime()
46+ private var lastCpuTimeChildrenNanos: Long = currentChildrenCpuTimeNanos()
47+
48+ @Synchronized
49+ fun sampleJvmProcessCpuPercent (): Double {
50+ val now = System .nanoTime()
51+ val cpuNow = currentJvmProcessCpuTimeNanos()
52+ val deltaCpu = (cpuNow - lastCpuTimeJvmNanos).coerceAtLeast(0L )
53+ val deltaWall = (now - lastWallTimeJvmNanos).coerceAtLeast(1L )
54+ lastCpuTimeJvmNanos = cpuNow
55+ lastWallTimeJvmNanos = now
56+ val pct = (deltaCpu.toDouble() / deltaWall.toDouble()) * 100.0
57+ return pct.coerceIn(0.0 , 100.0 * processors)
58+ }
59+
60+ @Synchronized
61+ fun sampleChildrenCpuPercent (): Double {
62+ val now = System .nanoTime()
63+ val cpuNow = currentChildrenCpuTimeNanos()
64+ val deltaCpu = (cpuNow - lastCpuTimeChildrenNanos).coerceAtLeast(0L )
65+ val deltaWall = (now - lastWallTimeChildrenNanos).coerceAtLeast(1L )
66+ lastCpuTimeChildrenNanos = cpuNow
67+ lastWallTimeChildrenNanos = now
68+ val pct = (deltaCpu.toDouble() / deltaWall.toDouble()) * 100.0
69+ // With 100% meaning one full core, allow up to cores*100%
70+ return pct.coerceIn(0.0 , 100.0 * processors)
71+ }
72+
73+ private fun currentJvmProcessCpuTimeNanos (): Long {
74+ // Prefer com.sun.management.OperatingSystemMXBean for precise JVM CPU time
75+ val osBean = ManagementFactory .getOperatingSystemMXBean()
76+ val cpuTimeFromOsBean = try {
77+ val sunBean = osBean as ? com.sun.management.OperatingSystemMXBean
78+ sunBean?.processCpuTime ? : - 1L
79+ } catch (_: Throwable ) {
80+ - 1L
81+ }
82+ if (cpuTimeFromOsBean >= 0L ) return cpuTimeFromOsBean
83+
84+ // Fallback to ProcessHandle info
85+ return try {
86+ val duration: Duration ? = ProcessHandle .current().info().totalCpuDuration().orElse(null )
87+ duration?.toNanos() ? : 0L
88+ } catch (_: Throwable ) {
89+ 0L
90+ }
91+ }
92+
93+ private fun currentChildrenCpuTimeNanos (): Long {
94+ return try {
95+ var sum = 0L
96+ val current = ProcessHandle .current()
97+ // Use descendants to include nested children
98+ current.descendants().forEach { descendant ->
99+ try {
100+ val d: Duration ? = descendant.info().totalCpuDuration().orElse(null )
101+ if (d != null ) {
102+ sum + = d.toNanos()
103+ }
104+ } catch (e: Throwable ) {
105+ // ignore processes we cannot inspect
106+ e.printStackTrace()
107+ }
108+ }
109+ sum
110+ } catch (e: Throwable ) {
111+ e.printStackTrace()
112+ 0L
113+ }
114+ }
115+ }
25116}
26117
27118fun <T : Number > MetricRegistry.register (metric : SystemLoadMetric <T >) {
0 commit comments