blob: 35261253c3cfcf17ef78c366a677a7c3ab419c85 [file] [log] [blame]
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
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);
}
/*
* Always print the current scope, scopes with context and the any scope whose
* parent didn't print. This ensure the first new scope always shows up.
*/
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;
}
}
/**
* Print or count the context objects for the current scope.
*/
private int printContext(StringBuilder str) {
int count = 0;
if (context != null && context.length > 0) {
// Include some context in the scope output
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;
}
}
}
/**
* Interface for an additional information object per scope. The information object will be
* given to child scopes, but can be explicitly set with
* {@link DebugScope#enhanceWithExtraInfo(CharSequence, ExtraInfo, boolean, Object...)}
*/
public interface ExtraInfo {
}
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 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 DebugScope(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;
}
/**
* Enable dumping at the new {@code dumpLevel} for the remainder of enclosing scopes. This only
* works if a {@link TopLevelDebugConfig} was installed at a higher scope.
*
* @param dumpLevel
*/
public static void setDumpLevel(int dumpLevel) {
TopLevelDebugConfig config = fetchTopLevelDebugConfig("setDebugLevel");
if (config != null) {
config.override(DelegatingDebugConfig.Level.DUMP, dumpLevel);
recursiveUpdateFlags();
}
}
/**
* Enable logging at the new {@code logLevel} for the remainder of enclosing scopes. This only
* works if a {@link TopLevelDebugConfig} was installed at a higher scope.
*
* @param logLevel
*/
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 getExtraInfo() {
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);
}
}
}
}
/**
* This method exists mainly to allow a debugger (e.g., Eclipse) to force dump a graph.
*/
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");
}
}
/**
* @see Debug#verify(Object, String)
*/
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);
}
}
}
}
/**
* Creates and enters a new debug scope which is either a child of the current scope or a
* disjoint top level scope.
*
* @param name the name of the new scope
* @param sandboxConfig the configuration to use for a new top level scope, or null if the new
* scope should be a child scope
* @param newContextObjects objects to be appended to the debug context
* @return the new scope which will be exited when its {@link #close()} method is called
*/
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 enhanceWithExtraInfo(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;
// Be pragmatic: provide a default log stream to prevent a crash if the stream is not
// set while logging
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 createChild(String newName, ExtraInfo newInfo, Object[] newContext) {
return new DebugScope(newName, this, this.scopeId, newInfo, false, newContext);
}
private DebugScope createChild(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;
}
}