├── resources └── buildscanoutput.png ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── libs.versions.toml ├── gradle-plugin ├── src │ ├── main │ │ └── kotlin │ │ │ └── io │ │ │ └── github │ │ │ └── cdsap │ │ │ └── gcreport │ │ │ └── plugin │ │ │ ├── model │ │ │ ├── GCLogFile.kt │ │ │ ├── GCType.kt │ │ │ ├── Bucket.kt │ │ │ ├── GC.kt │ │ │ ├── GCEvent.kt │ │ │ └── GCEntry.kt │ │ │ ├── extensions │ │ │ └── StringExtension.kt │ │ │ ├── report │ │ │ ├── ConsoleReport.kt │ │ │ └── DevelocityReport.kt │ │ │ ├── GCReportExtension.kt │ │ │ ├── ServiceHandler.kt │ │ │ ├── buckets │ │ │ └── GetBuckets.kt │ │ │ ├── histogram │ │ │ └── Histogram.kt │ │ │ ├── output │ │ │ └── DevelocityValues.kt │ │ │ ├── GCReportPlugin.kt │ │ │ ├── parser │ │ │ └── GCLogReader.kt │ │ │ └── GCReportService.kt │ └── test │ │ ├── java │ │ └── io │ │ │ └── github │ │ │ └── cdsap │ │ │ └── gcreport │ │ │ └── plugin │ │ │ ├── model │ │ │ └── ReponseModels.kt │ │ │ ├── parser │ │ │ └── GCLogReaderTest.kt │ │ │ ├── buckets │ │ │ └── GetBucketsTest.kt │ │ │ ├── histogram │ │ │ └── HistogramTest.kt │ │ │ ├── GCReportPluginWithoutDevelocityTest.kt │ │ │ └── GCReportPluginWithDevelocityTest.kt │ │ └── resources │ │ └── correct_g1_log └── build.gradle.kts ├── settings.gradle.kts ├── .gitignore ├── .github └── workflows │ └── build.yaml ├── LICENSE ├── gradlew.bat ├── README.md └── gradlew /resources/buildscanoutput.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdsap/GCReport/HEAD/resources/buildscanoutput.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdsap/GCReport/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle-plugin/src/main/kotlin/io/github/cdsap/gcreport/plugin/model/GCLogFile.kt: -------------------------------------------------------------------------------- 1 | package io.github.cdsap.gcreport.plugin.model 2 | 3 | enum class GCLogFile { 4 | DEFAULT, 5 | DETAILED, 6 | } 7 | -------------------------------------------------------------------------------- /gradle-plugin/src/main/kotlin/io/github/cdsap/gcreport/plugin/model/GCType.kt: -------------------------------------------------------------------------------- 1 | package io.github.cdsap.gcreport.plugin.model 2 | 3 | enum class GCType { 4 | G1, 5 | PARALLEL, 6 | ZGC, 7 | } 8 | -------------------------------------------------------------------------------- /gradle-plugin/src/main/kotlin/io/github/cdsap/gcreport/plugin/model/Bucket.kt: -------------------------------------------------------------------------------- 1 | package io.github.cdsap.gcreport.plugin.model 2 | 3 | enum class Bucket { 4 | Sturges, 5 | SquareRoot, 6 | FreedmanDiaconis, 7 | } 8 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencyResolutionManagement { 2 | repositories { 3 | mavenCentral() 4 | gradlePluginPortal() 5 | } 6 | } 7 | 8 | rootProject.name = "GCReport" 9 | include("gradle-plugin") 10 | 11 | -------------------------------------------------------------------------------- /gradle-plugin/src/main/kotlin/io/github/cdsap/gcreport/plugin/model/GC.kt: -------------------------------------------------------------------------------- 1 | package io.github.cdsap.gcreport.plugin.model 2 | 3 | data class GC( 4 | val type: GCType, 5 | val logFileType: GCLogFile, 6 | val events: List, 7 | ) 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradle-plugin/src/main/kotlin/io/github/cdsap/gcreport/plugin/extensions/StringExtension.kt: -------------------------------------------------------------------------------- 1 | package io.github.cdsap.gcreport.plugin.extensions 2 | 3 | fun String.getFileName(): String { 4 | return this.substringAfterLast("/") 5 | } 6 | 7 | fun String.getFileNameCsvLog(): String { 8 | return this.getFileName().replace(".log", ".csv") 9 | } 10 | -------------------------------------------------------------------------------- /gradle-plugin/src/main/kotlin/io/github/cdsap/gcreport/plugin/report/ConsoleReport.kt: -------------------------------------------------------------------------------- 1 | package io.github.cdsap.gcreport.plugin.report 2 | 3 | import io.github.cdsap.gcreport.plugin.ServiceHandler 4 | 5 | class ConsoleReport( 6 | private val service: ServiceHandler, 7 | ) { 8 | fun report() { 9 | service.createService() 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /gradle-plugin/src/main/kotlin/io/github/cdsap/gcreport/plugin/model/GCEvent.kt: -------------------------------------------------------------------------------- 1 | package io.github.cdsap.gcreport.plugin.model 2 | 3 | data class GCEvent( 4 | val id: Int, 5 | val type: String, 6 | val operation: String? = null, 7 | val length: String? = null, 8 | val memoryBefore: String? = null, 9 | val memoryAfter: String? = null, 10 | val totalMemory: String? = null, 11 | val duration: String? = null, 12 | val timestamp: String, 13 | ) 14 | -------------------------------------------------------------------------------- /gradle-plugin/src/main/kotlin/io/github/cdsap/gcreport/plugin/model/GCEntry.kt: -------------------------------------------------------------------------------- 1 | package io.github.cdsap.gcreport.plugin.model 2 | 3 | data class GCEntry( 4 | val timeStamp: String, 5 | val timeStampUnit: String, 6 | val id: String, 7 | val description: String, 8 | val memoryBefore: String? = null, 9 | val memoryBeforeUnit: String? = null, 10 | val memoryAfter: String? = null, 11 | val memoryAfterUnit: String? = null, 12 | val memory: String? = null, 13 | val memoryUnit: String? = null, 14 | val duration: String, 15 | val durationUnit: String, 16 | ) 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | !**/src/main/**/build/ 5 | !**/src/test/**/build/ 6 | 7 | ### IntelliJ IDEA ### 8 | .idea/modules.xml 9 | .idea/jarRepositories.xml 10 | .idea/compiler.xml 11 | .idea/libraries/ 12 | *.iws 13 | *.iml 14 | *.ipr 15 | out/ 16 | !**/src/main/**/out/ 17 | !**/src/test/**/out/ 18 | 19 | ### Eclipse ### 20 | .apt_generated 21 | .classpath 22 | .factorypath 23 | .project 24 | .settings 25 | .springBeans 26 | .sts4-cache 27 | bin/ 28 | !**/src/main/**/bin/ 29 | !**/src/test/**/bin/ 30 | 31 | ### NetBeans ### 32 | /nbproject/private/ 33 | /nbbuild/ 34 | /dist/ 35 | /nbdist/ 36 | /.nb-gradle/ 37 | 38 | ### VS Code ### 39 | .vscode/ 40 | 41 | ### Mac OS ### 42 | .DS_Store -------------------------------------------------------------------------------- /gradle-plugin/src/main/kotlin/io/github/cdsap/gcreport/plugin/GCReportExtension.kt: -------------------------------------------------------------------------------- 1 | package io.github.cdsap.gcreport.plugin 2 | 3 | import io.github.cdsap.gcreport.plugin.model.Bucket 4 | import org.gradle.api.model.ObjectFactory 5 | import org.gradle.api.provider.ListProperty 6 | import org.gradle.api.provider.Property 7 | 8 | open class GCReportExtension(objects: ObjectFactory) { 9 | val logs: ListProperty = objects.listProperty(String::class.java) 10 | val histogramEnabled: Property = objects.property(Boolean::class.java).convention(false) 11 | val histogramBucket: Property = objects.property(Bucket::class.java).convention(Bucket.FreedmanDiaconis) 12 | val enableConsoleLog: Property = objects.property(Boolean::class.java).convention(false) 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | build: 11 | name: "Build and test" 12 | runs-on: ubuntu-latest 13 | 14 | permissions: 15 | contents: write 16 | pull-requests: write 17 | security-events: write 18 | 19 | timeout-minutes: 60 20 | 21 | steps: 22 | - name: Checkout 23 | 24 | uses: actions/checkout@v4 25 | - name: Set up JDK 17 26 | uses: actions/setup-java@v4 27 | with: 28 | distribution: 'zulu' 29 | java-version: 17 30 | 31 | - name: Ktlinkt 32 | run: ./gradlew ktlintCheck 33 | 34 | - name: test 35 | run: ./gradlew test 36 | env: 37 | GE_API_KEY: ${{ secrets.GE_API }} 38 | GE_URL: ${{ secrets.GE_URL }} 39 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | kotlin = "2.1.0" 3 | develocity = "3.19.1" 4 | junit = "5.10.0" 5 | picnic = "0.7.0" 6 | ktor-client-cio = "3.0.3" 7 | publishing = "1.2.1" 8 | ktlint = "12.1.2" 9 | gson = "2.10.1" 10 | 11 | 12 | [libraries] 13 | develocity = { group = "com.gradle", name = "develocity-gradle-plugin", version.ref = "develocity"} 14 | junit = { group = "org.junit", name = "junit-bom", version.ref = "junit" } 15 | picnic = { group = "com.jakewharton.picnic", name = "picnic", version.ref = "picnic" } 16 | ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio", version.ref = "ktor-client-cio" } 17 | gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" } 18 | junit-jupiter = { group = "org.junit.jupiter", name = "junit-jupiter" } 19 | 20 | [plugins] 21 | kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } 22 | gradle-publish = { id = "com.gradle.plugin-publish", version.ref = "publishing" } 23 | ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" } 24 | -------------------------------------------------------------------------------- /gradle-plugin/src/main/kotlin/io/github/cdsap/gcreport/plugin/report/DevelocityReport.kt: -------------------------------------------------------------------------------- 1 | package io.github.cdsap.gcreport.plugin.report 2 | 3 | import com.gradle.develocity.agent.gradle.DevelocityConfiguration 4 | import io.github.cdsap.gcreport.plugin.GCReportExtension 5 | import io.github.cdsap.gcreport.plugin.output.DevelocityValues 6 | import io.github.cdsap.gcreport.plugin.parser.GCLogReader 7 | import java.io.File 8 | 9 | class DevelocityReport( 10 | private val develocityConfiguration: DevelocityConfiguration, 11 | private val extension: GCReportExtension, 12 | ) { 13 | fun report() { 14 | if (extension.enableConsoleLog.get()) { 15 | // serviceHandler.createService() 16 | } 17 | develocityConfiguration.buildScan.buildFinished { 18 | extension.logs.get().filter { File(it).exists() }.forEach { 19 | val gcEntries = GCLogReader(File(it)).parse() 20 | DevelocityValues(develocityConfiguration, gcEntries, it, extension).report() 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /gradle-plugin/src/test/java/io/github/cdsap/gcreport/plugin/model/ReponseModels.kt: -------------------------------------------------------------------------------- 1 | package io.github.cdsap.gcreport.plugin.model 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class Model( 6 | val id: String, 7 | val buildStartTime: Long, 8 | val buildDuration: Long, 9 | val gradleVersion: String, 10 | val pluginVersion: String, 11 | val rootProjectName: String, 12 | val requestedTasks: List, 13 | val hasFailed: Boolean, 14 | val tags: List, 15 | val values: List, 16 | ) 17 | 18 | data class Response( 19 | val id: String, 20 | val availableAt: Long, 21 | val buildToolType: String, 22 | val buildToolVersion: String, 23 | val buildAgentVersion: String, 24 | val models: Models, 25 | ) 26 | 27 | data class Models( 28 | @SerializedName("gradleAttributes") 29 | val gradleAttributes: GradleAttributes, 30 | ) 31 | 32 | data class GradleAttributes( 33 | val model: Model, 34 | ) 35 | 36 | data class Value( 37 | val name: String, 38 | val value: String, 39 | ) 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Iñaki Villar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /gradle-plugin/src/main/kotlin/io/github/cdsap/gcreport/plugin/ServiceHandler.kt: -------------------------------------------------------------------------------- 1 | package io.github.cdsap.gcreport.plugin 2 | 3 | import org.gradle.api.Project 4 | import org.gradle.api.provider.Provider 5 | import org.gradle.build.event.BuildEventsListenerRegistry 6 | import org.gradle.internal.extensions.core.serviceOf 7 | 8 | class ServiceHandler(private val project: Project, private val extension: GCReportExtension) { 9 | fun createService() { 10 | val service: Provider = 11 | project.gradle.sharedServices.registerIfAbsent( 12 | "gcReportService", 13 | GCReportService::class.java, 14 | ) { 15 | val buildOutput = project.layout.buildDirectory.dir("reports/gcreport") 16 | parameters.logs = extension.logs 17 | parameters.histogramEnabled = extension.histogramEnabled 18 | parameters.histogramBucket = extension.histogramBucket 19 | parameters.buildOutput = buildOutput 20 | } 21 | project.serviceOf().onTaskCompletion(service) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /gradle-plugin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.kotlin.jvm) 3 | `kotlin-dsl` 4 | `java-gradle-plugin` 5 | `maven-publish` 6 | alias(libs.plugins.gradle.publish) 7 | alias(libs.plugins.ktlint) 8 | } 9 | 10 | group = "io.github.cdsap" 11 | version = "0.1.0" 12 | 13 | dependencies { 14 | implementation(libs.develocity) 15 | implementation(libs.picnic) 16 | testImplementation(platform(libs.junit)) 17 | testImplementation(libs.ktor.client.cio) 18 | testImplementation(libs.gson) 19 | testImplementation(libs.junit.jupiter) 20 | } 21 | 22 | tasks.test { 23 | useJUnitPlatform() 24 | } 25 | gradlePlugin { 26 | website = "https://github.com/cdsap/GCReport" 27 | vcsUrl = "https://github.com/cdsap/GCReport.git" 28 | plugins { 29 | create("GCReport") { 30 | id = "io.github.cdsap.gcreport" 31 | implementationClass = "io.github.cdsap.gcreport.plugin.GCReportPlugin" 32 | displayName = "GC Report" 33 | description = "Gradle plugin that collects GC metrics based on the GC logs generated during the build" 34 | tags = listOf("kotlin", "gc", "performance") 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /gradle-plugin/src/test/java/io/github/cdsap/gcreport/plugin/parser/GCLogReaderTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.cdsap.gcreport.plugin.parser 2 | 3 | import io.github.cdsap.gcreport.plugin.model.GCEntry 4 | import org.junit.jupiter.api.Assertions.assertEquals 5 | import org.junit.jupiter.api.Test 6 | import java.io.File 7 | 8 | class GCLogReaderTest { 9 | @Test 10 | fun `parse complex G1 log with multiple events`() { 11 | val logFile = File("src/test/resources/correct_g1_log") 12 | val reader = GCLogReader(logFile) 13 | val gcEntries = reader.parse() 14 | 15 | assertEquals(19, gcEntries.size) 16 | 17 | val expectedEntry1 = 18 | GCEntry( 19 | timeStamp = "1.164", 20 | timeStampUnit = "s", 21 | id = "0", 22 | description = "Pause Young (Concurrent Start) (Metadata GC Threshold)", 23 | duration = "4.981", 24 | durationUnit = "ms", 25 | memoryBefore = "194", 26 | memoryBeforeUnit = "M", 27 | memoryAfter = "24", 28 | memoryAfterUnit = "M", 29 | memory = "4096", 30 | memoryUnit = "M", 31 | ) 32 | assertEquals(expectedEntry1, gcEntries[0]) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /gradle-plugin/src/main/kotlin/io/github/cdsap/gcreport/plugin/buckets/GetBuckets.kt: -------------------------------------------------------------------------------- 1 | package io.github.cdsap.gcreport.plugin.buckets 2 | 3 | import io.github.cdsap.gcreport.plugin.model.Bucket 4 | import io.github.cdsap.gcreport.plugin.model.GCEntry 5 | 6 | class GetBuckets(private val bucket: Bucket) { 7 | fun getSize(gcEntries: List): Double { 8 | if (gcEntries.isEmpty()) { 9 | return 0.0 10 | } 11 | val max = gcEntries.map { it.timeStamp.toDouble() } 12 | val range = max.maxOrNull()!! - max.minOrNull()!! 13 | val n = max.size 14 | 15 | val size = 16 | when (bucket) { 17 | Bucket.Sturges -> { 18 | val sturgesBucketCount = Math.ceil(Math.log(n.toDouble()) / Math.log(2.0) + 1).toInt() 19 | range / sturgesBucketCount 20 | } 21 | 22 | Bucket.SquareRoot -> { 23 | val sqrtBucketCount = Math.ceil(Math.sqrt(n.toDouble())).toInt() 24 | range / sqrtBucketCount 25 | } 26 | 27 | Bucket.FreedmanDiaconis -> { 28 | val sorted = max.sorted() 29 | val q1 = sorted[(n * 0.25).toInt()] 30 | val q3 = sorted[(n * 0.75).toInt()] 31 | val iqr = q3 - q1 32 | 2 * iqr / Math.cbrt(n.toDouble()) 33 | } 34 | } 35 | 36 | return String.format("%.2f", size).toDouble() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /gradle-plugin/src/main/kotlin/io/github/cdsap/gcreport/plugin/histogram/Histogram.kt: -------------------------------------------------------------------------------- 1 | package io.github.cdsap.gcreport.plugin.histogram 2 | 3 | import io.github.cdsap.gcreport.plugin.buckets.GetBuckets 4 | import io.github.cdsap.gcreport.plugin.model.Bucket 5 | import io.github.cdsap.gcreport.plugin.model.GCEntry 6 | 7 | class Histogram(private val bucket: Bucket) { 8 | fun getHistogram(gcEntries: List): List> { 9 | if (gcEntries.isEmpty()) { 10 | return emptyList() 11 | } 12 | val timestamps = gcEntries.map { it.timeStamp.toDouble() } 13 | val bucketsSize = GetBuckets(bucket).getSize(gcEntries) 14 | 15 | // unlikely event to handle the case when bucketsSize is 0 (all timestamps are the same) 16 | if (bucketsSize == 0.0) { 17 | val formattedTimestamp = String.format("%.2f", timestamps.first()) 18 | return listOf("$formattedTimestamp-End" to gcEntries.size) 19 | } 20 | 21 | val buckets = (0..(timestamps.maxOrNull()!! / bucketsSize).toInt()).map { it * bucketsSize } 22 | 23 | val histogram = 24 | buckets.mapIndexed { index, bucket -> 25 | val end = buckets.getOrNull(index + 1) ?: Double.POSITIVE_INFINITY 26 | val count = timestamps.count { it >= bucket && it < end } // Use end directly 27 | 28 | val formattedBucket = String.format("%.2f", bucket) 29 | val formattedEnd = if (end.isFinite()) String.format("%.2f", end) else "End" 30 | 31 | "$formattedBucket-$formattedEnd" to count 32 | } 33 | return histogram 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /gradle-plugin/src/main/kotlin/io/github/cdsap/gcreport/plugin/output/DevelocityValues.kt: -------------------------------------------------------------------------------- 1 | package io.github.cdsap.gcreport.plugin.output 2 | 3 | import com.gradle.develocity.agent.gradle.DevelocityConfiguration 4 | import io.github.cdsap.gcreport.plugin.GCReportExtension 5 | import io.github.cdsap.gcreport.plugin.extensions.getFileName 6 | import io.github.cdsap.gcreport.plugin.histogram.Histogram 7 | import io.github.cdsap.gcreport.plugin.model.GCEntry 8 | 9 | class DevelocityValues( 10 | private val develocityConfiguration: DevelocityConfiguration, 11 | private val gcEntries: List, 12 | private val log: String, 13 | private val extension: GCReportExtension, 14 | ) { 15 | fun report() { 16 | develocityConfiguration.buildScan { 17 | gcEntries.groupBy { it.description }.forEach { t, u -> 18 | value("gc-${log.getFileName()}-$t", "${u.size}") 19 | } 20 | val counter = gcEntries.filter { it.description != "Concurrent Mark Cycle" }.count() 21 | if (counter != 0) { 22 | value("gc-${log.getFileName()}-total-collections", "$counter") 23 | } 24 | if (extension.histogramEnabled.get()) { 25 | val histogram = 26 | Histogram(extension.histogramBucket.get()).getHistogram( 27 | gcEntries.filter { it.description != "Concurrent Mark Cycle" }, 28 | ) 29 | var histogramText = "[" 30 | histogram.forEach { 31 | histogramText += "\"${it.first}\": \"${it.second}\", " 32 | } 33 | if (histogramText.isNotEmpty()) { 34 | value("gc-${log.getFileName()}-histogram", "${histogramText.dropLast(1)}]") 35 | } 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /gradle-plugin/src/main/kotlin/io/github/cdsap/gcreport/plugin/GCReportPlugin.kt: -------------------------------------------------------------------------------- 1 | package io.github.cdsap.gcreport.plugin 2 | 3 | import com.gradle.develocity.agent.gradle.DevelocityConfiguration 4 | import io.github.cdsap.gcreport.plugin.report.DevelocityReport 5 | import org.gradle.api.Plugin 6 | import org.gradle.api.Project 7 | import org.gradle.api.provider.Property 8 | import org.gradle.api.provider.Provider 9 | import org.gradle.build.event.BuildEventsListenerRegistry 10 | import org.gradle.internal.extensions.core.serviceOf 11 | import org.gradle.kotlin.dsl.create 12 | 13 | class GCReportPlugin : Plugin { 14 | override fun apply(target: Project) { 15 | target.extensions.create("gcReport") 16 | val develocityConfiguration = 17 | target.gradle.rootProject.extensions.findByType(DevelocityConfiguration::class.java) 18 | target.gradle.rootProject { 19 | val extension = target.extensions.getByName("gcReport") as GCReportExtension 20 | if (develocityConfiguration != null) { 21 | createService(target, extension, extension.enableConsoleLog) 22 | DevelocityReport(develocityConfiguration, extension).report() 23 | } else { 24 | createService(target, extension) 25 | } 26 | } 27 | } 28 | 29 | private fun createService( 30 | project: Project, 31 | extension: GCReportExtension, 32 | enableLog: Property? = null, 33 | ) { 34 | val service: Provider = 35 | project.gradle.sharedServices.registerIfAbsent( 36 | "gcReportService", 37 | GCReportService::class.java, 38 | ) { 39 | val buildOutput = project.layout.buildDirectory.dir("reports/gcreport") 40 | parameters.logs = extension.logs 41 | parameters.histogramEnabled = extension.histogramEnabled 42 | parameters.histogramBucket = extension.histogramBucket 43 | parameters.buildOutput = buildOutput 44 | parameters.enabledReport = if (enableLog == null) project.provider { true } else enableLog 45 | } 46 | project.serviceOf().onTaskCompletion(service) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /gradle-plugin/src/test/java/io/github/cdsap/gcreport/plugin/buckets/GetBucketsTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.cdsap.gcreport.plugin.buckets 2 | 3 | import io.github.cdsap.gcreport.plugin.model.Bucket 4 | import io.github.cdsap.gcreport.plugin.model.GCEntry 5 | import org.junit.jupiter.api.Assertions.assertEquals 6 | import org.junit.jupiter.api.Test 7 | 8 | class GetBucketsTest { 9 | @Test 10 | fun getSizeWithSturgesBucket() { 11 | val gcEntries = 12 | listOf( 13 | GCEntry("1", "ms", "1", "desc", duration = "1", durationUnit = "ms"), 14 | GCEntry("2", "ms", "2", "desc", duration = "1", durationUnit = "ms"), 15 | GCEntry("3", "ms", "3", "desc", duration = "1", durationUnit = "ms"), 16 | GCEntry("4", "ms", "4", "desc", duration = "1", durationUnit = "ms"), 17 | GCEntry("5", "ms", "5", "desc", duration = "1", durationUnit = "ms"), 18 | ) 19 | val getBuckets = GetBuckets(Bucket.Sturges) 20 | val size = getBuckets.getSize(gcEntries) 21 | assertEquals(1.0, size) 22 | } 23 | 24 | @Test 25 | fun getSizeWithSquareRootBucket() { 26 | val gcEntries = 27 | listOf( 28 | GCEntry("1", "ms", "1", "desc", duration = "1", durationUnit = "ms"), 29 | GCEntry("2", "ms", "2", "desc", duration = "1", durationUnit = "ms"), 30 | GCEntry("3", "ms", "3", "desc", duration = "1", durationUnit = "ms"), 31 | GCEntry("4", "ms", "4", "desc", duration = "1", durationUnit = "ms"), 32 | GCEntry("5", "ms", "5", "desc", duration = "1", durationUnit = "ms"), 33 | ) 34 | val getBuckets = GetBuckets(Bucket.SquareRoot) 35 | val size = getBuckets.getSize(gcEntries) 36 | assertEquals(1.33, size) 37 | } 38 | 39 | @Test 40 | fun getSizeWithFreedmanDiaconisBucket() { 41 | val gcEntries = 42 | listOf( 43 | GCEntry("1", "ms", "1", "desc", duration = "1", durationUnit = "ms"), 44 | GCEntry("2", "ms", "2", "desc", duration = "1", durationUnit = "ms"), 45 | GCEntry("3", "ms", "3", "desc", duration = "1", durationUnit = "ms"), 46 | GCEntry("4", "ms", "4", "desc", duration = "1", durationUnit = "ms"), 47 | GCEntry("5", "ms", "5", "desc", duration = "1", durationUnit = "ms"), 48 | ) 49 | val getBuckets = GetBuckets(Bucket.FreedmanDiaconis) 50 | val size = getBuckets.getSize(gcEntries) 51 | assertEquals(2.34, size) 52 | } 53 | 54 | @Test 55 | fun getSizeWithEmptyGCEntries() { 56 | val gcEntries = emptyList() 57 | val getBuckets = GetBuckets(Bucket.Sturges) 58 | val size = getBuckets.getSize(gcEntries) 59 | assertEquals(0.0, size) 60 | } 61 | 62 | @Test 63 | fun getSizeWithSingleGCEntry() { 64 | val gcEntries = listOf(GCEntry("1", "ms", "1", "desc", duration = "1", durationUnit = "ms")) 65 | val getBuckets = GetBuckets(Bucket.Sturges) 66 | val size = getBuckets.getSize(gcEntries) 67 | assertEquals(0.0, size) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 1>&2 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 48 | echo. 1>&2 49 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 50 | echo location of your Java installation. 1>&2 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 1>&2 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 62 | echo. 1>&2 63 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 64 | echo location of your Java installation. 1>&2 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /gradle-plugin/src/main/kotlin/io/github/cdsap/gcreport/plugin/parser/GCLogReader.kt: -------------------------------------------------------------------------------- 1 | package io.github.cdsap.gcreport.plugin.parser 2 | 3 | import io.github.cdsap.gcreport.plugin.model.GCEntry 4 | import java.io.File 5 | 6 | class GCLogReader(private val file: File) { 7 | fun parse(): List { 8 | val maxGC = mutableListOf() 9 | val lines = file.readLines() 10 | 11 | lines.forEach { 12 | val regex = 13 | """\[(\d+\.\d+)([a-z]+)]\[.*?\] GC\((\d+)\) (.+?) (\d+)([A-Z])->(\d+)([A-Z])\((\d+)([A-Z])\) (\d+\.\d+)([a-z]+)""".toRegex() 14 | 15 | val match = regex.find(it) 16 | 17 | if (match != null) { 18 | val groups = match.groupValues 19 | maxGC.add( 20 | GCEntry( 21 | timeStamp = groups[1], 22 | timeStampUnit = groups[2], 23 | id = groups[3], 24 | description = groups[4], 25 | memoryBefore = groups[5], 26 | memoryBeforeUnit = groups[6], 27 | memoryAfter = groups[7], 28 | memoryAfterUnit = groups[8], 29 | memory = groups[9], 30 | memoryUnit = groups[10], 31 | duration = groups[11], 32 | durationUnit = groups[12], 33 | ), 34 | ) 35 | } 36 | } 37 | 38 | // Once we have all the GC entries found by th regex, we need to find 39 | // if we have Concurrent Mark Cycle entries. The current regex will include the 40 | // phases: Pause Remark and Pause Cleanup, something we are not interested. 41 | val tempMaxGc = mutableListOf() 42 | maxGC.groupBy { it.id } 43 | .filter { it.value.size > 1 }.forEach { 44 | val id = it.key 45 | val regex2 = """\[(\d+\.\d+)([a-z]+)]\[.*?\] GC\($id\)""".toRegex() 46 | val x = 47 | lines.last { 48 | val a = regex2.find(it) 49 | a != null 50 | } 51 | 52 | val regex3 = """\[(\d+\.\d+)([a-z]+)]\[.*?\] GC\((\d+)\) (.+?) (\d+\.\d+)([a-z]+)""".toRegex() 53 | val match = regex3.find(x) 54 | if (match != null) { 55 | val (timestamp, timestampUnit, id, description, duration, durationUnit) = match.destructured 56 | tempMaxGc.add( 57 | GCEntry( 58 | id = id, 59 | description = description, 60 | timeStamp = timestamp, 61 | timeStampUnit = timestampUnit, 62 | duration = duration, 63 | durationUnit = durationUnit, 64 | ), 65 | ) 66 | } 67 | } 68 | 69 | tempMaxGc.forEach { temp -> 70 | maxGC.removeIf { it.id == temp.id } 71 | maxGC.add(temp) 72 | } 73 | return maxGC 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## GC Report Plugin 2 | Gradle plugin that collects GC metrics based on the GC logs generated during the build. 3 | If [Develocity](https://gradle.com/develocity/) is configured in the project, the plugin publishes GC metrics as custom values. Otherwise, it generates a console report and a CSV file. 4 | 5 | Analyzing the types of garbage collections that occurred during the build can provide valuable insights into performance issues. 6 | 7 | **This plugin is intended for performance investigations and is not meant to be enabled in regular builds.** 8 | ### Usage 9 | #### Apply the plugin 10 | ```kotlin 11 | plugins { 12 | id("io.github.cdsap.gcreport") version "0.1.0" 13 | } 14 | ``` 15 | #### Configure JVM properties with the daemon log 16 | ```properties 17 | org.gradle.jvmargs=-Xlog:gc*:file=/project/gradle_gc.log 18 | ``` 19 | 20 | #### Configure the extension 21 | 22 | ```kotlin 23 | gcReport { 24 | logs = listOf("gradle_gc.log") 25 | } 26 | 27 | ``` 28 | 29 | #### Configuring GC Logs for Multiple Processes 30 | If your project involves multiple JVM processes, such as Gradle and Kotlin, you can configure the plugin to collect GC metrics from all of them by specifying multiple log files. 31 | 32 | For example, to capture GC logs from both Gradle and Kotlin daemon processes, configure their JVM arguments as follows: 33 | ``` 34 | org.gradle.jvmargs=-Xlog:gc*:file=/project/gradle_gc.log 35 | kotlin.daemon.jvmargs=-Xlog:gc*:file=/project/kotlin_gc.log 36 | ``` 37 | Then, update the plugin configuration to process both logs: 38 | ```kotlin 39 | gcReport { 40 | logs = listOf("gradle_gc.log","kotlin_gc.log") 41 | } 42 | ``` 43 | This ensures that GC metrics from both the Gradle build process and the Kotlin daemon are captured and analyzed. 44 | #### Output 45 | ##### Develocity 46 | ![resources/buildscanoutput.png](resources/buildscanoutput.png) 47 | ##### Console Output 48 | ``` 49 | ┌──────────────────────────────────────────────────────────────────────┐ 50 | │ GC Log: gc.log │ 51 | ├────────────────────────────────────────────────────────┬─────────────┤ 52 | │ Collection type │ Occurrences │ 53 | ├────────────────────────────────────────────────────────┼─────────────┤ 54 | │ Pause Young (Normal) (G1 Evacuation Pause) │ 6 │ 55 | ├────────────────────────────────────────────────────────┼─────────────┤ 56 | │ Pause Young (Concurrent Start) (Metadata GC Threshold) │ 4 │ 57 | ├────────────────────────────────────────────────────────┼─────────────┤ 58 | │ Pause Young (Prepare Mixed) (G1 Evacuation Pause) │ 3 │ 59 | ├────────────────────────────────────────────────────────┼─────────────┤ 60 | │ Pause Young (Mixed) (G1 Evacuation Pause) │ 3 │ 61 | ├────────────────────────────────────────────────────────┼─────────────┤ 62 | │ Concurrent Mark Cycle │ 4 │ 63 | └────────────────────────────────────────────────────────┴─────────────┘ 64 | ``` 65 | ##### CSV File Output 66 | ``` 67 | Collection type,Occurrences 68 | Pause Young (Normal) (G1 Evacuation Pause),8 69 | Pause Young (Concurrent Start) (Metadata GC Threshold),4 70 | Pause Young (Prepare Mixed) (G1 Evacuation Pause),1 71 | Pause Young (Mixed) (G1 Evacuation Pause),1 72 | Concurrent Mark Cycle,4 73 | ``` 74 | 75 | 76 | #### Configuring Histograms 77 | The plugin can generate histograms from the GC log data. This is useful for visualizing the distribution of GC events over time. 78 | You can configure the histograms by setting the `histogram` property in the `gcReport` extension: 79 | ```kotlin 80 | gcReport { 81 | logs.set(listOf("$gcLog")) 82 | histogramEnabled.set(true) 83 | histogramBucket.set(Bucket.SquareRoot) 84 | } 85 | ``` 86 | There are three bucket types available: 87 | * `Bucket.SquareRoot` - Uses the square root of the number of elements to determine the number of bins, often used for small datasets. 88 | * `Bucket.Sturges` - Uses Sturges' formula, which works well for normally distributed data. 89 | * `Bucket.FreedmanDiaconis` - Adapts the bin width based on data variability, useful for datasets with high variance. 90 | 91 | When enabling this option, the plugin will generate a histogram entry in Develocity as a custom value or a new CSV file: 92 | ``` 93 | Bucket,Occurrences 94 | 0.00-1.23,4 95 | 1.23-2.46,3 96 | 2.46-3.69,4 97 | 3.69-4.92,3 98 | 4.92-End,1 99 | ``` 100 | 101 | ### Considerations 102 | * Supported GC types: 103 | - G1 104 | - Parallel 105 | * The plugin output may be unreliable for incremental builds where log rotations have occurred. 106 | 107 | 108 | -------------------------------------------------------------------------------- /gradle-plugin/src/main/kotlin/io/github/cdsap/gcreport/plugin/GCReportService.kt: -------------------------------------------------------------------------------- 1 | package io.github.cdsap.gcreport.plugin 2 | 3 | import com.jakewharton.picnic.TextAlignment 4 | import com.jakewharton.picnic.table 5 | import io.github.cdsap.gcreport.plugin.extensions.getFileName 6 | import io.github.cdsap.gcreport.plugin.extensions.getFileNameCsvLog 7 | import io.github.cdsap.gcreport.plugin.histogram.Histogram 8 | import io.github.cdsap.gcreport.plugin.model.Bucket 9 | import io.github.cdsap.gcreport.plugin.parser.GCLogReader 10 | import org.gradle.api.file.Directory 11 | import org.gradle.api.provider.Provider 12 | import org.gradle.api.services.BuildService 13 | import org.gradle.api.services.BuildServiceParameters 14 | import org.gradle.tooling.events.FinishEvent 15 | import org.gradle.tooling.events.OperationCompletionListener 16 | import java.io.File 17 | 18 | abstract class GCReportService : BuildService, AutoCloseable, OperationCompletionListener { 19 | interface Params : BuildServiceParameters { 20 | var logs: Provider> 21 | var histogramEnabled: Provider 22 | var histogramBucket: Provider 23 | var buildOutput: Provider 24 | var enabledReport: Provider 25 | } 26 | 27 | override fun onFinish(event: FinishEvent?) {} 28 | 29 | override fun close() { 30 | if (parameters.enabledReport.get()) { 31 | parameters.logs.get().filter { File(it).exists() }.forEach { 32 | val gcEntries = GCLogReader(File(it)).parse() 33 | if (gcEntries.isEmpty()) { 34 | return 35 | } 36 | 37 | val file = File("${parameters.buildOutput.get()}/${it.getFileNameCsvLog()}") 38 | file.parentFile.mkdirs() 39 | 40 | val headers = "Collection type,Occurrences\n" 41 | var content = "" 42 | 43 | println( 44 | table { 45 | cellStyle { 46 | alignment = TextAlignment.MiddleLeft 47 | paddingLeft = 1 48 | paddingRight = 1 49 | border = true 50 | } 51 | row { 52 | cell("GC Log: ${it.getFileName()}") { 53 | columnSpan = 2 54 | } 55 | } 56 | row("Collection type", "Occurrences") 57 | gcEntries.groupBy { it.description }.forEach { (t, u) -> 58 | content += "$t,${u.size}\n" 59 | row { 60 | cell(t) 61 | cell(u.size) { 62 | alignment = TextAlignment.MiddleRight 63 | } 64 | } 65 | } 66 | }.toString(), 67 | ) 68 | file.writeText(headers + content) 69 | 70 | if (parameters.histogramEnabled.get()) { 71 | val fileHistogram = File("${parameters.buildOutput.get()}/histogram_${it.getFileNameCsvLog()}") 72 | fileHistogram.parentFile.mkdirs() 73 | val headersHistogram = "Bucket,Occurrences\n" 74 | var contentHistogram = "" 75 | 76 | println( 77 | table { 78 | cellStyle { 79 | alignment = TextAlignment.MiddleLeft 80 | paddingLeft = 1 81 | paddingRight = 1 82 | border = true 83 | } 84 | row { 85 | cell("GC Histogram: ${it.getFileName()}") { 86 | columnSpan = 2 87 | } 88 | } 89 | row { 90 | cell("Type: ${parameters.histogramBucket.get().name}") { 91 | columnSpan = 2 92 | } 93 | } 94 | row("Bucket", "Occurrences") 95 | val histogram = Histogram(parameters.histogramBucket.get()) 96 | val entries = 97 | histogram.getHistogram(gcEntries.filter { it.description != "Concurrent Mark Cycle" }) 98 | entries.forEach { 99 | contentHistogram += "${it.first},${it.second}\n" 100 | row { 101 | cell(it.first) 102 | cell(it.second) { 103 | alignment = TextAlignment.MiddleRight 104 | } 105 | } 106 | } 107 | }, 108 | ) 109 | fileHistogram.writeText(headersHistogram + contentHistogram) 110 | } 111 | } 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /gradle-plugin/src/test/java/io/github/cdsap/gcreport/plugin/histogram/HistogramTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.cdsap.gcreport.plugin.histogram 2 | 3 | import io.github.cdsap.gcreport.plugin.model.Bucket 4 | import io.github.cdsap.gcreport.plugin.model.GCEntry 5 | import org.junit.jupiter.api.Assertions.assertEquals 6 | import org.junit.jupiter.api.Test 7 | 8 | class HistogramTest { 9 | @Test 10 | fun `empty gcEntries returns empty histogram`() { 11 | val histogram = Histogram(Bucket.Sturges) 12 | val result = histogram.getHistogram(emptyList()) 13 | assertEquals(emptyList>(), result) 14 | } 15 | 16 | @Test 17 | fun `single gcEntry returns single bucket with End`() { 18 | val gcEntries = 19 | listOf( 20 | GCEntry("1.23", "s", "1", "G1", duration = "10", durationUnit = "ms"), 21 | ) 22 | val histogram = Histogram(Bucket.Sturges) 23 | val result = histogram.getHistogram(gcEntries) 24 | assertEquals(listOf("1.23-End" to 1), result) 25 | } 26 | 27 | @Test 28 | fun `multiple gcEntries with same timestamp return single bucket`() { 29 | val gcEntries = 30 | listOf( 31 | GCEntry("1.23", "s", "1", "G1", duration = "10", durationUnit = "ms"), 32 | GCEntry("1.23", "s", "2", "G1", duration = "12", durationUnit = "ms"), 33 | GCEntry("1.23", "s", "3", "G1", duration = "11", durationUnit = "ms"), 34 | ) 35 | val histogram = Histogram(Bucket.Sturges) 36 | val result = histogram.getHistogram(gcEntries) 37 | assertEquals(listOf("1.23-End" to 3), result) 38 | } 39 | 40 | @Test 41 | fun `multiple gcEntries with different timestamps return correct buckets`() { 42 | val gcEntries = 43 | listOf( 44 | GCEntry("1.23", "s", "1", "G1", duration = "10", durationUnit = "ms"), 45 | GCEntry("2.34", "s", "2", "G1", duration = "12", durationUnit = "ms"), 46 | GCEntry("3.45", "s", "3", "G1", duration = "11", durationUnit = "ms"), 47 | GCEntry("4.56", "s", "4", "G1", duration = "13", durationUnit = "ms"), 48 | ) 49 | val histogram = Histogram(Bucket.Sturges) 50 | val result = histogram.getHistogram(gcEntries) 51 | val expected = 52 | listOf( 53 | "0.00-1.11" to 0, 54 | "1.11-2.22" to 1, 55 | "2.22-3.33" to 1, 56 | "3.33-4.44" to 1, 57 | "4.44-End" to 1, 58 | ) 59 | assertEquals(expected, result) 60 | } 61 | 62 | @Test 63 | fun `gcEntries with timestamps on bucket boundaries are grouped correctly`() { 64 | val gcEntries = 65 | listOf( 66 | GCEntry("1.00", "s", "1", "G1", duration = "10", durationUnit = "ms"), 67 | GCEntry("2.00", "s", "2", "G1", duration = "12", durationUnit = "ms"), 68 | GCEntry("3.00", "s", "3", "G1", duration = "11", durationUnit = "ms"), 69 | ) 70 | val histogram = Histogram(Bucket.Sturges) 71 | val result = histogram.getHistogram(gcEntries) 72 | val expected = 73 | listOf( 74 | "0.00-0.67" to 0, 75 | "0.67-1.34" to 1, 76 | "1.34-2.01" to 1, 77 | "2.01-2.68" to 0, 78 | "2.68-End" to 1, 79 | ) 80 | assertEquals(expected, result) 81 | } 82 | 83 | @Test 84 | fun `multiple gcEntries with SquareRoot bucket return correct buckets`() { 85 | val gcEntries = 86 | listOf( 87 | GCEntry("1.23", "s", "1", "G1", duration = "10", durationUnit = "ms"), 88 | GCEntry("2.34", "s", "2", "G1", duration = "12", durationUnit = "ms"), 89 | GCEntry("3.45", "s", "3", "G1", duration = "11", durationUnit = "ms"), 90 | GCEntry("4.56", "s", "4", "G1", duration = "13", durationUnit = "ms"), 91 | ) 92 | val histogram = Histogram(Bucket.SquareRoot) // Use SquareRoot bucket 93 | val result = histogram.getHistogram(gcEntries) 94 | 95 | // Expected buckets based on SquareRoot calculation 96 | val expected = 97 | listOf( 98 | "0.00-1.66" to 1, 99 | "1.66-3.32" to 1, 100 | "3.32-End" to 2, 101 | ) 102 | assertEquals(expected, result) 103 | } 104 | 105 | @Test 106 | fun `gcEntries with timestamps on bucket boundaries with FreedmanDiaconis are grouped correctly`() { 107 | val gcEntries = 108 | listOf( 109 | GCEntry("1.00", "s", "1", "G1", duration = "10", durationUnit = "ms"), 110 | GCEntry("2.00", "s", "2", "G1", duration = "12", durationUnit = "ms"), 111 | GCEntry("3.00", "s", "3", "G1", duration = "11", durationUnit = "ms"), 112 | ) 113 | val histogram = Histogram(Bucket.FreedmanDiaconis) // Use FreedmanDiaconis bucket 114 | val result = histogram.getHistogram(gcEntries) 115 | 116 | // Expected buckets based on FreedmanDiaconis calculation 117 | val expected = 118 | listOf( 119 | "0.00-2.77" to 2, 120 | "2.77-End" to 1, 121 | ) 122 | assertEquals(expected, result) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit 88 | 89 | # Use the maximum available, or set MAX_FD != -1 to use that value. 90 | MAX_FD=maximum 91 | 92 | warn () { 93 | echo "$*" 94 | } >&2 95 | 96 | die () { 97 | echo 98 | echo "$*" 99 | echo 100 | exit 1 101 | } >&2 102 | 103 | # OS specific support (must be 'true' or 'false'). 104 | cygwin=false 105 | msys=false 106 | darwin=false 107 | nonstop=false 108 | case "$( uname )" in #( 109 | CYGWIN* ) cygwin=true ;; #( 110 | Darwin* ) darwin=true ;; #( 111 | MSYS* | MINGW* ) msys=true ;; #( 112 | NONSTOP* ) nonstop=true ;; 113 | esac 114 | 115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 116 | 117 | 118 | # Determine the Java command to use to start the JVM. 119 | if [ -n "$JAVA_HOME" ] ; then 120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 121 | # IBM's JDK on AIX uses strange locations for the executables 122 | JAVACMD=$JAVA_HOME/jre/sh/java 123 | else 124 | JAVACMD=$JAVA_HOME/bin/java 125 | fi 126 | if [ ! -x "$JAVACMD" ] ; then 127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 128 | 129 | Please set the JAVA_HOME variable in your environment to match the 130 | location of your Java installation." 131 | fi 132 | else 133 | JAVACMD=java 134 | if ! command -v java >/dev/null 2>&1 135 | then 136 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | fi 142 | 143 | # Increase the maximum file descriptors if we can. 144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 145 | case $MAX_FD in #( 146 | max*) 147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 148 | # shellcheck disable=SC2039,SC3045 149 | MAX_FD=$( ulimit -H -n ) || 150 | warn "Could not query maximum file descriptor limit" 151 | esac 152 | case $MAX_FD in #( 153 | '' | soft) :;; #( 154 | *) 155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 156 | # shellcheck disable=SC2039,SC3045 157 | ulimit -n "$MAX_FD" || 158 | warn "Could not set maximum file descriptor limit to $MAX_FD" 159 | esac 160 | fi 161 | 162 | # Collect all arguments for the java command, stacking in reverse order: 163 | # * args from the command line 164 | # * the main class name 165 | # * -classpath 166 | # * -D...appname settings 167 | # * --module-path (only if needed) 168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 169 | 170 | # For Cygwin or MSYS, switch paths to Windows format before running java 171 | if "$cygwin" || "$msys" ; then 172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command: 206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 207 | # and any embedded shellness will be escaped. 208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 209 | # treated as '${Hostname}' itself on the command line. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -classpath "$CLASSPATH" \ 214 | org.gradle.wrapper.GradleWrapperMain \ 215 | "$@" 216 | 217 | # Stop when "xargs" is not available. 218 | if ! command -v xargs >/dev/null 2>&1 219 | then 220 | die "xargs is not available" 221 | fi 222 | 223 | # Use "xargs" to parse quoted args. 224 | # 225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 226 | # 227 | # In Bash we could simply go: 228 | # 229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 230 | # set -- "${ARGS[@]}" "$@" 231 | # 232 | # but POSIX shell has neither arrays nor command substitution, so instead we 233 | # post-process each arg (as a line of input to sed) to backslash-escape any 234 | # character that might be a shell metacharacter, then use eval to reverse 235 | # that process (while maintaining the separation between arguments), and wrap 236 | # the whole thing up as a single "set" statement. 237 | # 238 | # This will of course break if any of these variables contains a newline or 239 | # an unmatched quote. 240 | # 241 | 242 | eval "set -- $( 243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 244 | xargs -n1 | 245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 246 | tr '\n' ' ' 247 | )" '"$@"' 248 | 249 | exec "$JAVACMD" "$@" 250 | -------------------------------------------------------------------------------- /gradle-plugin/src/test/java/io/github/cdsap/gcreport/plugin/GCReportPluginWithoutDevelocityTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.cdsap.gcreport.plugin 2 | 3 | import org.gradle.testkit.runner.GradleRunner 4 | import org.junit.jupiter.api.Assertions.assertTrue 5 | import org.junit.jupiter.api.Test 6 | import org.junit.jupiter.api.io.TempDir 7 | import java.io.File 8 | 9 | class GCReportPluginWithoutDevelocityTest { 10 | @TempDir 11 | lateinit var testProjectDir: File 12 | 13 | @Test 14 | fun `plugin generates Output with Gc report for G1`() { 15 | val gradleProperties = File(testProjectDir, "gradle.properties") 16 | val gcLog = "${testProjectDir.absolutePath}/gc.log" 17 | gradleProperties.writeText( 18 | """ 19 | org.gradle.jvmargs=-Xlog:gc*:file=$gcLog 20 | """.trimIndent(), 21 | ) 22 | 23 | val buildFile = File(testProjectDir, "build.gradle.kts") 24 | 25 | buildFile.writeText( 26 | """ 27 | plugins { 28 | id("io.github.cdsap.gcreport") 29 | java 30 | } 31 | 32 | gcReport { 33 | logs.set(listOf("$gcLog")) 34 | } 35 | """, 36 | ) 37 | 38 | val result = 39 | GradleRunner.create() 40 | .withProjectDir(testProjectDir) 41 | .withArguments("tasks") 42 | .withPluginClasspath() 43 | .build() 44 | assertTrue(result.output.contains("GC Log: gc.log")) 45 | assertTrue(result.output.contains("Collection type")) 46 | assertTrue(!result.output.contains("GC Histogram: gc.log")) 47 | assertTrue(!result.output.contains("Type: SquareRoot")) 48 | assertTrue(testProjectDir.resolve("build/reports/gcreport/gc.csv").exists()) 49 | } 50 | 51 | @Test 52 | fun `plugin generates Output with Gc report for Parallel`() { 53 | val gradleProperties = File(testProjectDir, "gradle.properties") 54 | val gcLog = "${testProjectDir.absolutePath}/gc.log" 55 | gradleProperties.writeText( 56 | """ 57 | org.gradle.jvmargs=-Xlog:gc*:file=$gcLog -XX:+UseParallelGC 58 | """.trimIndent(), 59 | ) 60 | 61 | val buildFile = File(testProjectDir, "build.gradle.kts") 62 | 63 | buildFile.writeText( 64 | """ 65 | plugins { 66 | id("io.github.cdsap.gcreport") 67 | java 68 | } 69 | 70 | gcReport { 71 | logs.set(listOf("$gcLog")) 72 | } 73 | """, 74 | ) 75 | 76 | val result = 77 | GradleRunner.create() 78 | .withProjectDir(testProjectDir) 79 | .withArguments("tasks") 80 | .withPluginClasspath() 81 | .build() 82 | 83 | assertTrue(result.output.contains("GC Log: gc.log")) 84 | assertTrue(result.output.contains("Collection type")) 85 | assertTrue(!result.output.contains("GC Histogram: gc.log")) 86 | assertTrue(!result.output.contains("Type: SquareRoot")) 87 | } 88 | 89 | @Test 90 | fun `plugin generates Output with Gc report and Histogram`() { 91 | val gradleProperties = File(testProjectDir, "gradle.properties") 92 | val gcLog = "${testProjectDir.absolutePath}/gc.log" 93 | gradleProperties.writeText( 94 | """ 95 | org.gradle.jvmargs=-Xlog:gc*:file=$gcLog 96 | """.trimIndent(), 97 | ) 98 | 99 | val buildFile = File(testProjectDir, "build.gradle.kts") 100 | 101 | buildFile.writeText( 102 | """ 103 | plugins { 104 | id("io.github.cdsap.gcreport") 105 | java 106 | } 107 | 108 | gcReport { 109 | logs.set(listOf("$gcLog")) 110 | histogramEnabled.set(true) 111 | histogramBucket.set(io.github.cdsap.gcreport.plugin.model.Bucket.SquareRoot) 112 | } 113 | """, 114 | ) 115 | 116 | val result = 117 | GradleRunner.create() 118 | .withProjectDir(testProjectDir) 119 | .withArguments("tasks") 120 | .withPluginClasspath() 121 | .build() 122 | 123 | assertTrue(result.output.contains("GC Log: gc.log")) 124 | assertTrue(result.output.contains("Collection type")) 125 | assertTrue(result.output.contains("GC Histogram: gc.log")) 126 | assertTrue(result.output.contains("Type: SquareRoot")) 127 | assertTrue(testProjectDir.resolve("build/reports/gcreport/gc.csv").exists()) 128 | assertTrue(testProjectDir.resolve("build/reports/gcreport/histogram_gc.csv").exists()) 129 | } 130 | 131 | @Test 132 | fun `plugin doesn't generate output if file is incorrect`() { 133 | val gradleProperties = File(testProjectDir, "gradle.properties") 134 | val gcLog = "${testProjectDir.absolutePath}/gc.log" 135 | val gcLogIncorrect = "${testProjectDir.absolutePath}/gc2.log" 136 | gradleProperties.writeText( 137 | """ 138 | org.gradle.jvmargs=-Xlog:gc*:file=$gcLog 139 | """.trimIndent(), 140 | ) 141 | 142 | val buildFile = File(testProjectDir, "build.gradle.kts") 143 | 144 | buildFile.writeText( 145 | """ 146 | plugins { 147 | id("io.github.cdsap.gcreport") 148 | java 149 | } 150 | 151 | gcReport { 152 | logs.set(listOf("$gcLogIncorrect")) 153 | } 154 | """, 155 | ) 156 | 157 | val result = 158 | GradleRunner.create() 159 | .withProjectDir(testProjectDir) 160 | .withArguments("tasks") 161 | .withPluginClasspath() 162 | .build() 163 | 164 | assertTrue(!result.output.contains("GC Log: gc.log")) 165 | assertTrue(!result.output.contains("Collection type")) 166 | assertTrue(!result.output.contains("GC Histogram: gc.log")) 167 | assertTrue(!result.output.contains("Type: SquareRoot")) 168 | } 169 | 170 | @Test 171 | fun `plugin supports kotlin and gradle gc logs`() { 172 | val gradleProperties = File(testProjectDir, "gradle.properties") 173 | val gcLog = "${testProjectDir.absolutePath}/gradle_gc.log" 174 | val gcKotlinLog = "${testProjectDir.absolutePath}/kotlin_gc.log" 175 | gradleProperties.writeText( 176 | """ 177 | org.gradle.jvmargs=-Xlog:gc*:file=$gcLog 178 | kotlin.daemon.jvmargs=-Xlog:gc*:file=$gcKotlinLog 179 | """.trimIndent(), 180 | ) 181 | 182 | val buildFile = File(testProjectDir, "build.gradle.kts") 183 | 184 | buildFile.writeText( 185 | """ 186 | 187 | plugins { 188 | id("io.github.cdsap.gcreport") 189 | kotlin("jvm") version "2.1.0" 190 | } 191 | 192 | repositories { 193 | mavenCentral() 194 | } 195 | 196 | gcReport { 197 | logs.set(listOf("$gcLog","$gcKotlinLog")) 198 | histogramEnabled.set(true) 199 | } 200 | """, 201 | ) 202 | 203 | val kotlinClassContent = 204 | """ 205 | package com.example 206 | 207 | class MyTestClass { 208 | fun hello() { 209 | println("Hello from MyTestClass!") 210 | } 211 | } 212 | """.trimIndent() 213 | 214 | val kotlinFile = File(testProjectDir, "src/main/kotlin/com/example/MyTestClass.kt") 215 | kotlinFile.parentFile.mkdirs() 216 | kotlinFile.writeText(kotlinClassContent) 217 | 218 | val result = 219 | GradleRunner.create() 220 | .withProjectDir(testProjectDir) 221 | .withArguments("assemble") 222 | .withPluginClasspath() 223 | .build() 224 | 225 | assertTrue(result.output.contains("GC Log: gradle_gc.log")) 226 | assertTrue(result.output.contains("GC Histogram: gradle_gc.log")) 227 | assertTrue(result.output.contains("GC Log: kotlin_gc.log")) 228 | assertTrue(result.output.contains("GC Histogram: kotlin_gc.log")) 229 | assertTrue(result.output.contains("Collection type")) 230 | assertTrue(result.output.contains("Type: FreedmanDiaconis")) 231 | assertTrue(testProjectDir.resolve("build/reports/gcreport/gradle_gc.csv").exists()) 232 | assertTrue(testProjectDir.resolve("build/reports/gcreport/histogram_gradle_gc.csv").exists()) 233 | assertTrue(testProjectDir.resolve("build/reports/gcreport/kotlin_gc.csv").exists()) 234 | assertTrue(testProjectDir.resolve("build/reports/gcreport/histogram_kotlin_gc.csv").exists()) 235 | } 236 | 237 | @Test 238 | fun `plugin compatible with configuration cache`() { 239 | val gradleProperties = File(testProjectDir, "gradle.properties") 240 | val gcLog = "${testProjectDir.absolutePath}/gc.log" 241 | gradleProperties.writeText( 242 | """ 243 | org.gradle.jvmargs=-Xlog:gc*:file=$gcLog 244 | """.trimIndent(), 245 | ) 246 | 247 | val buildFile = File(testProjectDir, "build.gradle.kts") 248 | 249 | buildFile.writeText( 250 | """ 251 | plugins { 252 | id("io.github.cdsap.gcreport") 253 | id("io.github.cdsap.gradleprocess") version "0.1.2" 254 | java 255 | } 256 | 257 | gcReport { 258 | logs.set(listOf("$gcLog")) 259 | } 260 | """, 261 | ) 262 | 263 | GradleRunner.create() 264 | .withProjectDir(testProjectDir) 265 | .withArguments("clean", "assemble", "--configuration-cache") 266 | .withPluginClasspath() 267 | .build() 268 | 269 | val withConfigurationCache = 270 | GradleRunner.create() 271 | .withProjectDir(testProjectDir) 272 | .withArguments("clean", "assemble", "--configuration-cache") 273 | .withPluginClasspath() 274 | .build() 275 | assertTrue(withConfigurationCache.output.contains("Reusing configuration cache.")) 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /gradle-plugin/src/test/java/io/github/cdsap/gcreport/plugin/GCReportPluginWithDevelocityTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.cdsap.gcreport.plugin 2 | 3 | import com.google.gson.Gson 4 | import io.github.cdsap.gcreport.plugin.model.Response 5 | import io.github.cdsap.gcreport.plugin.model.Value 6 | import io.ktor.client.HttpClient 7 | import io.ktor.client.call.body 8 | import io.ktor.client.engine.cio.CIO 9 | import io.ktor.client.request.get 10 | import io.ktor.client.request.header 11 | import io.ktor.client.request.parameter 12 | import kotlinx.coroutines.delay 13 | import kotlinx.coroutines.runBlocking 14 | import org.gradle.internal.impldep.org.junit.Assume 15 | import org.gradle.testkit.runner.GradleRunner 16 | import org.junit.jupiter.api.Assertions.assertTrue 17 | import org.junit.jupiter.api.Test 18 | import org.junit.jupiter.api.io.TempDir 19 | import java.io.File 20 | import kotlin.random.Random 21 | 22 | class GCReportPluginWithDevelocityTest { 23 | @TempDir 24 | lateinit var testProjectDir: File 25 | 26 | @Test 27 | fun `plugin generates Output with Gc report for G1`() { 28 | Assume.assumeTrue( 29 | "Develocity URL and Access Key are set", 30 | System.getenv("GE_URL") != null && System.getenv("GE_API_KEY") != null, 31 | ) 32 | val develocityUrl = System.getenv("GE_URL") 33 | val develocityAccessKey = System.getenv("GE_API_KEY") 34 | 35 | val gcFile = "gc.log" 36 | val gradleProperties = File(testProjectDir, "gradle.properties") 37 | val gcLog = "${testProjectDir.absolutePath}/$gcFile" 38 | gradleProperties.writeText( 39 | """ 40 | org.gradle.jvmargs=-Xlog:gc*:file=$gcLog 41 | """.trimIndent(), 42 | ) 43 | val settingsGradle = File(testProjectDir, "settings.gradle.kts") 44 | val randomValue = Random.nextInt(Int.MAX_VALUE).toString() 45 | 46 | settingsGradle.writeText( 47 | """ 48 | plugins { 49 | id("com.gradle.develocity") version "3.19" 50 | } 51 | gradleEnterprise { 52 | server = "$develocityUrl" 53 | accessKey="$develocityAccessKey" 54 | buildScan { 55 | isUploadInBackground = false 56 | tag("$randomValue") 57 | 58 | } 59 | } 60 | """.trimIndent(), 61 | ) 62 | 63 | val buildFile = File(testProjectDir, "build.gradle.kts") 64 | 65 | buildFile.writeText( 66 | """ 67 | plugins { 68 | id("io.github.cdsap.gcreport") 69 | java 70 | } 71 | 72 | gcReport { 73 | logs.set(listOf("$gcLog")) 74 | } 75 | """, 76 | ) 77 | 78 | GradleRunner.create() 79 | .withProjectDir(testProjectDir) 80 | .withArguments("tasks") 81 | .withPluginClasspath() 82 | .build() 83 | 84 | val gcLogs = mutableListOf() 85 | runBlocking { 86 | val client = HttpClient(CIO) {} 87 | try { 88 | delay(10000) 89 | val response = 90 | client.get("${develocityUrl}api/builds") { 91 | header("Authorization", "Bearer $develocityAccessKey") 92 | parameter("maxBuilds", 1) 93 | parameter("models", "gradle-attributes") 94 | parameter("reverse", true) 95 | parameter("query", "tag:$randomValue") 96 | } 97 | val responseBody: String = response.body() 98 | val gson = Gson() 99 | println(responseBody) 100 | val responseArray = gson.fromJson(responseBody, Array::class.java) 101 | gcLogs.addAll(responseArray[0].models.gradleAttributes.model.values) 102 | } catch (e: Exception) { 103 | println(e) 104 | } 105 | } 106 | assertTrue(gcLogs.isNotEmpty()) 107 | assertTrue(gcLogs.any { it.name == "gc-$gcFile-total-collections" }) 108 | assertTrue(gcLogs.any { it.name == "gc-$gcFile-Pause Young (Normal) (G1 Evacuation Pause)" }) 109 | } 110 | 111 | @Test 112 | fun `plugin generates Output with Gc report for Parallel`() { 113 | Assume.assumeTrue( 114 | "Develocity URL and Access Key are set", 115 | System.getenv("GE_URL") != null && System.getenv("GE_API_KEY") != null, 116 | ) 117 | 118 | val develocityUrl = System.getenv("GE_URL") 119 | val develocityAccessKey = System.getenv("GE_API_KEY") 120 | val gcFile = "gc.log" 121 | 122 | val gradleProperties = File(testProjectDir, "gradle.properties") 123 | val gcLog = "${testProjectDir.absolutePath}/$gcFile" 124 | gradleProperties.writeText( 125 | """ 126 | org.gradle.jvmargs=-Xlog:gc*:file=$gcLog -XX:+UseParallelGC 127 | """.trimIndent(), 128 | ) 129 | val settingsGradle = File(testProjectDir, "settings.gradle.kts") 130 | val randomValue = Random.nextInt(Int.MAX_VALUE).toString() 131 | 132 | settingsGradle.writeText( 133 | """ 134 | plugins { 135 | id("com.gradle.develocity") version "3.19" 136 | } 137 | gradleEnterprise { 138 | server = "$develocityUrl" 139 | accessKey="$develocityAccessKey" 140 | buildScan { 141 | isUploadInBackground = false 142 | tag("$randomValue") 143 | 144 | } 145 | } 146 | """.trimIndent(), 147 | ) 148 | 149 | val buildFile = File(testProjectDir, "build.gradle.kts") 150 | 151 | buildFile.writeText( 152 | """ 153 | plugins { 154 | id("io.github.cdsap.gcreport") 155 | java 156 | } 157 | 158 | gcReport { 159 | logs.set(listOf("$gcLog")) 160 | } 161 | """, 162 | ) 163 | 164 | GradleRunner.create() 165 | .withProjectDir(testProjectDir) 166 | .withArguments("tasks") 167 | .withPluginClasspath() 168 | .build() 169 | 170 | val gcLogs = mutableListOf() 171 | runBlocking { 172 | val client = HttpClient(CIO) {} 173 | try { 174 | delay(10000) 175 | val response = 176 | client.get("${develocityUrl}api/builds") { 177 | header("Authorization", "Bearer $develocityAccessKey") 178 | parameter("maxBuilds", 1) 179 | parameter("models", "gradle-attributes") 180 | parameter("reverse", true) 181 | parameter("query", "tag:$randomValue") 182 | } 183 | val responseBody: String = response.body() 184 | val gson = Gson() 185 | val responseArray = gson.fromJson(responseBody, Array::class.java) 186 | gcLogs.addAll(responseArray[0].models.gradleAttributes.model.values) 187 | } catch (e: Exception) { 188 | println(e) 189 | } 190 | } 191 | assertTrue(gcLogs.isNotEmpty()) 192 | assertTrue(gcLogs.any { it.name == "gc-$gcFile-total-collections" }) 193 | assertTrue(gcLogs.any { it.name == "gc-$gcFile-Pause Full (Metadata GC Threshold)" }) 194 | assertTrue(gcLogs.any { it.name == "gc-$gcFile-Pause Young (Metadata GC Threshold)" }) 195 | } 196 | 197 | @Test 198 | fun `plugin generates Output with Gc report and Histogram`() { 199 | Assume.assumeTrue( 200 | "Develocity URL and Access Key are set", 201 | System.getenv("GE_URL") != null && System.getenv("GE_API_KEY") != null, 202 | ) 203 | 204 | val develocityUrl = System.getenv("GE_URL") 205 | val develocityAccessKey = System.getenv("GE_API_KEY") 206 | val gcFile = "gc.log" 207 | val settingsGradle = File(testProjectDir, "settings.gradle.kts") 208 | val randomValue = Random.nextInt(Int.MAX_VALUE).toString() 209 | 210 | settingsGradle.writeText( 211 | """ 212 | plugins { 213 | id("com.gradle.develocity") version "3.19" 214 | } 215 | gradleEnterprise { 216 | server = "$develocityUrl" 217 | accessKey="$develocityAccessKey" 218 | buildScan { 219 | isUploadInBackground = false 220 | tag("$randomValue") 221 | 222 | } 223 | } 224 | """.trimIndent(), 225 | ) 226 | 227 | val gradleProperties = File(testProjectDir, "gradle.properties") 228 | val gcLog = "${testProjectDir.absolutePath}/$gcFile" 229 | gradleProperties.writeText( 230 | """ 231 | org.gradle.jvmargs=-Xlog:gc*:file=$gcLog 232 | """.trimIndent(), 233 | ) 234 | 235 | val buildFile = File(testProjectDir, "build.gradle.kts") 236 | 237 | buildFile.writeText( 238 | """ 239 | plugins { 240 | id("io.github.cdsap.gcreport") 241 | java 242 | } 243 | 244 | gcReport { 245 | logs.set(listOf("$gcLog")) 246 | histogramEnabled.set(true) 247 | histogramBucket.set(io.github.cdsap.gcreport.plugin.model.Bucket.SquareRoot) 248 | } 249 | """, 250 | ) 251 | 252 | GradleRunner.create() 253 | .withProjectDir(testProjectDir) 254 | .withArguments("tasks") 255 | .withPluginClasspath() 256 | .build() 257 | val gcLogs = mutableListOf() 258 | runBlocking { 259 | val client = HttpClient(CIO) {} 260 | try { 261 | delay(10000) 262 | val response = 263 | client.get("${develocityUrl}api/builds") { 264 | header("Authorization", "Bearer $develocityAccessKey") 265 | parameter("maxBuilds", 1) 266 | parameter("models", "gradle-attributes") 267 | parameter("reverse", true) 268 | parameter("query", "tag:$randomValue") 269 | } 270 | val responseBody: String = response.body() 271 | val gson = Gson() 272 | val responseArray = gson.fromJson(responseBody, Array::class.java) 273 | gcLogs.addAll(responseArray[0].models.gradleAttributes.model.values) 274 | } catch (e: Exception) { 275 | println(e) 276 | } 277 | } 278 | assertTrue(gcLogs.isNotEmpty()) 279 | assertTrue(gcLogs.any { it.name == "gc-$gcFile-total-collections" }) 280 | assertTrue(gcLogs.any { it.name == "gc-$gcFile-histogram" }) 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /gradle-plugin/src/test/resources/correct_g1_log: -------------------------------------------------------------------------------- 1 | [0.002s][warning][gc] -XX:+PrintGC is deprecated. Will use -Xlog:gc instead. 2 | [0.006s][info ][gc] Using G1 3 | [0.010s][info ][gc,init] Version: 17.0.3.1+2-LTS (release) 4 | [0.010s][info ][gc,init] CPUs: 10 total, 10 available 5 | [0.010s][info ][gc,init] Memory: 65536M 6 | [0.010s][info ][gc,init] Large Page Support: Disabled 7 | [0.010s][info ][gc,init] NUMA Support: Disabled 8 | [0.010s][info ][gc,init] Compressed Oops: Enabled (Zero based) 9 | [0.010s][info ][gc,init] Heap Region Size: 2M 10 | [0.010s][info ][gc,init] Heap Min Capacity: 4G 11 | [0.010s][info ][gc,init] Heap Initial Capacity: 4G 12 | [0.011s][info ][gc,init] Heap Max Capacity: 4G 13 | [0.011s][info ][gc,init] Pre-touch: Disabled 14 | [0.011s][info ][gc,init] Parallel Workers: 9 15 | [0.011s][info ][gc,init] Concurrent Workers: 2 16 | [0.011s][info ][gc,init] Concurrent Refinement Workers: 9 17 | [0.011s][info ][gc,init] Periodic GC: Disabled 18 | [0.011s][info ][gc,metaspace] CDS archive(s) mapped at: [0x0000000800000000-0x0000000800bd4000-0x0000000800bd4000), size 12402688, SharedBaseAddress: 0x0000000800000000, ArchiveRelocationMode: 0. 19 | [0.011s][info ][gc,metaspace] Compressed class space mapped at: 0x0000000800c00000-0x0000000840c00000, reserved size: 1073741824 20 | [0.011s][info ][gc,metaspace] Narrow klass base: 0x0000000800000000, Narrow klass shift: 0, Narrow klass range: 0x100000000 21 | [1.159s][info ][gc,start ] GC(0) Pause Young (Concurrent Start) (Metadata GC Threshold) 22 | [1.160s][info ][gc,task ] GC(0) Using 9 workers of 9 for evacuation 23 | [1.164s][info ][gc,phases ] GC(0) Pre Evacuate Collection Set: 0.2ms 24 | [1.164s][info ][gc,phases ] GC(0) Merge Heap Roots: 0.0ms 25 | [1.164s][info ][gc,phases ] GC(0) Evacuate Collection Set: 3.9ms 26 | [1.164s][info ][gc,phases ] GC(0) Post Evacuate Collection Set: 0.4ms 27 | [1.164s][info ][gc,phases ] GC(0) Other: 0.4ms 28 | [1.164s][info ][gc,heap ] GC(0) Eden regions: 95->0(92) 29 | [1.164s][info ][gc,heap ] GC(0) Survivor regions: 0->10(13) 30 | [1.164s][info ][gc,heap ] GC(0) Old regions: 0->0 31 | [1.164s][info ][gc,heap ] GC(0) Archive regions: 2->2 32 | [1.164s][info ][gc,heap ] GC(0) Humongous regions: 2->2 33 | [1.164s][info ][gc,metaspace] GC(0) Metaspace: 21226K(21504K)->21226K(21504K) NonClass: 18044K(18176K)->18044K(18176K) Class: 3181K(3328K)->3181K(3328K) 34 | [1.164s][info ][gc ] GC(0) Pause Young (Concurrent Start) (Metadata GC Threshold) 194M->24M(4096M) 4.981ms 35 | [1.164s][info ][gc,cpu ] GC(0) User=0.03s Sys=0.01s Real=0.01s 36 | [1.164s][info ][gc ] GC(1) Concurrent Mark Cycle 37 | [1.164s][info ][gc,marking ] GC(1) Concurrent Clear Claimed Marks 38 | [1.164s][info ][gc,marking ] GC(1) Concurrent Clear Claimed Marks 0.017ms 39 | [1.164s][info ][gc,marking ] GC(1) Concurrent Scan Root Regions 40 | [1.167s][info ][gc,marking ] GC(1) Concurrent Scan Root Regions 2.260ms 41 | [1.167s][info ][gc,marking ] GC(1) Concurrent Mark 42 | [1.167s][info ][gc,marking ] GC(1) Concurrent Mark From Roots 43 | [1.167s][info ][gc,task ] GC(1) Using 2 workers of 2 for marking 44 | [1.167s][info ][gc,marking ] GC(1) Concurrent Mark From Roots 0.557ms 45 | [1.167s][info ][gc,marking ] GC(1) Concurrent Preclean 46 | [1.167s][info ][gc,marking ] GC(1) Concurrent Preclean 0.015ms 47 | [1.170s][info ][gc,start ] GC(1) Pause Remark 48 | [1.171s][info ][gc ] GC(1) Pause Remark 25M->25M(4096M) 0.941ms 49 | [1.171s][info ][gc,cpu ] GC(1) User=0.00s Sys=0.00s Real=0.00s 50 | [1.171s][info ][gc,marking ] GC(1) Concurrent Mark 3.950ms 51 | [1.171s][info ][gc,marking ] GC(1) Concurrent Rebuild Remembered Sets 52 | [1.171s][info ][gc,marking ] GC(1) Concurrent Rebuild Remembered Sets 0.007ms 53 | [1.171s][info ][gc,start ] GC(1) Pause Cleanup 54 | [1.171s][info ][gc ] GC(1) Pause Cleanup 26M->26M(4096M) 0.010ms 55 | [1.171s][info ][gc,cpu ] GC(1) User=0.00s Sys=0.00s Real=0.00s 56 | [1.171s][info ][gc,marking ] GC(1) Concurrent Cleanup for Next Mark 57 | [1.175s][info ][gc,marking ] GC(1) Concurrent Cleanup for Next Mark 4.430ms 58 | [1.175s][info ][gc ] GC(1) Concurrent Mark Cycle 10.974ms 59 | [1.711s][info ][gc,start ] GC(2) Pause Young (Concurrent Start) (Metadata GC Threshold) 60 | [1.711s][info ][gc,task ] GC(2) Using 9 workers of 9 for evacuation 61 | [1.719s][info ][gc,phases ] GC(2) Pre Evacuate Collection Set: 0.1ms 62 | [1.719s][info ][gc,phases ] GC(2) Merge Heap Roots: 0.0ms 63 | [1.719s][info ][gc,phases ] GC(2) Evacuate Collection Set: 7.2ms 64 | [1.719s][info ][gc,phases ] GC(2) Post Evacuate Collection Set: 0.6ms 65 | [1.719s][info ][gc,phases ] GC(2) Other: 0.2ms 66 | [1.719s][info ][gc,heap ] GC(2) Eden regions: 60->0(97) 67 | [1.719s][info ][gc,heap ] GC(2) Survivor regions: 10->5(13) 68 | [1.719s][info ][gc,heap ] GC(2) Old regions: 0->10 69 | [1.719s][info ][gc,heap ] GC(2) Archive regions: 2->2 70 | [1.719s][info ][gc,heap ] GC(2) Humongous regions: 2->2 71 | [1.719s][info ][gc,metaspace] GC(2) Metaspace: 35479K(35968K)->35479K(35968K) NonClass: 30466K(30720K)->30466K(30720K) Class: 5012K(5248K)->5012K(5248K) 72 | [1.719s][info ][gc ] GC(2) Pause Young (Concurrent Start) (Metadata GC Threshold) 144M->33M(4096M) 8.273ms 73 | [1.719s][info ][gc,cpu ] GC(2) User=0.04s Sys=0.02s Real=0.01s 74 | [1.719s][info ][gc ] GC(3) Concurrent Mark Cycle 75 | [1.719s][info ][gc,marking ] GC(3) Concurrent Clear Claimed Marks 76 | [1.720s][info ][gc,marking ] GC(3) Concurrent Clear Claimed Marks 0.029ms 77 | [1.720s][info ][gc,marking ] GC(3) Concurrent Scan Root Regions 78 | [1.723s][info ][gc,marking ] GC(3) Concurrent Scan Root Regions 3.880ms 79 | [1.723s][info ][gc,marking ] GC(3) Concurrent Mark 80 | [1.723s][info ][gc,marking ] GC(3) Concurrent Mark From Roots 81 | [1.723s][info ][gc,task ] GC(3) Using 2 workers of 2 for marking 82 | [1.724s][info ][gc,marking ] GC(3) Concurrent Mark From Roots 0.588ms 83 | [1.724s][info ][gc,marking ] GC(3) Concurrent Preclean 84 | [1.724s][info ][gc,marking ] GC(3) Concurrent Preclean 0.016ms 85 | [1.724s][info ][gc,start ] GC(3) Pause Remark 86 | [1.726s][info ][gc ] GC(3) Pause Remark 35M->35M(4096M) 1.288ms 87 | [1.726s][info ][gc,cpu ] GC(3) User=0.01s Sys=0.00s Real=0.00s 88 | [1.726s][info ][gc,marking ] GC(3) Concurrent Mark 2.188ms 89 | [1.726s][info ][gc,marking ] GC(3) Concurrent Rebuild Remembered Sets 90 | [1.729s][info ][gc,marking ] GC(3) Concurrent Rebuild Remembered Sets 2.860ms 91 | [1.729s][info ][gc,start ] GC(3) Pause Cleanup 92 | [1.729s][info ][gc ] GC(3) Pause Cleanup 35M->35M(4096M) 0.104ms 93 | [1.729s][info ][gc,cpu ] GC(3) User=0.00s Sys=0.00s Real=0.00s 94 | [1.729s][info ][gc,marking ] GC(3) Concurrent Cleanup for Next Mark 95 | [1.734s][info ][gc,marking ] GC(3) Concurrent Cleanup for Next Mark 5.357ms 96 | [1.734s][info ][gc ] GC(3) Concurrent Mark Cycle 14.748ms 97 | [2.781s][info ][gc,start ] GC(4) Pause Young (Normal) (G1 Evacuation Pause) 98 | [2.781s][info ][gc,task ] GC(4) Using 9 workers of 9 for evacuation 99 | [2.787s][info ][gc,phases ] GC(4) Pre Evacuate Collection Set: 0.1ms 100 | [2.787s][info ][gc,phases ] GC(4) Merge Heap Roots: 0.1ms 101 | [2.787s][info ][gc,phases ] GC(4) Evacuate Collection Set: 5.4ms 102 | [2.787s][info ][gc,phases ] GC(4) Post Evacuate Collection Set: 0.5ms 103 | [2.787s][info ][gc,phases ] GC(4) Other: 0.1ms 104 | [2.787s][info ][gc,heap ] GC(4) Eden regions: 97->0(92) 105 | [2.787s][info ][gc,heap ] GC(4) Survivor regions: 5->10(13) 106 | [2.787s][info ][gc,heap ] GC(4) Old regions: 10->10 107 | [2.787s][info ][gc,heap ] GC(4) Archive regions: 2->2 108 | [2.787s][info ][gc,heap ] GC(4) Humongous regions: 2->2 109 | [2.787s][info ][gc,metaspace] GC(4) Metaspace: 51812K(52480K)->51812K(52480K) NonClass: 44352K(44736K)->44352K(44736K) Class: 7460K(7744K)->7460K(7744K) 110 | [2.787s][info ][gc ] GC(4) Pause Young (Normal) (G1 Evacuation Pause) 227M->44M(4096M) 6.388ms 111 | [2.787s][info ][gc,cpu ] GC(4) User=0.04s Sys=0.01s Real=0.01s 112 | [3.049s][info ][gc,start ] GC(5) Pause Young (Normal) (G1 Evacuation Pause) 113 | [3.049s][info ][gc,task ] GC(5) Using 9 workers of 9 for evacuation 114 | [3.056s][info ][gc,phases ] GC(5) Pre Evacuate Collection Set: 0.2ms 115 | [3.056s][info ][gc,phases ] GC(5) Merge Heap Roots: 0.0ms 116 | [3.056s][info ][gc,phases ] GC(5) Evacuate Collection Set: 6.6ms 117 | [3.056s][info ][gc,phases ] GC(5) Post Evacuate Collection Set: 0.3ms 118 | [3.056s][info ][gc,phases ] GC(5) Other: 0.1ms 119 | [3.056s][info ][gc,heap ] GC(5) Eden regions: 92->0(117) 120 | [3.056s][info ][gc,heap ] GC(5) Survivor regions: 10->10(13) 121 | [3.056s][info ][gc,heap ] GC(5) Old regions: 10->13 122 | [3.056s][info ][gc,heap ] GC(5) Archive regions: 2->2 123 | [3.056s][info ][gc,heap ] GC(5) Humongous regions: 2->2 124 | [3.056s][info ][gc,metaspace] GC(5) Metaspace: 52880K(53568K)->52880K(53568K) NonClass: 45305K(45696K)->45305K(45696K) Class: 7574K(7872K)->7574K(7872K) 125 | [3.056s][info ][gc ] GC(5) Pause Young (Normal) (G1 Evacuation Pause) 228M->50M(4096M) 7.401ms 126 | [3.056s][info ][gc,cpu ] GC(5) User=0.06s Sys=0.01s Real=0.01s 127 | [3.431s][info ][gc,start ] GC(6) Pause Young (Concurrent Start) (Metadata GC Threshold) 128 | [3.431s][info ][gc,task ] GC(6) Using 9 workers of 9 for evacuation 129 | [3.436s][info ][gc,phases ] GC(6) Pre Evacuate Collection Set: 0.2ms 130 | [3.436s][info ][gc,phases ] GC(6) Merge Heap Roots: 0.1ms 131 | [3.436s][info ][gc,phases ] GC(6) Evacuate Collection Set: 4.3ms 132 | [3.437s][info ][gc,phases ] GC(6) Post Evacuate Collection Set: 0.4ms 133 | [3.437s][info ][gc,phases ] GC(6) Other: 0.1ms 134 | [3.437s][info ][gc,heap ] GC(6) Eden regions: 55->0(151) 135 | [3.437s][info ][gc,heap ] GC(6) Survivor regions: 10->9(16) 136 | [3.437s][info ][gc,heap ] GC(6) Old regions: 13->18 137 | [3.437s][info ][gc,heap ] GC(6) Archive regions: 2->2 138 | [3.437s][info ][gc,heap ] GC(6) Humongous regions: 6->6 139 | [3.437s][info ][gc,metaspace] GC(6) Metaspace: 59497K(60160K)->59497K(60160K) NonClass: 50897K(51264K)->50897K(51264K) Class: 8600K(8896K)->8600K(8896K) 140 | [3.437s][info ][gc ] GC(6) Pause Young (Concurrent Start) (Metadata GC Threshold) 168M->67M(4096M) 5.131ms 141 | [3.437s][info ][gc,cpu ] GC(6) User=0.04s Sys=0.00s Real=0.01s 142 | [3.437s][info ][gc ] GC(7) Concurrent Mark Cycle 143 | [3.437s][info ][gc,marking ] GC(7) Concurrent Clear Claimed Marks 144 | [3.437s][info ][gc,marking ] GC(7) Concurrent Clear Claimed Marks 0.032ms 145 | [3.437s][info ][gc,marking ] GC(7) Concurrent Scan Root Regions 146 | [3.441s][info ][gc,marking ] GC(7) Concurrent Scan Root Regions 4.828ms 147 | [3.442s][info ][gc,marking ] GC(7) Concurrent Mark 148 | [3.442s][info ][gc,marking ] GC(7) Concurrent Mark From Roots 149 | [3.442s][info ][gc,task ] GC(7) Using 2 workers of 2 for marking 150 | [3.450s][info ][gc,marking ] GC(7) Concurrent Mark From Roots 8.630ms 151 | [3.450s][info ][gc,marking ] GC(7) Concurrent Preclean 152 | [3.450s][info ][gc,marking ] GC(7) Concurrent Preclean 0.103ms 153 | [3.450s][info ][gc,start ] GC(7) Pause Remark 154 | [3.452s][info ][gc ] GC(7) Pause Remark 70M->70M(4096M) 1.729ms 155 | [3.452s][info ][gc,cpu ] GC(7) User=0.01s Sys=0.00s Real=0.00s 156 | [3.452s][info ][gc,marking ] GC(7) Concurrent Mark 10.696ms 157 | [3.452s][info ][gc,marking ] GC(7) Concurrent Rebuild Remembered Sets 158 | [3.452s][info ][gc,marking ] GC(7) Concurrent Rebuild Remembered Sets 0.006ms 159 | [3.452s][info ][gc,start ] GC(7) Pause Cleanup 160 | [3.452s][info ][gc ] GC(7) Pause Cleanup 70M->70M(4096M) 0.015ms 161 | [3.452s][info ][gc,cpu ] GC(7) User=0.00s Sys=0.00s Real=0.00s 162 | [3.452s][info ][gc,marking ] GC(7) Concurrent Cleanup for Next Mark 163 | [3.454s][info ][gc,marking ] GC(7) Concurrent Cleanup for Next Mark 1.472ms 164 | [3.454s][info ][gc ] GC(7) Concurrent Mark Cycle 17.274ms 165 | [4.528s][info ][gc,start ] GC(8) Pause Young (Normal) (G1 Evacuation Pause) 166 | [4.528s][info ][gc,task ] GC(8) Using 9 workers of 9 for evacuation 167 | [4.536s][info ][gc,phases ] GC(8) Pre Evacuate Collection Set: 0.1ms 168 | [4.536s][info ][gc,phases ] GC(8) Merge Heap Roots: 0.1ms 169 | [4.536s][info ][gc,phases ] GC(8) Evacuate Collection Set: 7.0ms 170 | [4.536s][info ][gc,phases ] GC(8) Post Evacuate Collection Set: 0.5ms 171 | [4.536s][info ][gc,phases ] GC(8) Other: 0.1ms 172 | [4.536s][info ][gc,heap ] GC(8) Eden regions: 167->0(257) 173 | [4.536s][info ][gc,heap ] GC(8) Survivor regions: 9->19(22) 174 | [4.536s][info ][gc,heap ] GC(8) Old regions: 18->18 175 | [4.536s][info ][gc,heap ] GC(8) Archive regions: 2->2 176 | [4.536s][info ][gc,heap ] GC(8) Humongous regions: 8->8 177 | [4.536s][info ][gc,metaspace] GC(8) Metaspace: 75833K(76608K)->75833K(76608K) NonClass: 65304K(65728K)->65304K(65728K) Class: 10529K(10880K)->10529K(10880K) 178 | [4.536s][info ][gc ] GC(8) Pause Young (Normal) (G1 Evacuation Pause) 405M->90M(4096M) 7.869ms 179 | [4.536s][info ][gc,cpu ] GC(8) User=0.05s Sys=0.01s Real=0.01s 180 | [5.689s][info ][gc,start ] GC(9) Pause Young (Concurrent Start) (Metadata GC Threshold) 181 | [5.690s][info ][gc,task ] GC(9) Using 9 workers of 9 for evacuation 182 | [5.708s][info ][gc,phases ] GC(9) Pre Evacuate Collection Set: 0.2ms 183 | [5.708s][info ][gc,phases ] GC(9) Merge Heap Roots: 0.1ms 184 | [5.708s][info ][gc,phases ] GC(9) Evacuate Collection Set: 16.9ms 185 | [5.708s][info ][gc,phases ] GC(9) Post Evacuate Collection Set: 1.0ms 186 | [5.708s][info ][gc,phases ] GC(9) Other: 0.2ms 187 | [5.708s][info ][gc,heap ] GC(9) Eden regions: 194->0(158) 188 | [5.708s][info ][gc,heap ] GC(9) Survivor regions: 19->34(34) 189 | [5.708s][info ][gc,heap ] GC(9) Old regions: 18->23 190 | [5.708s][info ][gc,heap ] GC(9) Archive regions: 2->2 191 | [5.708s][info ][gc,heap ] GC(9) Humongous regions: 18->12 192 | [5.708s][info ][gc,metaspace] GC(9) Metaspace: 99808K(100608K)->99808K(100608K) NonClass: 86007K(86400K)->86007K(86400K) Class: 13800K(14208K)->13800K(14208K) 193 | [5.708s][info ][gc ] GC(9) Pause Young (Concurrent Start) (Metadata GC Threshold) 497M->139M(4096M) 18.345ms 194 | [5.708s][info ][gc,cpu ] GC(9) User=0.12s Sys=0.02s Real=0.02s 195 | [5.708s][info ][gc ] GC(10) Concurrent Mark Cycle 196 | [5.708s][info ][gc,marking ] GC(10) Concurrent Clear Claimed Marks 197 | [5.708s][info ][gc,marking ] GC(10) Concurrent Clear Claimed Marks 0.052ms 198 | [5.708s][info ][gc,marking ] GC(10) Concurrent Scan Root Regions 199 | [5.719s][info ][gc,marking ] GC(10) Concurrent Scan Root Regions 11.260ms 200 | [5.719s][info ][gc,marking ] GC(10) Concurrent Mark 201 | [5.719s][info ][gc,marking ] GC(10) Concurrent Mark From Roots 202 | [5.719s][info ][gc,task ] GC(10) Using 2 workers of 2 for marking 203 | [5.732s][info ][gc,marking ] GC(10) Concurrent Mark From Roots 12.926ms 204 | [5.732s][info ][gc,marking ] GC(10) Concurrent Preclean 205 | [5.733s][info ][gc,marking ] GC(10) Concurrent Preclean 0.352ms 206 | [5.733s][info ][gc,start ] GC(10) Pause Remark 207 | [5.741s][info ][gc ] GC(10) Pause Remark 145M->145M(4096M) 8.220ms 208 | [5.741s][info ][gc,cpu ] GC(10) User=0.06s Sys=0.01s Real=0.01s 209 | [5.741s][info ][gc,marking ] GC(10) Concurrent Mark 21.839ms 210 | [5.741s][info ][gc,marking ] GC(10) Concurrent Rebuild Remembered Sets 211 | [5.755s][info ][gc,marking ] GC(10) Concurrent Rebuild Remembered Sets 13.846ms 212 | [5.760s][info ][gc,start ] GC(10) Pause Cleanup 213 | [5.760s][info ][gc ] GC(10) Pause Cleanup 147M->147M(4096M) 0.125ms 214 | [5.760s][info ][gc,cpu ] GC(10) User=0.00s Sys=0.00s Real=0.00s 215 | [5.760s][info ][gc,marking ] GC(10) Concurrent Cleanup for Next Mark 216 | [5.761s][info ][gc,marking ] GC(10) Concurrent Cleanup for Next Mark 1.372ms 217 | [5.761s][info ][gc ] GC(10) Concurrent Mark Cycle 53.537ms 218 | [6.556s][info ][gc,start ] GC(11) Pause Young (Concurrent Start) (G1 Evacuation Pause) 219 | [6.556s][info ][gc,task ] GC(11) Using 9 workers of 9 for evacuation 220 | [6.573s][info ][gc,phases ] GC(11) Pre Evacuate Collection Set: 0.2ms 221 | [6.573s][info ][gc,phases ] GC(11) Merge Heap Roots: 0.1ms 222 | [6.573s][info ][gc,phases ] GC(11) Evacuate Collection Set: 16.1ms 223 | [6.573s][info ][gc,phases ] GC(11) Post Evacuate Collection Set: 0.5ms 224 | [6.573s][info ][gc,phases ] GC(11) Other: 0.2ms 225 | [6.573s][info ][gc,heap ] GC(11) Eden regions: 97->0(443) 226 | [6.573s][info ][gc,heap ] GC(11) Survivor regions: 34->8(17) 227 | [6.573s][info ][gc,heap ] GC(11) Old regions: 23->58 228 | [6.573s][info ][gc,heap ] GC(11) Archive regions: 2->2 229 | [6.573s][info ][gc,heap ] GC(11) Humongous regions: 12->12 230 | [6.573s][info ][gc,metaspace] GC(11) Metaspace: 117171K(117952K)->117171K(117952K) NonClass: 101352K(101760K)->101352K(101760K) Class: 15819K(16192K)->15819K(16192K) 231 | [6.573s][info ][gc ] GC(11) Pause Young (Concurrent Start) (G1 Evacuation Pause) 333M->156M(4096M) 17.251ms 232 | [6.573s][info ][gc,cpu ] GC(11) User=0.13s Sys=0.02s Real=0.02s 233 | [6.573s][info ][gc ] GC(12) Concurrent Mark Cycle 234 | [6.573s][info ][gc,marking ] GC(12) Concurrent Clear Claimed Marks 235 | [6.573s][info ][gc,marking ] GC(12) Concurrent Clear Claimed Marks 0.052ms 236 | [6.573s][info ][gc,marking ] GC(12) Concurrent Scan Root Regions 237 | [6.586s][info ][gc,marking ] GC(12) Concurrent Scan Root Regions 13.021ms 238 | [6.586s][info ][gc,marking ] GC(12) Concurrent Mark 239 | [6.586s][info ][gc,marking ] GC(12) Concurrent Mark From Roots 240 | [6.586s][info ][gc,task ] GC(12) Using 2 workers of 2 for marking 241 | [6.604s][info ][gc,marking ] GC(12) Concurrent Mark From Roots 17.082ms 242 | [6.604s][info ][gc,marking ] GC(12) Concurrent Preclean 243 | [6.604s][info ][gc,marking ] GC(12) Concurrent Preclean 0.239ms 244 | [6.604s][info ][gc,start ] GC(12) Pause Remark 245 | [6.607s][info ][gc ] GC(12) Pause Remark 161M->161M(4096M) 2.602ms 246 | [6.607s][info ][gc,cpu ] GC(12) User=0.02s Sys=0.00s Real=0.00s 247 | [6.607s][info ][gc,marking ] GC(12) Concurrent Mark 20.204ms 248 | [6.607s][info ][gc,marking ] GC(12) Concurrent Rebuild Remembered Sets 249 | [6.633s][info ][gc,marking ] GC(12) Concurrent Rebuild Remembered Sets 26.669ms 250 | [6.633s][info ][gc,start ] GC(12) Pause Cleanup 251 | [6.634s][info ][gc ] GC(12) Pause Cleanup 166M->166M(4096M) 0.184ms 252 | [6.634s][info ][gc,cpu ] GC(12) User=0.00s Sys=0.00s Real=0.00s 253 | [6.634s][info ][gc,marking ] GC(12) Concurrent Cleanup for Next Mark 254 | [6.635s][info ][gc,marking ] GC(12) Concurrent Cleanup for Next Mark 1.454ms 255 | [6.635s][info ][gc ] GC(12) Concurrent Mark Cycle 61.886ms 256 | [16.678s][info ][gc,start ] GC(13) Pause Young (Normal) (G1 Evacuation Pause) 257 | [16.678s][info ][gc,task ] GC(13) Using 9 workers of 9 for evacuation 258 | [16.696s][info ][gc,phases ] GC(13) Pre Evacuate Collection Set: 0.2ms 259 | [16.696s][info ][gc,phases ] GC(13) Merge Heap Roots: 0.2ms 260 | [16.696s][info ][gc,phases ] GC(13) Evacuate Collection Set: 15.7ms 261 | [16.696s][info ][gc,phases ] GC(13) Post Evacuate Collection Set: 1.1ms 262 | [16.696s][info ][gc,phases ] GC(13) Other: 0.2ms 263 | [16.696s][info ][gc,heap ] GC(13) Eden regions: 443->0(277) 264 | [16.696s][info ][gc,heap ] GC(13) Survivor regions: 8->32(57) 265 | [16.696s][info ][gc,heap ] GC(13) Old regions: 58->58 266 | [16.696s][info ][gc,heap ] GC(13) Archive regions: 2->2 267 | [16.696s][info ][gc,heap ] GC(13) Humongous regions: 15->13 268 | [16.696s][info ][gc,metaspace] GC(13) Metaspace: 174566K(175744K)->174566K(175744K) NonClass: 151299K(151936K)->151299K(151936K) Class: 23266K(23808K)->23266K(23808K) 269 | [16.696s][info ][gc ] GC(13) Pause Young (Normal) (G1 Evacuation Pause) 1048M->206M(4096M) 17.503ms 270 | [16.696s][info ][gc,cpu ] GC(13) User=0.13s Sys=0.01s Real=0.02s 271 | [19.298s][info ][gc,start ] GC(14) Pause Young (Normal) (G1 Evacuation Pause) 272 | [19.298s][info ][gc,task ] GC(14) Using 9 workers of 9 for evacuation 273 | [19.321s][info ][gc,phases ] GC(14) Pre Evacuate Collection Set: 0.2ms 274 | [19.321s][info ][gc,phases ] GC(14) Merge Heap Roots: 0.1ms 275 | [19.321s][info ][gc,phases ] GC(14) Evacuate Collection Set: 21.3ms 276 | [19.321s][info ][gc,phases ] GC(14) Post Evacuate Collection Set: 0.7ms 277 | [19.321s][info ][gc,phases ] GC(14) Other: 0.1ms 278 | [19.321s][info ][gc,heap ] GC(14) Eden regions: 277->0(725) 279 | [19.321s][info ][gc,heap ] GC(14) Survivor regions: 32->22(39) 280 | [19.321s][info ][gc,heap ] GC(14) Old regions: 58->87 281 | [19.321s][info ][gc,heap ] GC(14) Archive regions: 2->2 282 | [19.321s][info ][gc,heap ] GC(14) Humongous regions: 13->13 283 | [19.321s][info ][gc,metaspace] GC(14) Metaspace: 181047K(182592K)->181047K(182592K) NonClass: 156867K(157696K)->156867K(157696K) Class: 24180K(24896K)->24180K(24896K) 284 | [19.321s][info ][gc ] GC(14) Pause Young (Normal) (G1 Evacuation Pause) 760M->245M(4096M) 22.527ms 285 | [19.321s][info ][gc,cpu ] GC(14) User=0.17s Sys=0.02s Real=0.02s 286 | [24.536s][info ][gc,start ] GC(15) Pause Young (Concurrent Start) (Metadata GC Threshold) 287 | [24.536s][info ][gc,task ] GC(15) Using 9 workers of 9 for evacuation 288 | [24.591s][info ][gc,phases ] GC(15) Pre Evacuate Collection Set: 0.4ms 289 | [24.591s][info ][gc,phases ] GC(15) Merge Heap Roots: 0.2ms 290 | [24.591s][info ][gc,phases ] GC(15) Evacuate Collection Set: 52.3ms 291 | [24.591s][info ][gc,phases ] GC(15) Post Evacuate Collection Set: 1.4ms 292 | [24.591s][info ][gc,phases ] GC(15) Other: 0.2ms 293 | [24.591s][info ][gc,heap ] GC(15) Eden regions: 624->0(448) 294 | [24.591s][info ][gc,heap ] GC(15) Survivor regions: 22->84(94) 295 | [24.591s][info ][gc,heap ] GC(15) Old regions: 87->87 296 | [24.591s][info ][gc,heap ] GC(15) Archive regions: 2->2 297 | [24.591s][info ][gc,heap ] GC(15) Humongous regions: 13->10 298 | [24.591s][info ][gc,metaspace] GC(15) Metaspace: 195673K(197696K)->195673K(197696K) NonClass: 169656K(170752K)->169656K(170752K) Class: 26016K(26944K)->26016K(26944K) 299 | [24.591s][info ][gc ] GC(15) Pause Young (Concurrent Start) (Metadata GC Threshold) 1493M->362M(4096M) 54.732ms 300 | [24.591s][info ][gc,cpu ] GC(15) User=0.32s Sys=0.03s Real=0.05s 301 | [24.591s][info ][gc ] GC(16) Concurrent Mark Cycle 302 | [24.591s][info ][gc,marking ] GC(16) Concurrent Clear Claimed Marks 303 | [24.591s][info ][gc,marking ] GC(16) Concurrent Clear Claimed Marks 0.099ms 304 | [24.591s][info ][gc,marking ] GC(16) Concurrent Scan Root Regions 305 | [24.635s][info ][gc,marking ] GC(16) Concurrent Scan Root Regions 43.834ms 306 | [24.635s][info ][gc,marking ] GC(16) Concurrent Mark 307 | [24.635s][info ][gc,marking ] GC(16) Concurrent Mark From Roots 308 | [24.635s][info ][gc,task ] GC(16) Using 2 workers of 2 for marking 309 | [24.689s][info ][gc,marking ] GC(16) Concurrent Mark From Roots 53.828ms 310 | [24.689s][info ][gc,marking ] GC(16) Concurrent Preclean 311 | [24.690s][info ][gc,marking ] GC(16) Concurrent Preclean 1.759ms 312 | [24.691s][info ][gc,start ] GC(16) Pause Remark 313 | [24.708s][info ][gc ] GC(16) Pause Remark 375M->375M(4096M) 16.918ms 314 | [24.708s][info ][gc,cpu ] GC(16) User=0.10s Sys=0.01s Real=0.02s 315 | [24.708s][info ][gc,marking ] GC(16) Concurrent Mark 73.183ms 316 | [24.708s][info ][gc,marking ] GC(16) Concurrent Rebuild Remembered Sets 317 | [24.757s][info ][gc,marking ] GC(16) Concurrent Rebuild Remembered Sets 49.070ms 318 | [24.757s][info ][gc,start ] GC(16) Pause Cleanup 319 | [24.757s][info ][gc ] GC(16) Pause Cleanup 385M->385M(4096M) 0.161ms 320 | [24.757s][info ][gc,cpu ] GC(16) User=0.00s Sys=0.00s Real=0.00s 321 | [24.757s][info ][gc,marking ] GC(16) Concurrent Cleanup for Next Mark 322 | [24.759s][info ][gc,marking ] GC(16) Concurrent Cleanup for Next Mark 1.670ms 323 | [24.759s][info ][gc ] GC(16) Concurrent Mark Cycle 168.347ms 324 | [30.624s][info ][gc,start ] GC(17) Pause Young (Prepare Mixed) (G1 Evacuation Pause) 325 | [30.624s][info ][gc,task ] GC(17) Using 9 workers of 9 for evacuation 326 | [30.685s][info ][gc,phases ] GC(17) Pre Evacuate Collection Set: 0.2ms 327 | [30.685s][info ][gc,phases ] GC(17) Merge Heap Roots: 0.2ms 328 | [30.685s][info ][gc,phases ] GC(17) Evacuate Collection Set: 56.8ms 329 | [30.685s][info ][gc,phases ] GC(17) Post Evacuate Collection Set: 3.2ms 330 | [30.685s][info ][gc,phases ] GC(17) Other: 0.3ms 331 | [30.685s][info ][gc,heap ] GC(17) Eden regions: 448->0(79) 332 | [30.685s][info ][gc,heap ] GC(17) Survivor regions: 84->23(67) 333 | [30.685s][info ][gc,heap ] GC(17) Old regions: 87->166 334 | [30.685s][info ][gc,heap ] GC(17) Archive regions: 2->2 335 | [30.685s][info ][gc,heap ] GC(17) Humongous regions: 18->15 336 | [30.685s][info ][gc,metaspace] GC(17) Metaspace: 212227K(214400K)->212227K(214400K) NonClass: 184295K(185472K)->184295K(185472K) Class: 27931K(28928K)->27931K(28928K) 337 | [30.685s][info ][gc ] GC(17) Pause Young (Prepare Mixed) (G1 Evacuation Pause) 1274M->407M(4096M) 60.818ms 338 | [30.685s][info ][gc,cpu ] GC(17) User=0.33s Sys=0.03s Real=0.06s 339 | [33.532s][info ][gc,start ] GC(18) Pause Young (Mixed) (G1 Evacuation Pause) 340 | [33.532s][info ][gc,task ] GC(18) Using 9 workers of 9 for evacuation 341 | [33.559s][info ][gc,phases ] GC(18) Pre Evacuate Collection Set: 0.2ms 342 | [33.559s][info ][gc,phases ] GC(18) Merge Heap Roots: 0.3ms 343 | [33.559s][info ][gc,phases ] GC(18) Evacuate Collection Set: 24.4ms 344 | [33.559s][info ][gc,phases ] GC(18) Post Evacuate Collection Set: 1.9ms 345 | [33.559s][info ][gc,phases ] GC(18) Other: 0.1ms 346 | [33.559s][info ][gc,heap ] GC(18) Eden regions: 79->0(950) 347 | [33.559s][info ][gc,heap ] GC(18) Survivor regions: 23->6(13) 348 | [33.559s][info ][gc,heap ] GC(18) Old regions: 166->179 349 | [33.559s][info ][gc,heap ] GC(18) Archive regions: 2->2 350 | [33.559s][info ][gc,heap ] GC(18) Humongous regions: 17->13 351 | [33.559s][info ][gc,metaspace] GC(18) Metaspace: 213147K(215424K)->213147K(215424K) NonClass: 185140K(186368K)->185140K(186368K) Class: 28007K(29056K)->28007K(29056K) 352 | [33.559s][info ][gc ] GC(18) Pause Young (Mixed) (G1 Evacuation Pause) 569M->396M(4096M) 26.977ms 353 | [33.559s][info ][gc,cpu ] GC(18) User=0.14s Sys=0.01s Real=0.03s 354 | --------------------------------------------------------------------------------