(PerformanceTest) improvements in profiler snapshots

support for typing per inspection tests
 gradle arguments fixed to allow running tracing from cli
 Profiler disabled for warmup phases.

 relates to #KT-38653
This commit is contained in:
Vladimir Ilmov
2020-05-30 01:33:15 +02:00
parent 0d2552b0b6
commit 9719391c82
13 changed files with 176 additions and 100 deletions
+8 -9
View File
@@ -79,21 +79,20 @@ fun Project.projectTest(
} }
} }
doFirst { if (project.findProperty("kotlin.test.instrumentation.disable")?.toString()?.toBoolean() != true) {
val agent = tasks.findByPath(":test-instrumenter:jar")!!.outputs.files.singleFile doFirst {
val agent = tasks.findByPath(":test-instrumenter:jar")!!.outputs.files.singleFile
val args = project.findProperty("kotlin.test.instrumentation.args")?.let { "=$it" }.orEmpty() val args = project.findProperty("kotlin.test.instrumentation.args")?.let { "=$it" }.orEmpty()
jvmArgs("-javaagent:$agent$args")
jvmArgs("-javaagent:$agent$args") }
dependsOn(":test-instrumenter:jar")
} }
dependsOn(":test-instrumenter:jar")
jvmArgs( jvmArgs(
"-ea", "-ea",
"-XX:+HeapDumpOnOutOfMemoryError", "-XX:+HeapDumpOnOutOfMemoryError",
"-XX:+UseCodeCacheFlushing", "-XX:+UseCodeCacheFlushing",
"-XX:ReservedCodeCacheSize=128m", "-XX:ReservedCodeCacheSize=256m",
"-Djna.nosys=true" "-Djna.nosys=true"
) )
+4
View File
@@ -14,6 +14,10 @@ Run all Kotlin IDE plugin performance tests with
`$ gradle idea-plugin-performance-tests` `$ gradle idea-plugin-performance-tests`
## Run with profiler
`YOURKIT_PROFILER_HOME=/Applications/YourKit-Java-Profiler-2019.8.app ./gradlew -Pkotlin.test.instrumentation.disable=true :idea:performanceTests:performanceTest --tests "<test-filter>"`
## Performance test ## Performance test
Output is provided to console in TeamCity format like Output is provided to console in TeamCity format like
+3 -3
View File
@@ -84,9 +84,10 @@ projectTest(taskName = "performanceTest") {
maxHeapSize = "3g" maxHeapSize = "3g"
jvmArgs("-Didea.debug.mode=true") jvmArgs("-Didea.debug.mode=true")
jvmArgs("-XX:SoftRefLRUPolicyMSPerMB=50") jvmArgs("-XX:SoftRefLRUPolicyMSPerMB=50")
jvmArgs( jvmArgs(
"-XX:ReservedCodeCacheSize=240m",
"-XX:+UseCompressedOops", "-XX:+UseCompressedOops",
"-Didea.ProcessCanceledException=disabled",
"-XX:+UseConcMarkSweepGC" "-XX:+UseConcMarkSweepGC"
) )
@@ -97,7 +98,7 @@ projectTest(taskName = "performanceTest") {
classpath += files("$yourKitHome/lib/yjp-controller-api-redist.jar") classpath += files("$yourKitHome/lib/yjp-controller-api-redist.jar")
} }
currentOs.isMacOsX -> { currentOs.isMacOsX -> {
jvmArgs("-agentpath:$yourKitHome/Contents/Resources/bin/mac/libyjpagent.dylib") jvmArgs("-agentpath:$yourKitHome/Contents/Resources/bin/mac/libyjpagent.dylib=delay=5000,_socket_timeout_ms=120000,disablealloc,disable_async_sampling,disablenatives")
classpath += files("$yourKitHome/Contents/Resources/lib/yjp-controller-api-redist.jar") classpath += files("$yourKitHome/Contents/Resources/lib/yjp-controller-api-redist.jar")
} }
} }
@@ -130,7 +131,6 @@ projectTest(taskName = "wholeProjectsPerformanceTest") {
jvmArgs("-DemptyProfile=${System.getProperty("emptyProfile")}") jvmArgs("-DemptyProfile=${System.getProperty("emptyProfile")}")
jvmArgs("-XX:SoftRefLRUPolicyMSPerMB=50") jvmArgs("-XX:SoftRefLRUPolicyMSPerMB=50")
jvmArgs( jvmArgs(
"-XX:ReservedCodeCacheSize=240m",
"-XX:+UseCompressedOops", "-XX:+UseCompressedOops",
"-XX:+UseConcMarkSweepGC" "-XX:+UseConcMarkSweepGC"
) )
@@ -5,12 +5,14 @@
package org.jetbrains.kotlin.idea.perf package org.jetbrains.kotlin.idea.perf
import com.intellij.codeHighlighting.Pass
import com.intellij.codeInsight.daemon.impl.HighlightInfo import com.intellij.codeInsight.daemon.impl.HighlightInfo
import com.intellij.testFramework.UsefulTestCase import com.intellij.testFramework.UsefulTestCase
import org.jetbrains.kotlin.idea.perf.util.ExternalProject import org.jetbrains.kotlin.idea.perf.util.ExternalProject
import org.jetbrains.kotlin.idea.perf.util.lastPathSegment import org.jetbrains.kotlin.idea.perf.util.lastPathSegment
import org.jetbrains.kotlin.idea.perf.util.suite import org.jetbrains.kotlin.idea.perf.util.suite
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners import org.jetbrains.kotlin.test.JUnit3RunnerWithInners
import org.junit.Ignore
import org.junit.runner.RunWith import org.junit.runner.RunWith
/** /**
@@ -31,6 +33,7 @@ class AHeavyInspectionsPerformanceTest : UsefulTestCase() {
"UnusedSymbol", "UnusedSymbol",
"MemberVisibilityCanBePrivate" "MemberVisibilityCanBePrivate"
) )
val passesToIgnore = ((1 until 100) - Pass.LOCAL_INSPECTIONS - Pass.WHOLE_FILE_LOCAL_INSPECTIONS).toIntArray()
fun testLocalInspection() { fun testLocalInspection() {
suite { suite {
@@ -41,6 +44,65 @@ class AHeavyInspectionsPerformanceTest : UsefulTestCase() {
for (file in listOfFiles) { for (file in listOfFiles) {
val editorFile = editor(file) val editorFile = editor(file)
measure<List<HighlightInfo>>(inspection, file.lastPathSegment()) {
test = {
highlight(editorFile, passesToIgnore)
}
}
}
}
}
}
}
}
@Ignore
fun testUnusedSymbolLocalInspection() {
suite {
config.warmup = 1
config.iterations = 2
config.profilerConfig.enabled = true
config.profilerConfig.tracing = true
app {
project(ExternalProject.KOTLIN_AUTO) {
for (inspection in listOfInspections.sliceArray(0 until 1)) {
enableSingleInspection(inspection)
for (file in listOfFiles.sliceArray(0 until 1)) {
val editorFile = editor(file)
measure<List<HighlightInfo>>(inspection, file.lastPathSegment()) {
test = {
highlight(editorFile, passesToIgnore)
}
}
}
}
}
}
}
}
// used locally to test
val UNUSED_AUTO = ExternalProject(
"../unused",
ExternalProject.autoOpenAction("../unused")
)
@Ignore
fun testSingleInspection1() {
suite {
config.warmup = 0
config.iterations = 1
config.profilerConfig.enabled = true
config.profilerConfig.tracing = true
app {
project(UNUSED_AUTO) {
for (inspection in listOfInspections.sliceArray(0..1)) {
enableSingleInspection(inspection)
val listOfFiles = listOf("src/main/kotlin/org/test/test.kt")
for (file in listOfFiles) {
val editorFile = editor(file)
measure<List<HighlightInfo>>(inspection, file.lastPathSegment()) { measure<List<HighlightInfo>>(inspection, file.lastPathSegment()) {
test = { test = {
highlight(editorFile) highlight(editorFile)
@@ -350,7 +350,7 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
stats(stats) stats(stats)
warmUpIterations(8) warmUpIterations(8)
iterations(15) iterations(15)
profilerEnabled(true) profilerConfig.enabled = true
setUp { setUp {
val markerOffset = editor.document.text.indexOf(marker) val markerOffset = editor.document.text.indexOf(marker)
assertTrue("marker '$marker' not found in $fileName", markerOffset > 0) assertTrue("marker '$marker' not found in $fileName", markerOffset > 0)
@@ -468,7 +468,7 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
} }
commitAllDocuments() commitAllDocuments()
} }
profilerEnabled(true) profilerConfig.enabled = true
} }
} }
@@ -543,7 +543,7 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
commitAllDocuments() commitAllDocuments()
} }
} }
profilerEnabled(true) profilerConfig.enabled = true
} }
} }
@@ -610,7 +610,7 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
} }
PsiManager.getInstance(project).dropPsiCaches() PsiManager.getInstance(project).dropPsiCaches()
} }
profilerEnabled(!isWarmUp) profilerConfig.enabled = true
} }
highlightInfos highlightInfos
} }
@@ -663,7 +663,7 @@ abstract class AbstractPerformanceProjectsTest : UsefulTestCase() {
} }
it.value?.let { v -> assertNotNull(v) } it.value?.let { v -> assertNotNull(v) }
} }
profilerEnabled(true) profilerConfig.enabled = true
checkStability(false) checkStability(false)
} }
} }
@@ -15,9 +15,8 @@ class PerfTestBuilder<SV, TV> {
private var setUp: (TestData<SV, TV>) -> Unit = { } private var setUp: (TestData<SV, TV>) -> Unit = { }
private lateinit var test: (TestData<SV, TV>) -> Unit private lateinit var test: (TestData<SV, TV>) -> Unit
private var tearDown: (TestData<SV, TV>) -> Unit = { } private var tearDown: (TestData<SV, TV>) -> Unit = { }
private var profilerEnabled: Boolean = false
private var checkStability: Boolean = true private var checkStability: Boolean = true
private var profilerConfig: ProfilerConfig = ProfilerConfig() internal var profilerConfig: ProfilerConfig = ProfilerConfig()
internal fun run() { internal fun run() {
stats.perfTest( stats.perfTest(
@@ -27,7 +26,6 @@ class PerfTestBuilder<SV, TV> {
setUp = setUp, setUp = setUp,
test = test, test = test,
tearDown = tearDown, tearDown = tearDown,
profilerEnabled = profilerEnabled,
checkStability = checkStability checkStability = checkStability
) )
} }
@@ -60,10 +58,6 @@ class PerfTestBuilder<SV, TV> {
this.tearDown = tearDown this.tearDown = tearDown
} }
fun profilerEnabled(profilerEnabled: Boolean) {
this.profilerEnabled = profilerEnabled
}
fun profilerConfig(profilerConfig: ProfilerConfig) { fun profilerConfig(profilerConfig: ProfilerConfig) {
this.profilerConfig = profilerConfig this.profilerConfig = profilerConfig
} }
@@ -294,7 +294,7 @@ class PerformanceProjectsTest : AbstractPerformanceProjectsTest() {
setUp(perfKtsFileAnalysisSetUp(project, fileName)) setUp(perfKtsFileAnalysisSetUp(project, fileName))
test(perfKtsFileAnalysisTest()) test(perfKtsFileAnalysisTest())
tearDown(perfKtsFileAnalysisTearDown(extraTimingsNs, project)) tearDown(perfKtsFileAnalysisTearDown(extraTimingsNs, project))
profilerEnabled(true) profilerConfig.enabled = true
} }
extraStats.printWarmUpTimings( extraStats.printWarmUpTimings(
@@ -127,7 +127,6 @@ class Stats(
setUp: (TestData<SV, TV>) -> Unit = { }, setUp: (TestData<SV, TV>) -> Unit = { },
test: (TestData<SV, TV>) -> Unit, test: (TestData<SV, TV>) -> Unit,
tearDown: (TestData<SV, TV>) -> Unit = { }, tearDown: (TestData<SV, TV>) -> Unit = { },
profilerEnabled: Boolean = false,
checkStability: Boolean = true checkStability: Boolean = true
) { ) {
@@ -137,15 +136,13 @@ class Stats(
setUp = setUp, setUp = setUp,
test = test, test = test,
tearDown = tearDown, tearDown = tearDown,
profilerEnabled = profilerEnabled
) )
val mainPhaseData = PhaseData( val mainPhaseData = PhaseData(
iterations = iterations, iterations = iterations,
testName = testName, testName = testName,
setUp = setUp, setUp = setUp,
test = test, test = test,
tearDown = tearDown, tearDown = tearDown
profilerEnabled = profilerEnabled
) )
val block = { val block = {
warmUpPhase(warmPhaseData) warmUpPhase(warmPhaseData)
@@ -228,7 +225,7 @@ class Stats(
} }
private fun <SV, TV> warmUpPhase(phaseData: PhaseData<SV, TV>) { private fun <SV, TV> warmUpPhase(phaseData: PhaseData<SV, TV>) {
val warmUpStatInfosArray = phase(phaseData, WARM_UP) val warmUpStatInfosArray = phase(phaseData, WARM_UP, true)
if (phaseData.testName != WARM_UP) { if (phaseData.testName != WARM_UP) {
printWarmUpTimings(phaseData.testName, warmUpStatInfosArray) printWarmUpTimings(phaseData.testName, warmUpStatInfosArray)
@@ -256,17 +253,18 @@ class Stats(
return statInfosArray return statInfosArray
} }
private fun <SV, TV> phase(phaseData: PhaseData<SV, TV>, phaseName: String): Array<StatInfos> { private fun <SV, TV> phase(phaseData: PhaseData<SV, TV>, phaseName: String, warmup: Boolean = false): Array<StatInfos> {
val statInfosArray = Array<StatInfos>(phaseData.iterations) { null } val statInfosArray = Array<StatInfos>(phaseData.iterations) { null }
val testData = TestData<SV, TV>(null, null) val testData = TestData<SV, TV>(null, null)
try { try {
val phaseProfiler =
createPhaseProfiler(phaseData.testName, phaseName, profilerConfig.copy(warmup = warmup))
for (attempt in 0 until phaseData.iterations) { for (attempt in 0 until phaseData.iterations) {
testData.reset() testData.reset()
triggerGC(attempt) triggerGC(attempt)
val phaseProfiler = createPhaseProfiler(phaseData, phaseName, attempt, profilerConfig)
val setUpMillis = measureTimeMillis { phaseData.setUp(testData) } val setUpMillis = measureTimeMillis { phaseData.setUp(testData) }
val attemptName = "${phaseData.testName} #$attempt" val attemptName = "${phaseData.testName} #$attempt"
logMessage { "$attemptName setup took $setUpMillis ms" } logMessage { "$attemptName setup took $setUpMillis ms" }
@@ -274,12 +272,10 @@ class Stats(
val valueMap = HashMap<String, Any>(2 * PerformanceCounter.numberOfCounters + 1) val valueMap = HashMap<String, Any>(2 * PerformanceCounter.numberOfCounters + 1)
statInfosArray[attempt] = valueMap statInfosArray[attempt] = valueMap
try { try {
phaseProfiler.start() phaseProfiler.start()
valueMap[TEST_KEY] = measureNanoTime { valueMap[TEST_KEY] = measureNanoTime {
phaseData.test(testData) phaseData.test(testData)
} }
phaseProfiler.stop()
PerformanceCounter.report { name, counter, nanos -> PerformanceCounter.report { name, counter, nanos ->
valueMap["counter \"$name\": count"] = counter.toLong() valueMap["counter \"$name\": count"] = counter.toLong()
@@ -291,6 +287,7 @@ class Stats(
valueMap[ERROR_KEY] = t valueMap[ERROR_KEY] = t
break break
} finally { } finally {
phaseProfiler.stop()
try { try {
val tearDownMillis = measureTimeMillis { val tearDownMillis = measureTimeMillis {
phaseData.tearDown(testData) phaseData.tearDown(testData)
@@ -312,20 +309,20 @@ class Stats(
return statInfosArray return statInfosArray
} }
private fun <K, T> createPhaseProfiler( private fun createPhaseProfiler(
phaseData: PhaseData<K, T>, testName: String,
phaseName: String, phaseName: String,
attempt: Int,
profilerConfig: ProfilerConfig profilerConfig: ProfilerConfig
): PhaseProfiler { ): PhaseProfiler {
val profilerHandler = if (phaseData.profilerEnabled) ProfilerHandler.getInstance() else DummyProfilerHandler profilerConfig.name = "$testName${if (phaseName.isEmpty()) "" else "-"+phaseName}"
profilerConfig.path = pathToResource("profile/${plainname()}")
val profilerHandler = if (profilerConfig.enabled && !profilerConfig.warmup)
ProfilerHandler.getInstance(profilerConfig)
else
DummyProfilerHandler
return if (profilerHandler != DummyProfilerHandler) { return if (profilerHandler != DummyProfilerHandler) {
val profilerPath = pathToResource("profile/${plainname()}") ActualPhaseProfiler(profilerHandler)
check(with(File(profilerPath)) { exists() || mkdirs() }) { "unable to mkdirs $profilerPath for ${phaseData.testName}" }
val activityName = "${phaseData.testName}-${if (phaseName.isEmpty()) "" else "$phaseName-"}$attempt"
ActualPhaseProfiler(activityName, profilerPath, profilerHandler, profilerConfig)
} else { } else {
DummyPhaseProfiler DummyPhaseProfiler
} }
@@ -374,8 +371,7 @@ data class PhaseData<SV, TV>(
val testName: String, val testName: String,
val setUp: (TestData<SV, TV>) -> Unit, val setUp: (TestData<SV, TV>) -> Unit,
val test: (TestData<SV, TV>) -> Unit, val test: (TestData<SV, TV>) -> Unit,
val tearDown: (TestData<SV, TV>) -> Unit, val tearDown: (TestData<SV, TV>) -> Unit
val profilerEnabled: Boolean = false
) )
data class TestData<SV, TV>(var setUpValue: SV?, var value: TV?) { data class TestData<SV, TV>(var setUpValue: SV?, var value: TV?) {
@@ -17,19 +17,18 @@ object DummyPhaseProfiler : PhaseProfiler {
} }
class ActualPhaseProfiler( class ActualPhaseProfiler(
private val activityName: String, private val profilerHandler: ProfilerHandler
private val profilerPath: String, ) : PhaseProfiler {
private val profilerHandler: ProfilerHandler, private var attempt: Int = 1
private val config: ProfilerConfig = ProfilerConfig()
) :
PhaseProfiler {
override fun start() { override fun start() {
profilerHandler.startProfiling(activityName, config) profilerHandler.startProfiling()
} }
override fun stop() { override fun stop() {
profilerHandler.stopProfiling(profilerPath, activityName, config) profilerHandler.stopProfiling(attempt)
attempt++
} }
} }
data class ProfilerConfig(var tracing: Boolean = false, var options: List<String> = emptyList()) data class ProfilerConfig(var enabled: Boolean = false, var path: String = "", var name: String = "", var tracing: Boolean = false, var options: List<String> = emptyList(), var warmup: Boolean = false)
@@ -8,33 +8,44 @@ package org.jetbrains.kotlin.idea.perf.profilers
import org.jetbrains.kotlin.idea.perf.profilers.async.AsyncProfilerHandler import org.jetbrains.kotlin.idea.perf.profilers.async.AsyncProfilerHandler
import org.jetbrains.kotlin.idea.perf.profilers.yk.YKProfilerHandler import org.jetbrains.kotlin.idea.perf.profilers.yk.YKProfilerHandler
import org.jetbrains.kotlin.idea.perf.util.logMessage import org.jetbrains.kotlin.idea.perf.util.logMessage
import java.nio.file.Path
interface ProfilerHandler { interface ProfilerHandler {
fun startProfiling(activityName: String, config: ProfilerConfig) fun startProfiling()
fun stopProfiling(snapshotsPath: String, activityName: String, config: ProfilerConfig) fun stopProfiling(attempt: Int)
companion object { companion object {
private var instance: ProfilerHandler? = null private var instance: ProfilerHandler? = null
@Synchronized @Synchronized
fun getInstance(): ProfilerHandler { fun getInstance(profilerConfig: ProfilerConfig): ProfilerHandler {
return instance ?: run { return instance ?: run {
val handler = val handler =
doOrLog("asyncProfiler not found") { AsyncProfilerHandler() } doOrLog("asyncProfiler not found") { AsyncProfilerHandler(profilerConfig) }
?: doOrLog("yourKit not found") { YKProfilerHandler() } ?: DummyProfilerHandler ?: doOrLog("yourKit not found") { YKProfilerHandler(profilerConfig) } ?: DummyProfilerHandler
instance = handler instance = handler
return handler return handler
} }
} }
}
fun determinePhasePath(dumpPath: Path, profilerConfig: ProfilerConfig): Path {
val activityPath = dumpPath.parent.resolve(profilerConfig.path)
val runNumber =
(activityPath.toFile().listFiles()?.maxBy { it.name.toIntOrNull() ?: 0 }?.name?.toIntOrNull()
?: 0) + 1
val runPath = activityPath.resolve("$runNumber")
runPath.toFile().mkdirs()
logMessage { "profiler's run path found $runPath" }
return runPath
}
}
} }
object DummyProfilerHandler : ProfilerHandler { object DummyProfilerHandler : ProfilerHandler {
override fun startProfiling(activityName: String, config: ProfilerConfig) {} override fun startProfiling() {}
override fun stopProfiling(snapshotsPath: String, activityName: String, config: ProfilerConfig) {} override fun stopProfiling(attempt: Int) {}
} }
internal fun <T> doOrLog(message: String, block: () -> T?): T? { internal fun <T> doOrLog(message: String, block: () -> T?): T? {
@@ -26,7 +26,7 @@ import java.util.*
* *
* AsyncProfiler could be downloaded from https://github.com/jvm-profiling-tools/async-profiler/releases/ * AsyncProfiler could be downloaded from https://github.com/jvm-profiling-tools/async-profiler/releases/
*/ */
internal class AsyncProfilerHandler : ProfilerHandler { internal class AsyncProfilerHandler(val profilerConfig: ProfilerConfig) : ProfilerHandler {
private val asyncProfiler: Any private val asyncProfiler: Any
@@ -48,20 +48,20 @@ internal class AsyncProfilerHandler : ProfilerHandler {
executeMethod.invoke(asyncProfiler, command) executeMethod.invoke(asyncProfiler, command)
} }
override fun startProfiling(activityName: String, config: ProfilerConfig) { override fun startProfiling() {
try { try {
profilingOptions = config.options profilingOptions = profilerConfig.options
execute(AsyncProfilerCommandBuilder.buildStartCommand(config.options)) execute(AsyncProfilerCommandBuilder.buildStartCommand(profilerConfig.options))
profilingStarted = true profilingStarted = true
} catch (e: Exception) { } catch (e: Exception) {
throw RuntimeException(e) throw RuntimeException(e)
} }
} }
override fun stopProfiling(snapshotsPath: String, activityName: String, config: ProfilerConfig) { override fun stopProfiling(attempt: Int) {
val combinedOptions = ArrayList(config.options) val combinedOptions = ArrayList(profilerConfig.options)
val commandBuilder = AsyncProfilerCommandBuilder(snapshotsPath) val commandBuilder = AsyncProfilerCommandBuilder(profilerConfig.path)
val name = activityName.replace(' ', '_').replace('/', '_') val name = "${profilerConfig.name}-$attempt".replace(' ', '_').replace('/', '_')
val stopAndDumpCommands = commandBuilder.buildStopAndDumpCommands(name, combinedOptions) val stopAndDumpCommands = commandBuilder.buildStopAndDumpCommands(name, combinedOptions)
for (stopCommand in stopAndDumpCommands) { for (stopCommand in stopAndDumpCommands) {
execute(stopCommand) execute(stopCommand)
@@ -7,11 +7,15 @@ package org.jetbrains.kotlin.idea.perf.profilers.yk
import org.jetbrains.kotlin.idea.perf.profilers.ProfilerConfig import org.jetbrains.kotlin.idea.perf.profilers.ProfilerConfig
import org.jetbrains.kotlin.idea.perf.profilers.ProfilerHandler import org.jetbrains.kotlin.idea.perf.profilers.ProfilerHandler
import org.jetbrains.kotlin.idea.perf.profilers.ProfilerHandler.Companion.determinePhasePath
import org.jetbrains.kotlin.idea.perf.profilers.doOrThrow import org.jetbrains.kotlin.idea.perf.profilers.doOrThrow
import org.jetbrains.kotlin.idea.perf.util.logMessage import org.jetbrains.kotlin.idea.perf.util.logMessage
import java.io.PrintWriter
import java.io.StringWriter
import java.lang.management.ManagementFactory import java.lang.management.ManagementFactory
import java.lang.reflect.Method import java.lang.reflect.Method
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths import java.nio.file.Paths
/** /**
@@ -19,9 +23,10 @@ import java.nio.file.Paths
* - ${YOURKIT_PROFILER_HOME}/lib/yjp-controller-api-redist.jar has to be in a classpath * - ${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` * - add agentpath vm paramter like `-agentpath:${YOURKIT_PROFILER_HOME}/Resources/bin/mac/libyjpagent.dylib`
*/ */
class YKProfilerHandler : ProfilerHandler { class YKProfilerHandler(val profilerConfig: ProfilerConfig) : ProfilerHandler {
private var controller: Any private var controller: Any
lateinit var phasePath: Path
init { init {
check(ManagementFactory.getRuntimeMXBean().inputArguments.any { it.contains("yjpagent") }) { check(ManagementFactory.getRuntimeMXBean().inputArguments.any { it.contains("yjpagent") }) {
@@ -33,20 +38,28 @@ class YKProfilerHandler : ProfilerHandler {
} }
} }
override fun startProfiling(activityName: String, config: ProfilerConfig) { override fun startProfiling() {
if (config.tracing) try {
startTracingMethod.invoke(controller, null) if (profilerConfig.tracing) {
else startTracingMethod.invoke(controller, null)
startCPUSamplingMethod.invoke(controller, null) } else {
startSamplingMethod.invoke(controller, null)
}
} catch (e: Exception) {
val stringWriter = StringWriter().also { e.printStackTrace(PrintWriter(it)) }
logMessage { "exception while starting profile ${e.localizedMessage} $stringWriter" }
}
} }
override fun stopProfiling(snapshotsPath: String, activityName: String, config: ProfilerConfig) { override fun stopProfiling(attempt: Int) {
val dumpPath = captureSnapshotMethod.invoke(controller, SNAPSHOT_WITHOUT_HEAP) as String val pathToSnapshot = captureSnapshotMethod.invoke(controller, SNAPSHOT_WITHOUT_HEAP) as String
stopCPUProfilingMethod.invoke(controller) stopCpuProfilingMethod.invoke(controller)
val path = Paths.get(dumpPath) val dumpPath = Paths.get(pathToSnapshot)
val target = path.parent.resolve(snapshotsPath).resolve(activityName) if (!this::phasePath.isInitialized)
logMessage { "dump is moved to $target" } phasePath = determinePhasePath(dumpPath, profilerConfig)
Files.move(path, target) val targetFile = phasePath.resolve("$attempt-${profilerConfig.name}.snapshot")
logMessage { "dump is moved to $targetFile" }
Files.move(dumpPath, targetFile)
} }
companion object { companion object {
@@ -55,9 +68,9 @@ class YKProfilerHandler : ProfilerHandler {
private val ykLibClass: Class<*> = doOrThrow("yjp-controller-api-redist.jar is not in a classpath") { private val ykLibClass: Class<*> = doOrThrow("yjp-controller-api-redist.jar is not in a classpath") {
Class.forName("com.yourkit.api.Controller") Class.forName("com.yourkit.api.Controller")
} }
private val startCPUSamplingMethod: Method = doOrThrow("com.yourkit.api.Controller#startCPUSampling(String) not found") { private val startSamplingMethod: Method = doOrThrow("com.yourkit.api.Controller#startSampling(String) not found") {
ykLibClass.getMethod( ykLibClass.getMethod(
"startCPUSampling", "startSampling",
String::class.java String::class.java
) )
} }
@@ -74,12 +87,13 @@ class YKProfilerHandler : ProfilerHandler {
) )
} }
private val capturePerformanceSnapshotMethod: Method = doOrThrow("com.yourkit.api.Controller#capturePerformanceSnapshot() not found") { private val capturePerformanceSnapshotMethod: Method =
ykLibClass.getMethod("capturePerformanceSnapshot") doOrThrow("com.yourkit.api.Controller#capturePerformanceSnapshot() not found") {
} ykLibClass.getMethod("capturePerformanceSnapshot")
}
private val stopCPUProfilingMethod: Method = doOrThrow("com.yourkit.api.Controller#stopCPUProfiling() not found") { private val stopCpuProfilingMethod: Method = doOrThrow("com.yourkit.api.Controller#stopCPUProfiling() not found") {
ykLibClass.getMethod("stopCPUProfiling") ykLibClass.getMethod("stopCpuProfiling")
} }
} }
} }
@@ -61,11 +61,11 @@ class PerformanceSuite {
} }
} }
private fun PsiFile.highlightFile(): List<HighlightInfo> { private fun PsiFile.highlightFile(toIgnore: IntArray = ArrayUtilRt.EMPTY_INT_ARRAY): List<HighlightInfo> {
val document = FileDocumentManager.getInstance().getDocument(virtualFile)!! val document = FileDocumentManager.getInstance().getDocument(virtualFile)!!
val editor = EditorFactory.getInstance().getEditors(document).first() val editor = EditorFactory.getInstance().getEditors(document).first()
PsiDocumentManager.getInstance(project).commitAllDocuments() PsiDocumentManager.getInstance(project).commitAllDocuments()
return CodeInsightTestFixtureImpl.instantiateAndRun(this, editor, ArrayUtilRt.EMPTY_INT_ARRAY, true) return CodeInsightTestFixtureImpl.instantiateAndRun(this, editor, toIgnore, true)
} }
fun rollbackChanges(vararg file: VirtualFile) { fun rollbackChanges(vararg file: VirtualFile) {
@@ -139,7 +139,6 @@ class PerformanceSuite {
tearDown { tearDown {
after?.invoke() after?.invoke()
} }
profilerEnabled(config.profile)
profilerConfig(config.profilerConfig) profilerConfig(config.profilerConfig)
} }
return value return value
@@ -216,7 +215,7 @@ class PerformanceSuite {
} }
class StatsScopeConfig(var name: String? = null, var warmup: Int = 2, var iterations: Int = 5, var profile: Boolean = false, var profilerConfig: ProfilerConfig = ProfilerConfig()) class StatsScopeConfig(var name: String? = null, var warmup: Int = 2, var iterations: Int = 5, var profilerConfig: ProfilerConfig = ProfilerConfig())
class ProjectScopeConfig(val path: String, val openWith: ProjectOpenAction, val refresh: Boolean = false) { class ProjectScopeConfig(val path: String, val openWith: ProjectOpenAction, val refresh: Boolean = false) {
val name: String = path.lastPathSegment() val name: String = path.lastPathSegment()
@@ -239,10 +238,8 @@ class PerformanceSuite {
fun highlight(fixture: Fixture) = highlight(fixture.psiFile) fun highlight(fixture: Fixture) = highlight(fixture.psiFile)
fun highlight(editorFile: PsiFile?) = fun highlight(editorFile: PsiFile?, toIgnore: IntArray = ArrayUtilRt.EMPTY_INT_ARRAY) =
editorFile?.let { editorFile?.highlightFile(toIgnore) ?: error("editor isn't ready for highlight")
it.highlightFile()
} ?: error("editor isn't ready for highlight")
fun moveCursor(config: TypingConfig) { fun moveCursor(config: TypingConfig) {
val fixture = config.fixture val fixture = config.fixture
@@ -322,7 +319,7 @@ class PerformanceSuite {
fun <T> measure(vararg name: String, f: MeasurementScope<T>.() -> Unit): List<T?> { fun <T> measure(vararg name: String, f: MeasurementScope<T>.() -> Unit): List<T?> {
val after = { PsiManager.getInstance(project).dropPsiCaches() } val after = { PsiManager.getInstance(project).dropPsiCaches() }
return app.stats.measure("${name.joinToString("-")}", f, after) return app.stats.measure(name.joinToString("-"), f, after)
} }
fun <T> measure(vararg name: String, fixture: Fixture, f: MeasurementScope<T>.() -> Unit): List<T?> = fun <T> measure(vararg name: String, fixture: Fixture, f: MeasurementScope<T>.() -> Unit): List<T?> =