From 7e553363009f42e6e521152ffee98c6d2313cd8c Mon Sep 17 00:00:00 2001 From: Vladimir Dolzhenko Date: Thu, 30 Apr 2020 22:26:27 +0200 Subject: [PATCH] Add yourKit profiler to perfTests --- idea/performanceTests/build.gradle.kts | 22 +++++- .../idea/perf/profilers/ProfilerHandler.kt | 39 ++++++---- .../profilers/async/AsyncProfilerHandler.kt | 31 ++++++-- .../perf/profilers/yk/YKProfilerHandler.kt | 71 +++++++++++++++++++ 4 files changed, 140 insertions(+), 23 deletions(-) create mode 100644 idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/profilers/yk/YKProfilerHandler.kt diff --git a/idea/performanceTests/build.gradle.kts b/idea/performanceTests/build.gradle.kts index 82991ee245f..f9037f5d1fd 100644 --- a/idea/performanceTests/build.gradle.kts +++ b/idea/performanceTests/build.gradle.kts @@ -67,7 +67,14 @@ javadocJar() apply(from = "$rootDir/gradle/kotlinPluginPublication.gradle.kts") projectTest(taskName = "performanceTest") { - classpath += files("${System.getenv("ASYNC_PROFILER_HOME")}/build/async-profiler.jar") + val currentOs = org.gradle.internal.os.OperatingSystem.current() + + if (!currentOs.isWindows) { + System.getenv("ASYNC_PROFILER_HOME")?.let { asyncProfilerHome -> + classpath += files("$asyncProfilerHome/build/async-profiler.jar") + } + } + workingDir = rootDir jvmArgs?.removeAll { it.startsWith("-Xmx") } @@ -81,6 +88,19 @@ projectTest(taskName = "performanceTest") { "-XX:+UseConcMarkSweepGC" ) + System.getenv("YOURKIT_PROFILER_HOME")?.let {yourKitHome -> + when { + currentOs.isLinux -> { + jvmArgs("-agentpath:$yourKitHome/bin/linux-x86-64/libyjpagent.so") + classpath += files("$yourKitHome/lib/yjp-controller-api-redist.jar") + } + currentOs.isMacOsX -> { + jvmArgs("-agentpath:$yourKitHome/Contents/Resources/bin/mac/libyjpagent.dylib") + classpath += files("$yourKitHome/Contents/Resources/lib/yjp-controller-api-redist.jar") + } + } + } + doFirst { systemProperty("idea.home.path", intellijRootDir().canonicalPath) project.findProperty("cacheRedirectorEnabled")?.let { diff --git a/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/profilers/ProfilerHandler.kt b/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/profilers/ProfilerHandler.kt index b7ad56e16c4..160bfc1331a 100644 --- a/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/profilers/ProfilerHandler.kt +++ b/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/profilers/ProfilerHandler.kt @@ -5,8 +5,8 @@ package org.jetbrains.kotlin.idea.perf.profilers -import com.intellij.openapi.util.SystemInfo import org.jetbrains.kotlin.idea.perf.profilers.async.AsyncProfilerHandler +import org.jetbrains.kotlin.idea.perf.profilers.yk.YKProfilerHandler import org.jetbrains.kotlin.idea.testFramework.logMessage interface ProfilerHandler { @@ -19,21 +19,13 @@ interface ProfilerHandler { @Synchronized fun getInstance(): ProfilerHandler { - if (instance == null) { - if (!SystemInfo.isWindows) { - try { - instance = AsyncProfilerHandler() - } catch (e: Throwable) { - logMessage { "asyncProfiler not found" } - } - } + return instance ?: run { + val handler = + doOrLog("asyncProfiler not found") { AsyncProfilerHandler() } + ?: doOrLog("yourKit not found") { YKProfilerHandler() } ?: DummyProfilerHandler + instance = handler + return handler } - - if (instance == null) { - // fallback to dummy - instance = DummyProfilerHandler - } - return instance!! } } @@ -43,4 +35,21 @@ object DummyProfilerHandler : ProfilerHandler { override fun startProfiling(activityName: String, options: List) {} override fun stopProfiling(snapshotsPath: String, activityName: String, options: List) {} +} + +internal fun doOrLog(message: String, block: () -> T?): T? { + return try { + block() + } catch (e: Throwable) { + logMessage { message } + null + } +} + +internal fun doOrThrow(message: String, block: () -> T): T { + return try { + block() + } catch (e: Throwable) { + throw Exception(message) + } ?: throw Exception(message) } \ No newline at end of file diff --git a/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/profilers/async/AsyncProfilerHandler.kt b/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/profilers/async/AsyncProfilerHandler.kt index e0f07c665db..ea2fe0b63f5 100644 --- a/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/profilers/async/AsyncProfilerHandler.kt +++ b/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/profilers/async/AsyncProfilerHandler.kt @@ -9,12 +9,22 @@ import com.intellij.openapi.util.SystemInfo import com.intellij.openapi.util.io.FileUtil import com.intellij.util.containers.ContainerUtil import org.jetbrains.kotlin.idea.perf.profilers.ProfilerHandler +import org.jetbrains.kotlin.idea.perf.profilers.doOrThrow import org.jetbrains.kotlin.idea.testFramework.logMessage import java.io.File +import java.lang.reflect.Method import java.nio.file.Files import java.nio.file.StandardCopyOption import java.util.* +/** + * To use AsyncProfilerHandler: + * - it has to be running on GNU/Linux or MacOSX (as async-profiler does NOT work on Windows) + * - env variable ASYNC_PROFILER_HOME has to be specified and points to async-profiler installation + * - ${ASYNC_PROFILER_HOME}/build/async-profiler.jar has to be in a classpath (done by gradle task) + * + * AsyncProfiler could be downloaded from https://github.com/jvm-profiling-tools/async-profiler/releases/ + */ internal class AsyncProfilerHandler : ProfilerHandler { private val asyncProfiler: Any @@ -59,23 +69,30 @@ internal class AsyncProfilerHandler : ProfilerHandler { } companion object { - const val AGENT_FILE_NAME = "libasyncProfiler.so" - private val asyncLibClass: Class<*> = Class.forName("one.profiler.AsyncProfiler")!! - private val executeMethod = asyncLibClass.getMethod("execute", String::class.java) + private val asyncLibClass: Class<*> = + doOrThrow("async-profiler.jar is not in a classpath") { Class.forName("one.profiler.AsyncProfiler") } + private val executeMethod: Method = + doOrThrow("one.profiler.AsyncProfiler#execute(String) not found") { asyncLibClass.getMethod("execute", String::class.java) } + + private const val AGENT_FILE_NAME = "libasyncProfiler.so" private var extractedFile: File? = null private fun asyncLib(): File { - if (extractedFile == null) { - extractedFile = File("${System.getenv("ASYNC_PROFILER_HOME")}/build/$AGENT_FILE_NAME") + val osName = when { + SystemInfo.isLinux -> "linux" + SystemInfo.isMac -> "macos" + else -> error("AsyncProfiler does not support OS ${SystemInfo.OS_NAME}") } + + extractedFile = extractedFile ?: File("${System.getenv("ASYNC_PROFILER_HOME")}/build/$AGENT_FILE_NAME") if (extractedFile == null || !extractedFile!!.exists()) { val extracted = FileUtil.createTempFile("extracted_$AGENT_FILE_NAME", null, true) - val osName = if (SystemInfo.isLinux) "linux" else "macos" + val inputStream = asyncLibClass.getResourceAsStream("/binaries/$osName/$AGENT_FILE_NAME") Files.copy(inputStream, extracted.toPath(), StandardCopyOption.REPLACE_EXISTING) extractedFile = extracted } - return extractedFile!! + return extractedFile ?: error("Unable to lookup $AGENT_FILE_NAME") } } diff --git a/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/profilers/yk/YKProfilerHandler.kt b/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/profilers/yk/YKProfilerHandler.kt new file mode 100644 index 00000000000..15cfaf946c2 --- /dev/null +++ b/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/profilers/yk/YKProfilerHandler.kt @@ -0,0 +1,71 @@ +/* + * Copyright 2010-2020 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.idea.perf.profilers.yk + +import org.jetbrains.kotlin.idea.perf.profilers.ProfilerHandler +import org.jetbrains.kotlin.idea.perf.profilers.doOrThrow +import org.jetbrains.kotlin.idea.testFramework.logMessage +import java.lang.management.ManagementFactory +import java.lang.reflect.Method +import java.nio.file.Files +import java.nio.file.Paths + +/** + * To use YKProfilerHandler: + * - ${YOURKIT_PROFILER_HOME}/lib/yjp-controller-api-redist.jar has to be in a classpath + * - add agentpath vm paramter like `-agentpath:${YOURKIT_PROFILER_HOME}/Resources/bin/mac/libyjpagent.dylib` + */ +class YKProfilerHandler : ProfilerHandler { + + private var controller: Any + + init { + check(ManagementFactory.getRuntimeMXBean().inputArguments.any { it.contains("yjpagent") }) { + "vm parameter -agentpath:\$YOURKIT_PROFILER_HOME/bin/../libyjpagent is not specified" + } + + controller = doOrThrow("Unable to create com.yourkit.api.Controller instance") { + ykLibClass.getConstructor().newInstance() + } + } + + override fun startProfiling(activityName: String, options: List) { + startCPUSamplingMethod.invoke(controller, null) + } + + override fun stopProfiling(snapshotsPath: String, activityName: String, options: List) { + val dumpPath = captureSnapshotMethod.invoke(controller, SNAPSHOT_WITHOUT_HEAP) as String + stopCPUProfilingMethod.invoke(controller) + val path = Paths.get(dumpPath) + val target = path.parent.resolve(snapshotsPath) + logMessage { "dump is moved to $target" } + Files.move(path, target) + } + + companion object { + const val SNAPSHOT_WITHOUT_HEAP = 0L + + private val ykLibClass: Class<*> = doOrThrow("yjp-controller-api-redist.jar is not in a classpath") { + Class.forName("com.yourkit.api.Controller") + } + private val startCPUSamplingMethod: Method = doOrThrow("com.yourkit.api.Controller#startCPUSampling(String) not found") { + ykLibClass.getMethod( + "startCPUSampling", + String::class.java + ) + } + private val captureSnapshotMethod: Method = doOrThrow("com.yourkit.api.Controller#captureSnapshot(long) not found") { + ykLibClass.getMethod( + "captureSnapshot", + SNAPSHOT_WITHOUT_HEAP::class.java + ) + } + + private val stopCPUProfilingMethod: Method = doOrThrow("com.yourkit.api.Controller#stopCPUProfiling() not found") { + ykLibClass.getMethod("stopCPUProfiling") + } + } +} \ No newline at end of file