Create performance counter with excluded counters

This commit is contained in:
Stanislav Erokhin
2015-06-18 21:18:54 +03:00
parent d830729ddb
commit 45a3d7ec46
3 changed files with 190 additions and 73 deletions
@@ -48,6 +48,7 @@ import org.jetbrains.kotlin.types.JetType;
import org.jetbrains.kotlin.types.TypeSubstitutor;
import org.jetbrains.kotlin.types.expressions.ExpressionTypingContext;
import org.jetbrains.kotlin.types.expressions.ExpressionTypingServices;
import org.jetbrains.kotlin.types.expressions.ExpressionTypingVisitorDispatcher;
import org.jetbrains.kotlin.types.expressions.OperatorConventions;
import org.jetbrains.kotlin.util.PerformanceCounter;
@@ -74,7 +75,7 @@ public class CallResolver {
private TaskPrioritizer taskPrioritizer;
private AdditionalCheckerProvider additionalCheckerProvider;
private static final PerformanceCounter callResolvePerfCounter = PerformanceCounter.Companion.create("Call resolve", true);
private static final PerformanceCounter callResolvePerfCounter = PerformanceCounter.Companion.create("Call resolve", ExpressionTypingVisitorDispatcher.typeInfoPerfCounter);
private static final PerformanceCounter candidatePerfCounter = PerformanceCounter.Companion.create("Call resolve candidate analysis", true);
@Inject
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.types.expressions;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProcessCanceledException;
import kotlin.jvm.functions.Function0;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils;
@@ -30,6 +31,7 @@ import org.jetbrains.kotlin.types.DeferredType;
import org.jetbrains.kotlin.types.ErrorUtils;
import org.jetbrains.kotlin.types.JetType;
import org.jetbrains.kotlin.types.expressions.typeInfoFactory.TypeInfoFactoryPackage;
import org.jetbrains.kotlin.util.PerformanceCounter;
import org.jetbrains.kotlin.util.ReenteringLazyValueComputationException;
import org.jetbrains.kotlin.utils.KotlinFrontEndException;
@@ -38,6 +40,8 @@ import static org.jetbrains.kotlin.resolve.bindingContextUtil.BindingContextUtil
public class ExpressionTypingVisitorDispatcher extends JetVisitor<JetTypeInfo, ExpressionTypingContext> implements ExpressionTypingInternals {
public static final PerformanceCounter typeInfoPerfCounter = PerformanceCounter.Companion.create("Type info", true);
public interface StatementVisitorProvider {
ExpressionTypingVisitorForStatements get(@NotNull ExpressionTypingContext context);
}
@@ -161,50 +165,55 @@ public class ExpressionTypingVisitorDispatcher extends JetVisitor<JetTypeInfo, E
}
@NotNull
private static JetTypeInfo getTypeInfo(@NotNull JetExpression expression, ExpressionTypingContext context, JetVisitor<JetTypeInfo, ExpressionTypingContext> visitor) {
try {
JetTypeInfo recordedTypeInfo = BindingContextUtils.getRecordedTypeInfo(expression, context.trace.getBindingContext());
if (recordedTypeInfo != null) {
return recordedTypeInfo;
}
JetTypeInfo result;
try {
result = expression.accept(visitor, context);
// Some recursive definitions (object expressions) must put their types in the cache manually:
//noinspection ConstantConditions
if (context.trace.get(BindingContext.PROCESSED, expression)) {
JetType type = context.trace.getBindingContext().getType(expression);
return result.replaceType(type);
}
private static JetTypeInfo getTypeInfo(@NotNull final JetExpression expression, final ExpressionTypingContext context, final JetVisitor<JetTypeInfo, ExpressionTypingContext> visitor) {
return typeInfoPerfCounter.time(new Function0<JetTypeInfo>() {
@Override
public JetTypeInfo invoke() {
try {
JetTypeInfo recordedTypeInfo = BindingContextUtils.getRecordedTypeInfo(expression, context.trace.getBindingContext());
if (recordedTypeInfo != null) {
return recordedTypeInfo;
}
JetTypeInfo result;
try {
result = expression.accept(visitor, context);
// Some recursive definitions (object expressions) must put their types in the cache manually:
//noinspection ConstantConditions
if (context.trace.get(BindingContext.PROCESSED, expression)) {
JetType type = context.trace.getBindingContext().getType(expression);
return result.replaceType(type);
}
if (result.getType() instanceof DeferredType) {
result = result.replaceType(((DeferredType) result.getType()).getDelegate());
}
context.trace.record(BindingContext.EXPRESSION_TYPE_INFO, expression, result);
}
catch (ReenteringLazyValueComputationException e) {
context.trace.report(TYPECHECKER_HAS_RUN_INTO_RECURSIVE_PROBLEM.on(expression));
result = TypeInfoFactoryPackage.noTypeInfo(context);
}
if (result.getType() instanceof DeferredType) {
result = result.replaceType(((DeferredType) result.getType()).getDelegate());
}
context.trace.record(BindingContext.EXPRESSION_TYPE_INFO, expression, result);
}
catch (ReenteringLazyValueComputationException e) {
context.trace.report(TYPECHECKER_HAS_RUN_INTO_RECURSIVE_PROBLEM.on(expression));
result = TypeInfoFactoryPackage.noTypeInfo(context);
}
context.trace.record(BindingContext.PROCESSED, expression);
recordScopeAndDataFlowInfo(context.replaceDataFlowInfo(result.getDataFlowInfo()), expression);
return result;
}
catch (ProcessCanceledException e) {
throw e;
}
catch (KotlinFrontEndException e) {
throw e;
}
catch (Throwable e) {
context.trace.report(Errors.EXCEPTION_FROM_ANALYZER.on(expression, e));
logOrThrowException(expression, e);
return TypeInfoFactoryPackage.createTypeInfo(
ErrorUtils.createErrorType(e.getClass().getSimpleName() + " from analyzer"),
context
);
}
context.trace.record(BindingContext.PROCESSED, expression);
recordScopeAndDataFlowInfo(context.replaceDataFlowInfo(result.getDataFlowInfo()), expression);
return result;
}
catch (ProcessCanceledException e) {
throw e;
}
catch (KotlinFrontEndException e) {
throw e;
}
catch (Throwable e) {
context.trace.report(Errors.EXCEPTION_FROM_ANALYZER.on(expression, e));
logOrThrowException(expression, e);
return TypeInfoFactoryPackage.createTypeInfo(
ErrorUtils.createErrorType(e.getClass().getSimpleName() + " from analyzer"),
context
);
}
}
});
}
private static void logOrThrowException(@NotNull JetExpression expression, Throwable e) {
@@ -17,35 +17,20 @@
package org.jetbrains.kotlin.util
import java.lang.management.ManagementFactory
import java.util.*
import java.util.concurrent.TimeUnit
public class PerformanceCounter private constructor (val name: String, val reenterable: Boolean = false) {
public abstract class PerformanceCounter protected constructor(val name: String) {
companion object {
private val threadMxBean = ManagementFactory.getThreadMXBean()
private val allCounters = arrayListOf<PerformanceCounter>()
private val enteredCounters = ThreadLocal<MutableSet<PerformanceCounter>>()
private var enabled = false
init {
threadMxBean.setThreadCpuTimeEnabled(true)
}
private fun enterCounter(counter: PerformanceCounter): Boolean {
var enteredCountersInThread = enteredCounters.get()
if (enteredCountersInThread == null) {
enteredCountersInThread = hashSetOf(counter)
enteredCounters.set(enteredCountersInThread)
return true
}
return enteredCountersInThread.add(counter)
}
private fun leaveCounter(counter: PerformanceCounter) {
enteredCounters.get()?.remove(counter)
}
public fun currentThreadCpuTime(): Long = threadMxBean.getCurrentThreadUserTime()
public fun report(consumer: (String) -> Unit) {
@@ -59,13 +44,29 @@ public class PerformanceCounter private constructor (val name: String, val reent
enabled = enable
}
public jvmOverloads fun create(name: String, reentable: Boolean = false): PerformanceCounter {
return PerformanceCounter(name, reentable)
public jvmOverloads fun create(name: String, reenterable: Boolean = false): PerformanceCounter {
return if (reenterable)
ReenterableCounter(name)
else
SimpleCounter(name)
}
public fun create(name: String, vararg excluded: PerformanceCounter): PerformanceCounter = CounterWithExclude(name, *excluded)
protected inline fun <T> getOrPut(threadLocal: ThreadLocal<T>, default: () -> T) : T {
var value = threadLocal.get()
if (value == null) {
value = default()
threadLocal.set(value)
}
return value
}
}
protected val excludedFrom: MutableList<CounterWithExclude> = ArrayList()
private var count: Int = 0
private var totalTimeNanos: Long = 0
protected var totalTimeNanos: Long = 0
init {
synchronized(allCounters) {
@@ -73,27 +74,25 @@ public class PerformanceCounter private constructor (val name: String, val reent
}
}
public fun increment() {
public final fun increment() {
count++
}
public fun time<T>(block: () -> T): T {
public final fun time<T>(block: () -> T): T {
count++
if (!enabled) return block()
val needTime = !reenterable || enterCounter(this)
val startTime = currentThreadCpuTime()
excludedFrom.forEach { it.enterExcludedMethod() }
try {
return block()
return countTime(block)
}
finally {
if (needTime) {
totalTimeNanos += currentThreadCpuTime() - startTime
if (reenterable) leaveCounter(this)
}
excludedFrom.forEach { it.exitExcludedMethod() }
}
}
protected abstract fun countTime<T>(block: () -> T): T
public fun report(consumer: (String) -> Unit) {
if (totalTimeNanos == 0L) {
consumer("$name performed $count times")
@@ -103,4 +102,112 @@ public class PerformanceCounter private constructor (val name: String, val reent
consumer("$name performed $count times, total time $millis ms")
}
}
}
}
private class SimpleCounter(name: String): PerformanceCounter(name) {
override fun <T> countTime(block: () -> T): T {
val startTime = PerformanceCounter.currentThreadCpuTime()
try {
return block()
}
finally {
totalTimeNanos += PerformanceCounter.currentThreadCpuTime() - startTime
}
}
}
private class ReenterableCounter(name: String): PerformanceCounter(name) {
companion object {
private val enteredCounters = ThreadLocal<MutableSet<ReenterableCounter>>()
private fun enterCounter(counter: ReenterableCounter) = PerformanceCounter.getOrPut(enteredCounters) { HashSet() }.add(counter)
private fun leaveCounter(counter: ReenterableCounter) {
enteredCounters.get()?.remove(counter)
}
}
override fun <T> countTime(block: () -> T): T {
val startTime = PerformanceCounter.currentThreadCpuTime()
val needTime = enterCounter(this)
try {
return block()
}
finally {
if (needTime) {
totalTimeNanos += PerformanceCounter.currentThreadCpuTime() - startTime
leaveCounter(this)
}
}
}
}
/**
* This class allows to calculate pure time for some method excluding some other methods.
* For example, we can calculate total time for CallResolver excluding time for getTypeInfo().
*
* Main and excluded methods may be reenterable.
*/
private class CounterWithExclude(name: String, vararg excludedCounters: PerformanceCounter): PerformanceCounter(name) {
companion object {
private val counterToCallStackMapThreadLocal = ThreadLocal<MutableMap<CounterWithExclude, CallStackWithTime>>()
private fun getCallStack(counter: CounterWithExclude)
= PerformanceCounter.getOrPut(counterToCallStackMapThreadLocal) { HashMap() }.getOrPut(counter) { CallStackWithTime() }
}
init {
excludedCounters.forEach { it.excludedFrom.add(this) }
}
private val callStack: CallStackWithTime
get() = getCallStack(this)
override fun <T> countTime(block: () -> T): T {
totalTimeNanos += callStack.push(true)
try {
return block()
}
finally {
totalTimeNanos += callStack.pop(true)
}
}
fun enterExcludedMethod() {
totalTimeNanos += callStack.push(false)
}
fun exitExcludedMethod() {
totalTimeNanos += callStack.pop(false)
}
private class CallStackWithTime {
private val callStack = Stack<Boolean>()
private var intervalStartTime: Long = 0
fun Stack<Boolean>.peekOrFalse() = if (isEmpty()) false else peek()
private fun intervalUsefulTime(callStackUpdate: Stack<Boolean>.() -> Unit): Long {
val delta = if (callStack.peekOrFalse()) PerformanceCounter.currentThreadCpuTime() - intervalStartTime else 0
callStack.callStackUpdate()
intervalStartTime = PerformanceCounter.currentThreadCpuTime()
return delta
}
fun push(usefulCall: Boolean): Long {
if (!isEnteredCounter() && !usefulCall) return 0
return intervalUsefulTime { push(usefulCall) }
}
fun pop(usefulCall: Boolean): Long {
if (!isEnteredCounter()) return 0
assert(callStack.peek() == usefulCall)
return intervalUsefulTime { pop() }
}
fun isEnteredCounter(): Boolean = !callStack.isEmpty()
}
}