(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:
@@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
)
|
)
|
||||||
|
|||||||
+62
@@ -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)
|
||||||
|
|||||||
+5
-5
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -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?) {
|
||||||
|
|||||||
+8
-9
@@ -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)
|
||||||
|
|||||||
+19
-8
@@ -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? {
|
||||||
|
|||||||
+8
-8
@@ -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)
|
||||||
|
|||||||
+34
-20
@@ -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?> =
|
||||||
|
|||||||
Reference in New Issue
Block a user