/* * 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 instanceTL = new ThreadLocal<>(); private static final ThreadLocal lastClosedTL = new ThreadLocal<>(); private static final ThreadLocal configTL = new ThreadLocal<>(); private static final ThreadLocal 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 getCurrentContext() { final DebugScope scope = this; return new Iterable() { @Override public Iterator iterator() { return new Iterator() { 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 call(Callable 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; } }