From 70aaa212a59907dece7ca54d1736fa7d8ce62ac5 Mon Sep 17 00:00:00 2001 From: Dmitriy Dolovov Date: Tue, 24 May 2022 09:56:33 +0300 Subject: [PATCH] [Native][tests] Track and report memory usage while tests are running --- native/native.tests/build.gradle.kts | 3 + .../support/ConfigurationProperties.kt | 3 +- .../blackboxtest/support/NativeTestSupport.kt | 43 +++++++++--- .../support/util/MemoryTracker.kt | 69 +++++++++++++++++++ 4 files changed, 109 insertions(+), 9 deletions(-) create mode 100644 native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/util/MemoryTracker.kt diff --git a/native/native.tests/build.gradle.kts b/native/native.tests/build.gradle.kts index ddf95d21937..f3df4a998d2 100644 --- a/native/native.tests/build.gradle.kts +++ b/native/native.tests/build.gradle.kts @@ -132,6 +132,9 @@ fun nativeTest(taskName: String, vararg tags: String) = projectTest( TestProperty.CACHE_MODE.setUpFromGradleProperty(this) TestProperty.EXECUTION_TIMEOUT.setUpFromGradleProperty(this) + // Pass the current Gradle task name so test can use it in logging. + environment("GRADLE_TASK_NAME", path) + useJUnitPlatform { includeTags(*tags) } diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/ConfigurationProperties.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/ConfigurationProperties.kt index 2406f8ee291..96bd4d53fd0 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/ConfigurationProperties.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/ConfigurationProperties.kt @@ -87,7 +87,8 @@ private fun fullPropertyName(shortName: String) = "kotlin.internal.native.test.$ /*************** Environment variables ***************/ internal enum class EnvironmentVariable { - PROJECT_BUILD_DIR; + PROJECT_BUILD_DIR, + GRADLE_TASK_NAME; fun readValue(): String = System.getenv(name) ?: fail { "Unspecified $name environment variable" } } diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/NativeTestSupport.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/NativeTestSupport.kt index ce56214086c..3ca1892e868 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/NativeTestSupport.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/NativeTestSupport.kt @@ -43,9 +43,6 @@ class NativeBlackBoxTestSupport : BeforeEachCallback { override fun beforeEach(extensionContext: ExtensionContext): Unit = with(extensionContext) { val settings = createTestRunSettings() - // Set the essential compiler property. - System.setProperty("kotlin.native.home", settings.get().dir.path) - // Inject the required properties to test instance. with(settings.get().enclosingTestInstance) { testRunSettings = settings @@ -58,9 +55,6 @@ class NativeSimpleTestSupport : BeforeEachCallback { override fun beforeEach(extensionContext: ExtensionContext): Unit = with(extensionContext) { val settings = createSimpleTestRunSettings() - // Set the essential compiler property. - System.setProperty("kotlin.native.home", settings.get().dir.path) - // Inject the required properties to test instance. with(settings.get().enclosingTestInstance) { testRunSettings = settings @@ -70,14 +64,20 @@ class NativeSimpleTestSupport : BeforeEachCallback { } private object NativeTestSupport { - private val NAMESPACE = ExtensionContext.Namespace.create(NativeBlackBoxTestSupport::class.java.simpleName) + private val NAMESPACE = ExtensionContext.Namespace.create(NativeTestSupport::class.java.simpleName) /*************** Test process settings ***************/ fun ExtensionContext.getOrCreateTestProcessSettings(): TestProcessSettings = root.getStore(NAMESPACE).getOrComputeIfAbsent(TestProcessSettings::class.java.name) { + val nativeHome = computeNativeHome() + + // Apply the necessary process-wide settings: + System.setProperty("kotlin.native.home", nativeHome.dir.path) // Set the essential compiler property. + setUpMemoryTracking() // Set up memory tracking and reporting. + TestProcessSettings( - computeNativeHome(), + nativeHome, computeNativeClassLoader(), computeBaseDirs() ) @@ -103,6 +103,33 @@ private object NativeTestSupport { return BaseDirs(testBuildDir) } + private fun ExtensionContext.setUpMemoryTracking() { + TestLogger.initialize() // Initialize special logging (directly to Gradle's console). + + val gradleTaskName = EnvironmentVariable.GRADLE_TASK_NAME.readValue() + fun Long.toMBs() = (this / 1024 / 1024) + + // Set up memory tracking and reporting: + MemoryTracker.startTracking(intervalMillis = 1000) { memoryMark -> + TestLogger.log( + buildString { + append(memoryMark.timestamp).append(' ').append(gradleTaskName) + append(" Memory usage (MB): ") + append("used=").append(memoryMark.usedMemory.toMBs()) + append(", free=").append(memoryMark.freeMemory.toMBs()) + append(", total=").append(memoryMark.totalMemory.toMBs()) + append(", max=").append(memoryMark.maxMemory.toMBs()) + } + ) + } + + // Stop tracking memory when all tests are finished: + root.getStore(NAMESPACE).put( + testClassKeyFor(), + ExtensionContext.Store.CloseableResource { MemoryTracker.stopTracking() } + ) + } + /*************** Test class settings (common part) ***************/ private fun ExtensionContext.addCommonTestClassSettingsTo( diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/util/MemoryTracker.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/util/MemoryTracker.kt new file mode 100644 index 00000000000..f02592683bf --- /dev/null +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/util/MemoryTracker.kt @@ -0,0 +1,69 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.konan.blackboxtest.support.util + +import java.time.LocalDateTime +import java.util.concurrent.atomic.AtomicReference + +/** + * Internal tool for tracking used memory. + */ +internal object MemoryTracker { + data class MemoryMark( + val timestamp: LocalDateTime, + val usedMemory: Long, + val freeMemory: Long, + val totalMemory: Long, + val maxMemory: Long + ) + + private class MemoryTrackerRunner( + private val intervalMillis: Long, + private val logger: (MemoryMark) -> Unit + ) : Thread("NativeTestMemoryTrackerRunner") { + private val runtime = Runtime.getRuntime() + + override fun run() { + try { + while (!interrupted()) { + val timestamp = LocalDateTime.now() + + val free = runtime.freeMemory() + val total = runtime.totalMemory() + val used = total - free + val max = runtime.maxMemory() + + logger( + MemoryMark( + timestamp = timestamp, + usedMemory = used, + freeMemory = free, + totalMemory = total, + maxMemory = max + ) + ) + + sleep(intervalMillis) + } + } catch (_: InterruptedException) { + // do nothing, just leave the loop + } + } + } + + private val activeRunner = AtomicReference() + + fun startTracking(intervalMillis: Long, logger: (MemoryMark) -> Unit) { + val runner = MemoryTrackerRunner(intervalMillis, logger) + check(activeRunner.compareAndSet(null, runner)) { "There is another active runner" } + runner.start() + } + + fun stopTracking() { + val runner = activeRunner.getAndSet(null) ?: error("No active runner") + runner.interrupt() + } +}