package org.graalvm.compiler.debug.internal;
import java.io.PrintStream;
import java.util.Iterator;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicLong;
import org.graalvm.compiler.debug.Debug;
import org.graalvm.compiler.debug.DebugConfig;
import org.graalvm.compiler.debug.DebugDumpHandler;
import org.graalvm.compiler.debug.DebugVerifyHandler;
import org.graalvm.compiler.debug.DelegatingDebugConfig;
import org.graalvm.compiler.debug.Indent;
import org.graalvm.compiler.debug.JavaMethodContext;
import org.graalvm.compiler.debug.TTY;
import org.graalvm.compiler.debug.TopLevelDebugConfig;
import jdk.vm.ci.meta.JavaMethod;
public final class DebugScope implements Debug.Scope {
private final class IndentImpl implements Indent {
private static final String INDENTATION_INCREMENT = " ";
final String indent;
final IndentImpl parentIndent;
IndentImpl(IndentImpl parentIndent) {
this.parentIndent = parentIndent;
this.indent = (parentIndent == null ? "" : parentIndent.indent + INDENTATION_INCREMENT);
}
private boolean logScopeName() {
return logScopeName;
}
private void printScopeName(StringBuilder str, boolean isCurrent) {
if (logScopeName) {
boolean parentPrinted = false;
if (parentIndent != null) {
parentPrinted = parentIndent.logScopeName();
parentIndent.printScopeName(str, false);
}
if (isCurrent || printContext(null) != 0 || !parentPrinted) {
str.append(indent).append("[thread:").append(Thread.currentThread().getId()).append("] scope: ").append(getQualifiedName()).append(System.lineSeparator());
}
printContext(str);
logScopeName = false;
}
}
private int printContext(StringBuilder str) {
int count = 0;
if (context != null && context.length > 0) {
for (Object contextObj : context) {
if (contextObj instanceof JavaMethodContext || contextObj instanceof JavaMethod) {
if (str != null) {
str.append(indent).append("Context: ").append(contextObj).append(System.lineSeparator());
}
count++;
}
}
}
return count;
}
public void log(int logLevel, String msg, Object... args) {
if (isLogEnabled(logLevel)) {
StringBuilder str = new StringBuilder();
printScopeName(str, true);
str.append(indent);
String result = args.length == 0 ? msg : String.format(msg, args);
String lineSep = System.lineSeparator();
str.append(result.replace(lineSep, lineSep.concat(indent)));
str.append(lineSep);
output.append(str);
lastUsedIndent = this;
}
}
IndentImpl indent() {
lastUsedIndent = new IndentImpl(this);
return lastUsedIndent;
}
@Override
public void close() {
if (parentIndent != null) {
lastUsedIndent = parentIndent;
}
}
}
public interface {
}
private static final ThreadLocal<DebugScope> instanceTL = new ThreadLocal<>();
private static final ThreadLocal<DebugScope> lastClosedTL = new ThreadLocal<>();
private static final ThreadLocal<DebugConfig> configTL = new ThreadLocal<>();
private static final ThreadLocal<Throwable> lastExceptionThrownTL = new ThreadLocal<>();
private final DebugScope parent;
private final DebugConfig parentConfig;
private final boolean sandbox;
private IndentImpl lastUsedIndent;
private boolean logScopeName;
private final Object[] context;
private DebugValueMap valueMap;
private String qualifiedName;
private final String unqualifiedName;
private final ExtraInfo ;
private static final AtomicLong uniqueScopeId = new AtomicLong();
private final long scopeId;
private static final char SCOPE_SEP = '.';
private boolean countEnabled;
private boolean timeEnabled;
private boolean memUseTrackingEnabled;
private boolean verifyEnabled;
private boolean methodMetricsEnabled;
private int currentDumpLevel;
private int currentLogLevel;
private PrintStream output;
public static long getCurrentGlobalScopeId() {
return uniqueScopeId.get();
}
public static DebugScope getInstance() {
DebugScope result = instanceTL.get();
if (result == null) {
DebugScope topLevelDebugScope = new DebugScope(Thread.currentThread());
instanceTL.set(topLevelDebugScope);
return topLevelDebugScope;
} else {
return result;
}
}
public static DebugConfig getConfig() {
return configTL.get();
}
static final Object[] EMPTY_CONTEXT = new Object[0];
private DebugScope(Thread thread) {
this(thread.getName(), null, uniqueScopeId.incrementAndGet(), null, false);
computeValueMap(thread.getName());
DebugValueMap.registerTopLevel(getValueMap());
}
private (String unqualifiedName, DebugScope parent, long scopeId, ExtraInfo metaInfo, boolean sandbox, Object... context) {
this.parent = parent;
this.sandbox = sandbox;
this.parentConfig = getConfig();
this.context = context;
this.scopeId = scopeId;
this.unqualifiedName = unqualifiedName;
this.extraInfo = metaInfo;
if (parent != null) {
logScopeName = !unqualifiedName.equals("");
} else {
logScopeName = true;
}
this.output = TTY.out;
assert context != null;
}
private void computeValueMap(String name) {
if (parent != null) {
for (DebugValueMap child : parent.getValueMap().getChildren()) {
if (child.getName().equals(name)) {
this.valueMap = child;
return;
}
}
this.valueMap = new DebugValueMap(name);
parent.getValueMap().addChild(this.valueMap);
} else {
this.valueMap = new DebugValueMap(name);
}
}
@Override
public void close() {
instanceTL.set(parent);
configTL.set(parentConfig);
lastClosedTL.set(this);
}
public boolean isDumpEnabled(int dumpLevel) {
assert dumpLevel > 0;
return currentDumpLevel >= dumpLevel;
}
public static void setDumpLevel(int dumpLevel) {
TopLevelDebugConfig config = fetchTopLevelDebugConfig("setDebugLevel");
if (config != null) {
config.override(DelegatingDebugConfig.Level.DUMP, dumpLevel);
recursiveUpdateFlags();
}
}
public static void setLogLevel(int logLevel) {
TopLevelDebugConfig config = fetchTopLevelDebugConfig("setLogLevel");
if (config != null) {
config.override(DelegatingDebugConfig.Level.LOG, logLevel);
config.delegate(DelegatingDebugConfig.Feature.LOG_METHOD);
recursiveUpdateFlags();
}
}
private static void recursiveUpdateFlags() {
DebugScope c = DebugScope.getInstance();
while (c != null) {
c.updateFlags();
c = c.parent;
}
}
private static TopLevelDebugConfig fetchTopLevelDebugConfig(String msg) {
DebugConfig config = getConfig();
if (config instanceof TopLevelDebugConfig) {
return (TopLevelDebugConfig) config;
} else {
if (config == null) {
TTY.println("DebugScope.%s ignored because debugging is disabled", msg);
} else {
TTY.println("DebugScope.%s ignored because top level delegate config missing", msg);
}
return null;
}
}
public boolean isVerifyEnabled() {
return verifyEnabled;
}
public boolean isLogEnabled(int logLevel) {
assert logLevel > 0;
return currentLogLevel >= logLevel;
}
public boolean isCountEnabled() {
return countEnabled;
}
public boolean isTimeEnabled() {
return timeEnabled;
}
public boolean isMethodMeterEnabled() {
return methodMetricsEnabled;
}
public boolean isMemUseTrackingEnabled() {
return memUseTrackingEnabled;
}
public void log(int logLevel, String msg, Object... args) {
if (isLogEnabled(logLevel)) {
getLastUsedIndent().log(logLevel, msg, args);
}
}
public ExtraInfo () {
return extraInfo;
}
public long scopeId() {
return scopeId;
}
public void dump(int dumpLevel, Object object, String formatString, Object... args) {
if (isDumpEnabled(dumpLevel)) {
DebugConfig config = getConfig();
if (config != null) {
String message = String.format(formatString, args);
for (DebugDumpHandler dumpHandler : config.dumpHandlers()) {
dumpHandler.dump(object, message);
}
}
}
}
public static void forceDump(Object object, String format, Object... args) {
DebugConfig config = getConfig();
if (config != null) {
String message = String.format(format, args);
for (DebugDumpHandler dumpHandler : config.dumpHandlers()) {
dumpHandler.dump(object, message);
}
} else {
TTY.println("Forced dump ignored because debugging is disabled - use -Dgraal.Dump=xxx");
}
}
public void verify(Object object, String formatString, Object... args) {
if (isVerifyEnabled()) {
DebugConfig config = getConfig();
if (config != null) {
String message = String.format(formatString, args);
for (DebugVerifyHandler handler : config.verifyHandlers()) {
handler.verify(object, message);
}
}
}
}
public DebugScope scope(CharSequence name, DebugConfig sandboxConfig, Object... newContextObjects) {
DebugScope newScope = null;
if (sandboxConfig != null) {
newScope = new DebugScope(name.toString(), this, uniqueScopeId.incrementAndGet(), null, true, newContextObjects);
configTL.set(sandboxConfig);
} else {
newScope = this.createChild(name.toString(), this.extraInfo, newContextObjects);
}
instanceTL.set(newScope);
newScope.updateFlags();
return newScope;
}
public DebugScope (CharSequence name, ExtraInfo newInfo, boolean newId, Object... newContext) {
DebugScope newScope = createChild(name.toString(), newInfo, newId ? uniqueScopeId.incrementAndGet() : this.scopeId, newContext);
instanceTL.set(newScope);
newScope.updateFlags();
return newScope;
}
public RuntimeException handle(Throwable e) {
DebugScope lastClosed = lastClosedTL.get();
assert lastClosed.parent == this : "Debug.handle() used with no matching Debug.scope(...) or Debug.sandbox(...)";
if (e != lastExceptionThrownTL.get()) {
RuntimeException newException = null;
instanceTL.set(lastClosed);
try (DebugScope s = lastClosed) {
newException = s.interceptException(e);
}
assert instanceTL.get() == this;
assert lastClosed == lastClosedTL.get();
if (newException == null) {
lastExceptionThrownTL.set(e);
} else {
lastExceptionThrownTL.set(newException);
throw newException;
}
}
if (e instanceof Error) {
throw (Error) e;
}
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}
throw new RuntimeException(e);
}
private void updateFlags() {
DebugConfig config = getConfig();
if (config == null) {
countEnabled = false;
memUseTrackingEnabled = false;
timeEnabled = false;
verifyEnabled = false;
currentDumpLevel = 0;
methodMetricsEnabled = false;
output = TTY.out;
} else {
countEnabled = config.isCountEnabled();
memUseTrackingEnabled = config.isMemUseTrackingEnabled();
timeEnabled = config.isTimeEnabled();
verifyEnabled = config.isVerifyEnabled();
output = config.output();
currentDumpLevel = config.getDumpLevel();
currentLogLevel = config.getLogLevel();
methodMetricsEnabled = config.isMethodMeterEnabled();
}
}
@SuppressWarnings("try")
private RuntimeException interceptException(final Throwable e) {
final DebugConfig config = getConfig();
if (config != null) {
try (DebugScope s = scope("InterceptException", null, e)) {
return config.interceptException(e);
} catch (Throwable t) {
return new RuntimeException("Exception while intercepting exception", t);
}
}
return null;
}
private DebugValueMap getValueMap() {
if (valueMap == null) {
computeValueMap(unqualifiedName);
}
return valueMap;
}
long getCurrentValue(int index) {
return getValueMap().getCurrentValue(index);
}
void setCurrentValue(int index, long l) {
getValueMap().setCurrentValue(index, l);
}
private DebugScope (String newName, ExtraInfo newInfo, Object[] newContext) {
return new DebugScope(newName, this, this.scopeId, newInfo, false, newContext);
}
private DebugScope (String newName, ExtraInfo newInfo, long newId, Object[] newContext) {
return new DebugScope(newName, this, newId, newInfo, false, newContext);
}
public Iterable<Object> getCurrentContext() {
final DebugScope scope = this;
return new Iterable<Object>() {
@Override
public Iterator<Object> iterator() {
return new Iterator<Object>() {
DebugScope currentScope = scope;
int objectIndex;
@Override
public boolean hasNext() {
selectScope();
return currentScope != null;
}
private void selectScope() {
while (currentScope != null && currentScope.context.length <= objectIndex) {
currentScope = currentScope.sandbox ? null : currentScope.parent;
objectIndex = 0;
}
}
@Override
public Object next() {
selectScope();
if (currentScope != null) {
return currentScope.context[objectIndex++];
}
throw new IllegalStateException("May only be called if there is a next element.");
}
@Override
public void remove() {
throw new UnsupportedOperationException("This iterator is read only.");
}
};
}
};
}
public static <T> T call(Callable<T> callable) {
try {
return callable.call();
} catch (Exception e) {
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
} else {
throw new RuntimeException(e);
}
}
}
public void setConfig(DebugConfig newConfig) {
configTL.set(newConfig);
updateFlags();
}
public String getQualifiedName() {
if (qualifiedName == null) {
if (parent == null) {
qualifiedName = unqualifiedName;
} else {
qualifiedName = parent.getQualifiedName() + SCOPE_SEP + unqualifiedName;
}
}
return qualifiedName;
}
public Indent pushIndentLogger() {
lastUsedIndent = getLastUsedIndent().indent();
return lastUsedIndent;
}
public IndentImpl getLastUsedIndent() {
if (lastUsedIndent == null) {
if (parent != null) {
lastUsedIndent = new IndentImpl(parent.getLastUsedIndent());
} else {
lastUsedIndent = new IndentImpl(null);
}
}
return lastUsedIndent;
}
}