| /* |
| * 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; |
| |
| import static java.util.FormattableFlags.LEFT_JUSTIFY; |
| import static java.util.FormattableFlags.UPPERCASE; |
| import static org.graalvm.compiler.debug.DebugOptions.Count; |
| import static org.graalvm.compiler.debug.DebugOptions.Counters; |
| import static org.graalvm.compiler.debug.DebugOptions.Dump; |
| import static org.graalvm.compiler.debug.DebugOptions.DumpOnError; |
| import static org.graalvm.compiler.debug.DebugOptions.DumpOnPhaseChange; |
| import static org.graalvm.compiler.debug.DebugOptions.DumpPath; |
| import static org.graalvm.compiler.debug.DebugOptions.ListMetrics; |
| import static org.graalvm.compiler.debug.DebugOptions.Log; |
| import static org.graalvm.compiler.debug.DebugOptions.MemUseTrackers; |
| import static org.graalvm.compiler.debug.DebugOptions.ShowDumpFiles; |
| import static org.graalvm.compiler.debug.DebugOptions.Time; |
| import static org.graalvm.compiler.debug.DebugOptions.Timers; |
| import static org.graalvm.compiler.debug.DebugOptions.TrackMemUse; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.PrintStream; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.nio.file.StandardOpenOption; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Formatter; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.SortedMap; |
| import java.util.TreeMap; |
| |
| import jdk.internal.vm.compiler.collections.EconomicMap; |
| import jdk.internal.vm.compiler.collections.EconomicSet; |
| import jdk.internal.vm.compiler.collections.Pair; |
| import org.graalvm.compiler.options.OptionKey; |
| import org.graalvm.compiler.options.OptionValues; |
| import org.graalvm.compiler.serviceprovider.GraalServices; |
| import org.graalvm.graphio.GraphOutput; |
| |
| import jdk.vm.ci.meta.JavaMethod; |
| |
| /** |
| * A facility for logging and dumping as well as a container for values associated with |
| * {@link MetricKey}s. |
| * |
| * A {@code DebugContext} object must only be used on the thread that created it. This means it |
| * needs to be passed around as a parameter. For convenience, it can be encapsulated in a widely |
| * used object that is in scope wherever a {@code DebugContext} is needed. However, care must be |
| * taken when such objects can be exposed to multiple threads (e.g., they are in a non-thread-local |
| * cache). |
| */ |
| public final class DebugContext implements AutoCloseable { |
| |
| public static final Description NO_DESCRIPTION = null; |
| public static final GlobalMetrics NO_GLOBAL_METRIC_VALUES = null; |
| public static final Iterable<DebugHandlersFactory> NO_CONFIG_CUSTOMIZERS = Collections.emptyList(); |
| |
| public static final PrintStream DEFAULT_LOG_STREAM = TTY.out; |
| |
| /** |
| * Contains the immutable parts of a debug context. This separation allows the immutable parts |
| * to be shared and reduces the overhead of initialization since most immutable fields are |
| * configured by parsing options. |
| */ |
| final Immutable immutable; |
| |
| /** |
| * Determines whether metrics are enabled. |
| */ |
| boolean metricsEnabled; |
| |
| DebugConfigImpl currentConfig; |
| ScopeImpl currentScope; |
| CloseableCounter currentTimer; |
| CloseableCounter currentMemUseTracker; |
| Scope lastClosedScope; |
| Throwable lastExceptionThrown; |
| private IgvDumpChannel sharedChannel; |
| private GraphOutput<?, ?> parentOutput; |
| |
| /** |
| * Stores the {@link MetricKey} values. |
| */ |
| private long[] metricValues; |
| |
| /** |
| * Determines if dynamic scopes are enabled. |
| */ |
| public boolean areScopesEnabled() { |
| return immutable.scopesEnabled; |
| } |
| |
| public <G, N, M> GraphOutput<G, M> buildOutput(GraphOutput.Builder<G, N, M> builder) throws IOException { |
| if (parentOutput != null) { |
| return builder.build(parentOutput); |
| } else { |
| if (sharedChannel == null) { |
| sharedChannel = new IgvDumpChannel(() -> getDumpPath(".bgv", false), immutable.options); |
| } |
| final GraphOutput<G, M> output = builder.build(sharedChannel); |
| parentOutput = output; |
| return output; |
| } |
| } |
| |
| /** |
| * Adds version properties to the provided map. The version properties are read at a start of |
| * the JVM from a JVM specific location. Each property identifiers a commit of a certain |
| * component in the system. The properties added to the {@code properties} map are prefixed with |
| * {@code "version."} prefix. |
| * |
| * @param properties map to add the version properties to or {@code null} |
| * @return {@code properties} with version properties added or an unmodifiable map containing |
| * the version properties if {@code properties == null} |
| */ |
| public static Map<Object, Object> addVersionProperties(Map<Object, Object> properties) { |
| return Versions.VERSIONS.withVersions(properties); |
| } |
| |
| /** |
| * The immutable configuration that can be shared between {@link DebugContext} objects. |
| */ |
| static final class Immutable { |
| |
| private static final Immutable[] CACHE = new Immutable[5]; |
| |
| /** |
| * The options from which this object was configured. |
| */ |
| final OptionValues options; |
| |
| /** |
| * Specifies if dynamic scopes are enabled. |
| */ |
| final boolean scopesEnabled; |
| |
| final boolean listMetrics; |
| |
| /** |
| * Names of unscoped counters. A counter is unscoped if this set is empty or contains the |
| * counter's name. |
| */ |
| final EconomicSet<String> unscopedCounters; |
| |
| /** |
| * Names of unscoped timers. A timer is unscoped if this set is empty or contains the |
| * timer's name. |
| */ |
| final EconomicSet<String> unscopedTimers; |
| |
| /** |
| * Names of unscoped memory usage trackers. A memory usage tracker is unscoped if this set |
| * is empty or contains the memory usage tracker's name. |
| */ |
| final EconomicSet<String> unscopedMemUseTrackers; |
| |
| private static EconomicSet<String> parseUnscopedMetricSpec(String spec, boolean unconditional, boolean accumulatedKey) { |
| EconomicSet<String> res; |
| if (spec == null) { |
| if (!unconditional) { |
| res = null; |
| } else { |
| res = EconomicSet.create(); |
| } |
| } else { |
| res = EconomicSet.create(); |
| if (!spec.isEmpty()) { |
| if (!accumulatedKey) { |
| res.addAll(Arrays.asList(spec.split(","))); |
| } else { |
| for (String n : spec.split(",")) { |
| res.add(n + AccumulatedKey.ACCUMULATED_KEY_SUFFIX); |
| res.add(n + AccumulatedKey.FLAT_KEY_SUFFIX); |
| } |
| } |
| |
| } |
| } |
| return res; |
| } |
| |
| static Immutable create(OptionValues options) { |
| int i = 0; |
| while (i < CACHE.length) { |
| Immutable immutable = CACHE[i]; |
| if (immutable == null) { |
| break; |
| } |
| if (immutable.options == options) { |
| return immutable; |
| } |
| i++; |
| } |
| Immutable immutable = new Immutable(options); |
| if (i < CACHE.length) { |
| CACHE[i] = immutable; |
| } |
| return immutable; |
| } |
| |
| private static boolean isNotEmpty(OptionKey<String> option, OptionValues options) { |
| return option.getValue(options) != null && !option.getValue(options).isEmpty(); |
| } |
| |
| private Immutable(OptionValues options) { |
| this.options = options; |
| String timeValue = Time.getValue(options); |
| String trackMemUseValue = TrackMemUse.getValue(options); |
| this.unscopedCounters = parseUnscopedMetricSpec(Counters.getValue(options), "".equals(Count.getValue(options)), false); |
| this.unscopedTimers = parseUnscopedMetricSpec(Timers.getValue(options), "".equals(timeValue), true); |
| this.unscopedMemUseTrackers = parseUnscopedMetricSpec(MemUseTrackers.getValue(options), "".equals(trackMemUseValue), true); |
| |
| if (unscopedTimers != null || timeValue != null) { |
| if (!GraalServices.isCurrentThreadCpuTimeSupported()) { |
| throw new IllegalArgumentException("Time and Timers options require VM support for querying CPU time"); |
| } |
| } |
| |
| if (unscopedMemUseTrackers != null || trackMemUseValue != null) { |
| if (!GraalServices.isThreadAllocatedMemorySupported()) { |
| throw new IllegalArgumentException("MemUseTrackers and TrackMemUse options require VM support for querying thread allocated memory"); |
| } |
| } |
| |
| this.scopesEnabled = DumpOnError.getValue(options) || |
| Dump.getValue(options) != null || |
| Log.getValue(options) != null || |
| isNotEmpty(DebugOptions.Count, options) || |
| isNotEmpty(DebugOptions.Time, options) || |
| isNotEmpty(DebugOptions.TrackMemUse, options) || |
| DumpOnPhaseChange.getValue(options) != null; |
| this.listMetrics = ListMetrics.getValue(options); |
| } |
| |
| private Immutable() { |
| this.options = new OptionValues(EconomicMap.create()); |
| this.unscopedCounters = null; |
| this.unscopedTimers = null; |
| this.unscopedMemUseTrackers = null; |
| this.scopesEnabled = false; |
| this.listMetrics = false; |
| } |
| |
| public boolean hasUnscopedMetrics() { |
| return unscopedCounters != null || unscopedTimers != null || unscopedMemUseTrackers != null; |
| } |
| } |
| |
| /** |
| * Gets the options this debug context was constructed with. |
| */ |
| public OptionValues getOptions() { |
| return immutable.options; |
| } |
| |
| static class Activated extends ThreadLocal<DebugContext> { |
| } |
| |
| private static final Activated activated = new Activated(); |
| |
| /** |
| * An object used to undo the changes made by DebugContext#activate(). |
| */ |
| public static class Activation implements AutoCloseable { |
| private final DebugContext parent; |
| |
| Activation(DebugContext parent) { |
| this.parent = parent; |
| } |
| |
| @Override |
| public void close() { |
| activated.set(parent); |
| } |
| } |
| |
| /** |
| * Activates this object as the debug context {@linkplain DebugContext#forCurrentThread for the |
| * current thread}. This method should be used in a try-with-resources statement. |
| * |
| * @return an object that will deactivate the debug context for the current thread when |
| * {@link Activation#close()} is called on it |
| */ |
| public Activation activate() { |
| Activation res = new Activation(activated.get()); |
| activated.set(this); |
| return res; |
| } |
| |
| /** |
| * Shared object used to represent a disabled debug context. |
| */ |
| public static final DebugContext DISABLED = new DebugContext(NO_DESCRIPTION, NO_GLOBAL_METRIC_VALUES, DEFAULT_LOG_STREAM, new Immutable(), NO_CONFIG_CUSTOMIZERS); |
| |
| /** |
| * Gets the debug context for the current thread. This should only be used when there is no |
| * other reasonable means to get a hold of a debug context. |
| */ |
| public static DebugContext forCurrentThread() { |
| DebugContext current = activated.get(); |
| if (current == null) { |
| return DISABLED; |
| } |
| return current; |
| } |
| |
| private final GlobalMetrics globalMetrics; |
| |
| /** |
| * Describes the computation associated with a {@link DebugContext}. |
| */ |
| public static class Description { |
| /** |
| * The primary input to the computation. |
| */ |
| final Object compilable; |
| |
| /** |
| * A runtime based identifier that is most likely to be unique. |
| */ |
| final String identifier; |
| |
| public Description(Object compilable, String identifier) { |
| this.compilable = compilable; |
| this.identifier = identifier; |
| } |
| |
| @Override |
| public String toString() { |
| String compilableName = compilable instanceof JavaMethod ? ((JavaMethod) compilable).format("%H.%n(%p)%R") : String.valueOf(compilable); |
| return identifier + ":" + compilableName; |
| } |
| |
| final String getLabel() { |
| if (compilable instanceof JavaMethod) { |
| JavaMethod method = (JavaMethod) compilable; |
| return method.format("%h.%n(%p)%r"); |
| } |
| return String.valueOf(compilable); |
| } |
| } |
| |
| private final Description description; |
| |
| /** |
| * Gets a description of the computation associated with this debug context. |
| * |
| * @return {@code null} if no description is available |
| */ |
| public Description getDescription() { |
| return description; |
| } |
| |
| /** |
| * Gets the global metrics associated with this debug context. |
| * |
| * @return {@code null} if no global metrics are available |
| */ |
| public GlobalMetrics getGlobalMetrics() { |
| return globalMetrics; |
| } |
| |
| /** |
| * Creates a {@link DebugContext} based on a given set of option values and {@code factory}. |
| */ |
| public static DebugContext create(OptionValues options, DebugHandlersFactory factory) { |
| return new DebugContext(NO_DESCRIPTION, NO_GLOBAL_METRIC_VALUES, DEFAULT_LOG_STREAM, Immutable.create(options), Collections.singletonList(factory)); |
| } |
| |
| /** |
| * Creates a {@link DebugContext} based on a given set of option values and {@code factories}. |
| * The {@link DebugHandlersFactory#LOADER} can be used for the latter. |
| */ |
| public static DebugContext create(OptionValues options, Iterable<DebugHandlersFactory> factories) { |
| return new DebugContext(NO_DESCRIPTION, NO_GLOBAL_METRIC_VALUES, DEFAULT_LOG_STREAM, Immutable.create(options), factories); |
| } |
| |
| /** |
| * Creates a {@link DebugContext}. |
| */ |
| public static DebugContext create(OptionValues options, Description description, GlobalMetrics globalMetrics, PrintStream logStream, Iterable<DebugHandlersFactory> factories) { |
| return new DebugContext(description, globalMetrics, logStream, Immutable.create(options), factories); |
| } |
| |
| private DebugContext(Description description, GlobalMetrics globalMetrics, PrintStream logStream, Immutable immutable, Iterable<DebugHandlersFactory> factories) { |
| this.immutable = immutable; |
| this.description = description; |
| this.globalMetrics = globalMetrics; |
| if (immutable.scopesEnabled) { |
| OptionValues options = immutable.options; |
| List<DebugDumpHandler> dumpHandlers = new ArrayList<>(); |
| List<DebugVerifyHandler> verifyHandlers = new ArrayList<>(); |
| for (DebugHandlersFactory factory : factories) { |
| for (DebugHandler handler : factory.createHandlers(options)) { |
| if (handler instanceof DebugDumpHandler) { |
| dumpHandlers.add((DebugDumpHandler) handler); |
| } else { |
| assert handler instanceof DebugVerifyHandler; |
| verifyHandlers.add((DebugVerifyHandler) handler); |
| } |
| } |
| } |
| currentConfig = new DebugConfigImpl(options, logStream, dumpHandlers, verifyHandlers); |
| currentScope = new ScopeImpl(this, Thread.currentThread()); |
| currentScope.updateFlags(currentConfig); |
| metricsEnabled = true; |
| } else { |
| metricsEnabled = immutable.hasUnscopedMetrics() || immutable.listMetrics; |
| } |
| } |
| |
| public Path getDumpPath(String extension, boolean directory) { |
| try { |
| String id = description == null ? null : description.identifier; |
| String label = description == null ? null : description.getLabel(); |
| Path result = PathUtilities.createUnique(immutable.options, DumpPath, id, label, extension, directory); |
| if (ShowDumpFiles.getValue(immutable.options)) { |
| TTY.println("Dumping debug output to %s", result.toAbsolutePath().toString()); |
| } |
| return result; |
| } catch (IOException ex) { |
| throw rethrowSilently(RuntimeException.class, ex); |
| } |
| } |
| |
| /** |
| * A special dump level that indicates the dumping machinery is enabled but no dumps will be |
| * produced except through other options. |
| */ |
| public static final int ENABLED_LEVEL = 0; |
| |
| /** |
| * Basic debug level. |
| * |
| * For HIR dumping, only ~5 graphs per method: after parsing, after inlining, after high tier, |
| * after mid tier, after low tier. |
| * |
| * LIR dumping: After LIR generation, after each pre-allocation, allocation and post allocation |
| * stage, and after code installation. |
| */ |
| public static final int BASIC_LEVEL = 1; |
| |
| /** |
| * Informational debug level. |
| * |
| * HIR dumping: One graph after each applied top-level phase. |
| * |
| * LIR dumping: After each applied phase. |
| */ |
| public static final int INFO_LEVEL = 2; |
| |
| /** |
| * Verbose debug level. |
| * |
| * HIR dumping: One graph after each phase (including sub phases). |
| * |
| * LIR dumping: After each phase including sub phases. |
| */ |
| public static final int VERBOSE_LEVEL = 3; |
| |
| /** |
| * Detailed debug level. |
| * |
| * HIR dumping: Graphs within phases where interesting for a phase, max ~5 per phase. |
| * |
| * LIR dumping: Dump CFG within phases where interesting. |
| */ |
| public static final int DETAILED_LEVEL = 4; |
| |
| /** |
| * Very detailed debug level. |
| * |
| * HIR dumping: Graphs per node granularity graph change (before/after change). |
| * |
| * LIR dumping: Intermediate CFGs of phases where interesting. |
| */ |
| public static final int VERY_DETAILED_LEVEL = 5; |
| |
| public boolean isDumpEnabled(int dumpLevel) { |
| return currentScope != null && currentScope.isDumpEnabled(dumpLevel); |
| } |
| |
| /** |
| * Determines if verification is enabled for any {@link JavaMethod} in the current scope. |
| * |
| * @see DebugContext#verify(Object, String) |
| */ |
| public boolean isVerifyEnabledForMethod() { |
| if (currentScope == null) { |
| return false; |
| } |
| if (currentConfig == null) { |
| return false; |
| } |
| return currentConfig.isVerifyEnabledForMethod(currentScope); |
| } |
| |
| /** |
| * Determines if verification is enabled in the current scope. |
| * |
| * @see DebugContext#verify(Object, String) |
| */ |
| public boolean isVerifyEnabled() { |
| return currentScope != null && currentScope.isVerifyEnabled(); |
| } |
| |
| public boolean isCountEnabled() { |
| return currentScope != null && currentScope.isCountEnabled(); |
| } |
| |
| public boolean isTimeEnabled() { |
| return currentScope != null && currentScope.isTimeEnabled(); |
| } |
| |
| public boolean isMemUseTrackingEnabled() { |
| return currentScope != null && currentScope.isMemUseTrackingEnabled(); |
| } |
| |
| public boolean isDumpEnabledForMethod() { |
| if (currentConfig == null) { |
| return false; |
| } |
| return currentConfig.isDumpEnabledForMethod(currentScope); |
| } |
| |
| public boolean isLogEnabledForMethod() { |
| if (currentScope == null) { |
| return false; |
| } |
| if (currentConfig == null) { |
| return false; |
| } |
| return currentConfig.isLogEnabledForMethod(currentScope); |
| } |
| |
| public boolean isLogEnabled() { |
| return currentScope != null && isLogEnabled(BASIC_LEVEL); |
| } |
| |
| public boolean isLogEnabled(int logLevel) { |
| return currentScope != null && currentScope.isLogEnabled(logLevel); |
| } |
| |
| /** |
| * Gets a string composed of the names in the current nesting of debug |
| * {@linkplain #scope(Object) scopes} separated by {@code '.'}. |
| */ |
| public String getCurrentScopeName() { |
| if (currentScope != null) { |
| return currentScope.getQualifiedName(); |
| } else { |
| return ""; |
| } |
| } |
| |
| /** |
| * Creates and enters a new debug scope which will be a child of the current debug scope. |
| * <p> |
| * It is recommended to use the try-with-resource statement for managing entering and leaving |
| * debug scopes. For example: |
| * |
| * <pre> |
| * try (Scope s = Debug.scope("InliningGraph", inlineeGraph)) { |
| * ... |
| * } catch (Throwable e) { |
| * throw Debug.handle(e); |
| * } |
| * </pre> |
| * |
| * The {@code name} argument is subject to the following type based conversion before having |
| * {@link Object#toString()} called on it: |
| * |
| * <pre> |
| * Type | Conversion |
| * ------------------+----------------- |
| * java.lang.Class | arg.getSimpleName() |
| * | |
| * </pre> |
| * |
| * @param name the name of the new scope |
| * @param contextObjects an array of object to be appended to the {@linkplain #context() |
| * current} debug context |
| * @throws Throwable used to enforce a catch block. |
| * @return the scope entered by this method which will be exited when its {@link Scope#close()} |
| * method is called |
| */ |
| public DebugContext.Scope scope(Object name, Object[] contextObjects) throws Throwable { |
| if (currentScope != null) { |
| return enterScope(convertFormatArg(name).toString(), null, contextObjects); |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Similar to {@link #scope(Object, Object[])} but without context objects. Therefore the catch |
| * block can be omitted. |
| * |
| * @see #scope(Object, Object[]) |
| */ |
| public DebugContext.Scope scope(Object name) { |
| if (currentScope != null) { |
| return enterScope(convertFormatArg(name).toString(), null); |
| } else { |
| return null; |
| } |
| } |
| |
| private final Invariants invariants = Assertions.assertionsEnabled() ? new Invariants() : null; |
| |
| static StackTraceElement[] getStackTrace(Thread thread) { |
| return thread.getStackTrace(); |
| } |
| |
| /** |
| * Utility for enforcing {@link DebugContext} invariants via assertions. |
| */ |
| static class Invariants { |
| private final Thread thread; |
| private final StackTraceElement[] origin; |
| |
| Invariants() { |
| thread = Thread.currentThread(); |
| origin = getStackTrace(thread); |
| } |
| |
| boolean checkNoConcurrentAccess() { |
| Thread currentThread = Thread.currentThread(); |
| if (currentThread != thread) { |
| Formatter buf = new Formatter(); |
| buf.format("Thread local %s object was created on thread %s but is being accessed by thread %s. The most likely cause is " + |
| "that the object is being retrieved from a non-thread-local cache.", |
| DebugContext.class.getName(), thread, currentThread); |
| int debugContextConstructors = 0; |
| boolean addedHeader = false; |
| for (StackTraceElement e : origin) { |
| if (e.getMethodName().equals("<init>") && e.getClassName().equals(DebugContext.class.getName())) { |
| debugContextConstructors++; |
| } else if (debugContextConstructors != 0) { |
| if (!addedHeader) { |
| addedHeader = true; |
| buf.format(" The object was instantiated here:"); |
| } |
| // Distinguish from assertion stack trace by using double indent and |
| // "in" instead of "at" prefix. |
| buf.format("%n\t\tin %s", e); |
| } |
| } |
| if (addedHeader) { |
| buf.format("%n"); |
| } |
| |
| throw new AssertionError(buf.toString()); |
| } |
| return true; |
| } |
| } |
| |
| boolean checkNoConcurrentAccess() { |
| assert invariants == null || invariants.checkNoConcurrentAccess(); |
| return true; |
| } |
| |
| private DebugContext.Scope enterScope(CharSequence name, DebugConfig sandboxConfig, Object... newContextObjects) { |
| assert checkNoConcurrentAccess(); |
| currentScope = currentScope.scope(name, sandboxConfig, newContextObjects); |
| return currentScope; |
| } |
| |
| /** |
| * @see #scope(Object, Object[]) |
| * @param context an object to be appended to the {@linkplain #context() current} debug context |
| */ |
| public DebugContext.Scope scope(Object name, Object context) throws Throwable { |
| if (currentScope != null) { |
| return enterScope(convertFormatArg(name).toString(), null, context); |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * @see #scope(Object, Object[]) |
| * @param context1 first object to be appended to the {@linkplain #context() current} debug |
| * context |
| * @param context2 second object to be appended to the {@linkplain #context() current} debug |
| * context |
| */ |
| public DebugContext.Scope scope(Object name, Object context1, Object context2) throws Throwable { |
| if (currentScope != null) { |
| return enterScope(convertFormatArg(name).toString(), null, context1, context2); |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * @see #scope(Object, Object[]) |
| * @param context1 first object to be appended to the {@linkplain #context() current} debug |
| * context |
| * @param context2 second object to be appended to the {@linkplain #context() current} debug |
| * context |
| * @param context3 third object to be appended to the {@linkplain #context() current} debug |
| * context |
| */ |
| public DebugContext.Scope scope(Object name, Object context1, Object context2, Object context3) throws Throwable { |
| if (currentScope != null) { |
| return enterScope(convertFormatArg(name).toString(), null, context1, context2, context3); |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Create an unnamed scope that appends some context to the current scope. |
| * |
| * @param context an object to be appended to the {@linkplain #context() current} debug context |
| */ |
| public DebugContext.Scope withContext(Object context) throws Throwable { |
| if (currentScope != null) { |
| return enterScope("", null, context); |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Creates and enters a new debug scope which will be disjoint from the current debug scope. |
| * <p> |
| * It is recommended to use the try-with-resource statement for managing entering and leaving |
| * debug scopes. For example: |
| * |
| * <pre> |
| * try (Scope s = Debug.sandbox("CompilingStub", null, stubGraph)) { |
| * ... |
| * } catch (Throwable e) { |
| * throw Debug.handle(e); |
| * } |
| * </pre> |
| * |
| * @param name the name of the new scope |
| * @param config the debug configuration to use for the new scope or {@code null} to disable the |
| * scoping mechanism within the sandbox scope |
| * @param context objects to be appended to the {@linkplain #context() current} debug context |
| * @return the scope entered by this method which will be exited when its {@link Scope#close()} |
| * method is called |
| */ |
| public DebugContext.Scope sandbox(CharSequence name, DebugConfig config, Object... context) throws Throwable { |
| if (config == null) { |
| return disable(); |
| } |
| if (currentScope != null) { |
| return enterScope(name, config, context); |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Determines if scopes are enabled and this context is in a non-top-level scope. |
| */ |
| public boolean inNestedScope() { |
| if (immutable.scopesEnabled) { |
| if (currentScope == null) { |
| // In an active DisabledScope |
| return true; |
| } |
| return !currentScope.isTopLevel(); |
| } |
| return immutable.scopesEnabled && currentScope == null; |
| } |
| |
| class DisabledScope implements DebugContext.Scope { |
| final boolean savedMetricsEnabled; |
| final ScopeImpl savedScope; |
| final DebugConfigImpl savedConfig; |
| |
| DisabledScope() { |
| this.savedMetricsEnabled = metricsEnabled; |
| this.savedScope = currentScope; |
| this.savedConfig = currentConfig; |
| metricsEnabled = false; |
| currentScope = null; |
| currentConfig = null; |
| } |
| |
| @Override |
| public String getQualifiedName() { |
| return ""; |
| } |
| |
| @Override |
| public Iterable<Object> getCurrentContext() { |
| return Collections.emptyList(); |
| } |
| |
| @Override |
| public void close() { |
| metricsEnabled = savedMetricsEnabled; |
| currentScope = savedScope; |
| currentConfig = savedConfig; |
| lastClosedScope = this; |
| } |
| } |
| |
| /** |
| * Disables all metrics and scope related functionality until {@code close()} is called on the |
| * returned object. |
| */ |
| public DebugContext.Scope disable() { |
| if (currentScope != null) { |
| return new DisabledScope(); |
| } else { |
| return null; |
| } |
| } |
| |
| public DebugContext.Scope forceLog() throws Throwable { |
| if (currentConfig != null) { |
| ArrayList<Object> context = new ArrayList<>(); |
| for (Object obj : context()) { |
| context.add(obj); |
| } |
| DebugConfigImpl config = new DebugConfigImpl(new OptionValues(currentConfig.getOptions(), DebugOptions.Log, ":1000")); |
| return sandbox("forceLog", config, context.toArray()); |
| } |
| return null; |
| } |
| |
| /** |
| * Opens a scope in which exception |
| * {@linkplain DebugConfig#interceptException(DebugContext, Throwable) interception} is |
| * disabled. The current state of interception is restored when {@link DebugCloseable#close()} |
| * is called on the returned object. |
| * |
| * This is particularly useful to suppress extraneous output in JUnit tests that are expected to |
| * throw an exception. |
| */ |
| public DebugCloseable disableIntercept() { |
| if (currentScope != null) { |
| return currentScope.disableIntercept(); |
| } |
| return null; |
| } |
| |
| /** |
| * Handles an exception in the context of the debug scope just exited. The just exited scope |
| * must have the current scope as its parent which will be the case if the try-with-resource |
| * pattern recommended by {@link #scope(Object)} and |
| * {@link #sandbox(CharSequence, DebugConfig, Object...)} is used |
| * |
| * @see #scope(Object, Object[]) |
| * @see #sandbox(CharSequence, DebugConfig, Object...) |
| */ |
| public RuntimeException handle(Throwable exception) { |
| if (currentScope != null) { |
| return currentScope.handle(exception); |
| } else { |
| if (exception instanceof Error) { |
| throw (Error) exception; |
| } |
| if (exception instanceof RuntimeException) { |
| throw (RuntimeException) exception; |
| } |
| throw new RuntimeException(exception); |
| } |
| } |
| |
| public void log(String msg) { |
| log(BASIC_LEVEL, msg); |
| } |
| |
| /** |
| * Prints a message to the current debug scope's logging stream if logging is enabled. |
| * |
| * @param msg the message to log |
| */ |
| public void log(int logLevel, String msg) { |
| if (currentScope != null) { |
| currentScope.log(logLevel, msg); |
| } |
| } |
| |
| public void log(String format, Object arg) { |
| log(BASIC_LEVEL, format, arg); |
| } |
| |
| /** |
| * Prints a message to the current debug scope's logging stream if logging is enabled. |
| * |
| * @param format a format string |
| * @param arg the argument referenced by the format specifiers in {@code format} |
| */ |
| public void log(int logLevel, String format, Object arg) { |
| if (currentScope != null) { |
| currentScope.log(logLevel, format, arg); |
| } |
| } |
| |
| public void log(String format, int arg) { |
| log(BASIC_LEVEL, format, arg); |
| } |
| |
| /** |
| * Prints a message to the current debug scope's logging stream if logging is enabled. |
| * |
| * @param format a format string |
| * @param arg the argument referenced by the format specifiers in {@code format} |
| */ |
| public void log(int logLevel, String format, int arg) { |
| if (currentScope != null) { |
| currentScope.log(logLevel, format, arg); |
| } |
| } |
| |
| public void log(String format, Object arg1, Object arg2) { |
| log(BASIC_LEVEL, format, arg1, arg2); |
| } |
| |
| /** |
| * @see #log(int, String, Object) |
| */ |
| public void log(int logLevel, String format, Object arg1, Object arg2) { |
| if (currentScope != null) { |
| currentScope.log(logLevel, format, arg1, arg2); |
| } |
| } |
| |
| public void log(String format, int arg1, Object arg2) { |
| log(BASIC_LEVEL, format, arg1, arg2); |
| } |
| |
| /** |
| * @see #log(int, String, Object) |
| */ |
| public void log(int logLevel, String format, int arg1, Object arg2) { |
| if (currentScope != null) { |
| currentScope.log(logLevel, format, arg1, arg2); |
| } |
| } |
| |
| public void log(String format, Object arg1, int arg2) { |
| log(BASIC_LEVEL, format, arg1, arg2); |
| } |
| |
| /** |
| * @see #log(int, String, Object) |
| */ |
| public void log(int logLevel, String format, Object arg1, int arg2) { |
| if (currentScope != null) { |
| currentScope.log(logLevel, format, arg1, arg2); |
| } |
| } |
| |
| public void log(String format, int arg1, int arg2) { |
| log(BASIC_LEVEL, format, arg1, arg2); |
| } |
| |
| /** |
| * @see #log(int, String, Object) |
| */ |
| public void log(int logLevel, String format, int arg1, int arg2) { |
| if (currentScope != null) { |
| currentScope.log(logLevel, format, arg1, arg2); |
| } |
| } |
| |
| public void log(String format, Object arg1, Object arg2, Object arg3) { |
| log(BASIC_LEVEL, format, arg1, arg2, arg3); |
| } |
| |
| /** |
| * @see #log(int, String, Object) |
| */ |
| public void log(int logLevel, String format, Object arg1, Object arg2, Object arg3) { |
| if (currentScope != null) { |
| currentScope.log(logLevel, format, arg1, arg2, arg3); |
| } |
| } |
| |
| public void log(String format, int arg1, int arg2, int arg3) { |
| log(BASIC_LEVEL, format, arg1, arg2, arg3); |
| } |
| |
| /** |
| * @see #log(int, String, Object) |
| */ |
| public void log(int logLevel, String format, int arg1, int arg2, int arg3) { |
| if (currentScope != null) { |
| currentScope.log(logLevel, format, arg1, arg2, arg3); |
| } |
| } |
| |
| public void log(String format, Object arg1, Object arg2, Object arg3, Object arg4) { |
| log(BASIC_LEVEL, format, arg1, arg2, arg3, arg4); |
| } |
| |
| /** |
| * @see #log(int, String, Object) |
| */ |
| public void log(int logLevel, String format, Object arg1, Object arg2, Object arg3, Object arg4) { |
| if (currentScope != null) { |
| currentScope.log(logLevel, format, arg1, arg2, arg3, arg4); |
| } |
| } |
| |
| public void log(String format, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5) { |
| log(BASIC_LEVEL, format, arg1, arg2, arg3, arg4, arg5); |
| } |
| |
| /** |
| * @see #log(int, String, Object) |
| */ |
| public void log(int logLevel, String format, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5) { |
| if (currentScope != null) { |
| currentScope.log(logLevel, format, arg1, arg2, arg3, arg4, arg5); |
| } |
| } |
| |
| public void log(String format, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6) { |
| log(BASIC_LEVEL, format, arg1, arg2, arg3, arg4, arg5, arg6); |
| } |
| |
| /** |
| * @see #log(int, String, Object) |
| */ |
| public void log(int logLevel, String format, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6) { |
| if (currentScope != null) { |
| currentScope.log(logLevel, format, arg1, arg2, arg3, arg4, arg5, arg6); |
| } |
| } |
| |
| public void log(String format, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7) { |
| log(BASIC_LEVEL, format, arg1, arg2, arg3, arg4, arg5, arg6, arg7); |
| } |
| |
| public void log(String format, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7, Object arg8) { |
| log(BASIC_LEVEL, format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); |
| } |
| |
| /** |
| * @see #log(int, String, Object) |
| */ |
| public void log(int logLevel, String format, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7) { |
| if (currentScope != null) { |
| currentScope.log(logLevel, format, arg1, arg2, arg3, arg4, arg5, arg6, arg7); |
| } |
| } |
| |
| public void log(int logLevel, String format, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7, Object arg8) { |
| if (currentScope != null) { |
| currentScope.log(logLevel, format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); |
| } |
| } |
| |
| public void log(String format, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7, Object arg8, Object arg9) { |
| log(BASIC_LEVEL, format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); |
| } |
| |
| public void log(int logLevel, String format, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7, Object arg8, Object arg9) { |
| if (currentScope != null) { |
| currentScope.log(logLevel, format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); |
| } |
| } |
| |
| public void log(String format, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7, Object arg8, Object arg9, Object arg10) { |
| log(BASIC_LEVEL, format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10); |
| } |
| |
| public void log(int logLevel, String format, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7, Object arg8, Object arg9, Object arg10) { |
| if (currentScope != null) { |
| currentScope.log(logLevel, format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10); |
| } |
| } |
| |
| public void logv(String format, Object... args) { |
| logv(BASIC_LEVEL, format, args); |
| } |
| |
| /** |
| * Prints a message to the current debug scope's logging stream. This method must only be called |
| * if debugging scopes are {@linkplain DebugContext#areScopesEnabled() enabled} as it incurs |
| * allocation at the call site. If possible, call one of the other {@code log()} methods in this |
| * class that take a fixed number of parameters. |
| * |
| * @param format a format string |
| * @param args the arguments referenced by the format specifiers in {@code format} |
| */ |
| public void logv(int logLevel, String format, Object... args) { |
| if (currentScope == null) { |
| throw new InternalError("Use of Debug.logv() must be guarded by a test of Debug.isEnabled()"); |
| } |
| currentScope.log(logLevel, format, args); |
| } |
| |
| /** |
| * This override exists to catch cases when {@link #log(String, Object)} is called with one |
| * argument bound to a varargs method parameter. It will bind to this method instead of the |
| * single arg variant and produce a deprecation warning instead of silently wrapping the |
| * Object[] inside of another Object[]. |
| */ |
| @Deprecated |
| public void log(String format, Object[] args) { |
| assert false : "shouldn't use this"; |
| log(BASIC_LEVEL, format, args); |
| } |
| |
| /** |
| * This override exists to catch cases when {@link #log(int, String, Object)} is called with one |
| * argument bound to a varargs method parameter. It will bind to this method instead of the |
| * single arg variant and produce a deprecation warning instead of silently wrapping the |
| * Object[] inside of another Object[]. |
| */ |
| @Deprecated |
| public void log(int logLevel, String format, Object[] args) { |
| assert false : "shouldn't use this"; |
| logv(logLevel, format, args); |
| } |
| |
| /** |
| * Forces an unconditional dump. This method exists mainly for debugging. It can also be used to |
| * force a graph dump from IDEs that support invoking a Java method while at a breakpoint. |
| */ |
| public void forceDump(Object object, String format, Object... args) { |
| DebugConfig config = currentConfig; |
| Collection<DebugDumpHandler> dumpHandlers; |
| boolean closeAfterDump; |
| if (config != null) { |
| dumpHandlers = config.dumpHandlers(); |
| closeAfterDump = false; |
| } else { |
| OptionValues options = getOptions(); |
| dumpHandlers = new ArrayList<>(); |
| for (DebugHandlersFactory factory : DebugHandlersFactory.LOADER) { |
| for (DebugHandler handler : factory.createHandlers(options)) { |
| if (handler instanceof DebugDumpHandler) { |
| dumpHandlers.add((DebugDumpHandler) handler); |
| } |
| } |
| } |
| closeAfterDump = true; |
| } |
| for (DebugDumpHandler dumpHandler : dumpHandlers) { |
| dumpHandler.dump(this, object, format, args); |
| if (closeAfterDump) { |
| dumpHandler.close(); |
| } |
| } |
| } |
| |
| public void dump(int dumpLevel, Object object, String msg) { |
| if (currentScope != null && currentScope.isDumpEnabled(dumpLevel)) { |
| currentScope.dump(dumpLevel, object, msg); |
| } |
| } |
| |
| public void dump(int dumpLevel, Object object, String format, Object arg) { |
| if (currentScope != null && currentScope.isDumpEnabled(dumpLevel)) { |
| currentScope.dump(dumpLevel, object, format, arg); |
| } |
| } |
| |
| public void dump(int dumpLevel, Object object, String format, Object arg1, Object arg2) { |
| if (currentScope != null && currentScope.isDumpEnabled(dumpLevel)) { |
| currentScope.dump(dumpLevel, object, format, arg1, arg2); |
| } |
| } |
| |
| public void dump(int dumpLevel, Object object, String format, Object arg1, Object arg2, Object arg3) { |
| if (currentScope != null && currentScope.isDumpEnabled(dumpLevel)) { |
| currentScope.dump(dumpLevel, object, format, arg1, arg2, arg3); |
| } |
| } |
| |
| /** |
| * This override exists to catch cases when {@link #dump(int, Object, String, Object)} is called |
| * with one argument bound to a varargs method parameter. It will bind to this method instead of |
| * the single arg variant and produce a deprecation warning instead of silently wrapping the |
| * Object[] inside of another Object[]. |
| */ |
| @Deprecated |
| public void dump(int dumpLevel, Object object, String format, Object[] args) { |
| assert false : "shouldn't use this"; |
| if (currentScope != null && currentScope.isDumpEnabled(dumpLevel)) { |
| currentScope.dump(dumpLevel, object, format, args); |
| } |
| } |
| |
| /** |
| * Calls all {@link DebugVerifyHandler}s in the current {@linkplain #getConfig() config} to |
| * perform verification on a given object. |
| * |
| * @param object object to verify |
| * @param message description of verification context |
| * |
| * @see DebugVerifyHandler#verify |
| */ |
| public void verify(Object object, String message) { |
| if (currentScope != null && currentScope.isVerifyEnabled()) { |
| currentScope.verify(object, message); |
| } |
| } |
| |
| /** |
| * Calls all {@link DebugVerifyHandler}s in the current {@linkplain #getConfig() config} to |
| * perform verification on a given object. |
| * |
| * @param object object to verify |
| * @param format a format string for the description of the verification context |
| * @param arg the argument referenced by the format specifiers in {@code format} |
| * |
| * @see DebugVerifyHandler#verify |
| */ |
| public void verify(Object object, String format, Object arg) { |
| if (currentScope != null && currentScope.isVerifyEnabled()) { |
| currentScope.verify(object, format, arg); |
| } |
| } |
| |
| /** |
| * This override exists to catch cases when {@link #verify(Object, String, Object)} is called |
| * with one argument bound to a varargs method parameter. It will bind to this method instead of |
| * the single arg variant and produce a deprecation warning instead of silently wrapping the |
| * Object[] inside of another Object[]. |
| */ |
| @Deprecated |
| public void verify(Object object, String format, Object[] args) { |
| assert false : "shouldn't use this"; |
| if (currentScope != null && currentScope.isVerifyEnabled()) { |
| currentScope.verify(object, format, args); |
| } |
| } |
| |
| /** |
| * Opens a new indentation level (by adding some spaces) based on the current indentation level. |
| * This should be used in a {@linkplain Indent try-with-resources} pattern. |
| * |
| * @return an object that reverts to the current indentation level when |
| * {@linkplain Indent#close() closed} or null if debugging is disabled |
| * @see #logAndIndent(int, String) |
| * @see #logAndIndent(int, String, Object) |
| */ |
| public Indent indent() { |
| if (currentScope != null) { |
| return currentScope.pushIndentLogger(); |
| } |
| return null; |
| } |
| |
| public Indent logAndIndent(String msg) { |
| return logAndIndent(BASIC_LEVEL, msg); |
| } |
| |
| /** |
| * A convenience function which combines {@link #log(String)} and {@link #indent()}. |
| * |
| * @param msg the message to log |
| * @return an object that reverts to the current indentation level when |
| * {@linkplain Indent#close() closed} or null if debugging is disabled |
| */ |
| public Indent logAndIndent(int logLevel, String msg) { |
| if (currentScope != null && isLogEnabled(logLevel)) { |
| return logvAndIndentInternal(logLevel, msg); |
| } |
| return null; |
| } |
| |
| public Indent logAndIndent(String format, Object arg) { |
| return logAndIndent(BASIC_LEVEL, format, arg); |
| } |
| |
| /** |
| * A convenience function which combines {@link #log(String, Object)} and {@link #indent()}. |
| * |
| * @param format a format string |
| * @param arg the argument referenced by the format specifiers in {@code format} |
| * @return an object that reverts to the current indentation level when |
| * {@linkplain Indent#close() closed} or null if debugging is disabled |
| */ |
| public Indent logAndIndent(int logLevel, String format, Object arg) { |
| if (currentScope != null && isLogEnabled(logLevel)) { |
| return logvAndIndentInternal(logLevel, format, arg); |
| } |
| return null; |
| } |
| |
| public Indent logAndIndent(String format, int arg) { |
| return logAndIndent(BASIC_LEVEL, format, arg); |
| } |
| |
| /** |
| * A convenience function which combines {@link #log(String, Object)} and {@link #indent()}. |
| * |
| * @param format a format string |
| * @param arg the argument referenced by the format specifiers in {@code format} |
| * @return an object that reverts to the current indentation level when |
| * {@linkplain Indent#close() closed} or null if debugging is disabled |
| */ |
| public Indent logAndIndent(int logLevel, String format, int arg) { |
| if (currentScope != null && isLogEnabled(logLevel)) { |
| return logvAndIndentInternal(logLevel, format, arg); |
| } |
| return null; |
| } |
| |
| public Indent logAndIndent(String format, int arg1, Object arg2) { |
| return logAndIndent(BASIC_LEVEL, format, arg1, arg2); |
| } |
| |
| /** |
| * @see #logAndIndent(int, String, Object) |
| */ |
| public Indent logAndIndent(int logLevel, String format, int arg1, Object arg2) { |
| if (currentScope != null && isLogEnabled(logLevel)) { |
| return logvAndIndentInternal(logLevel, format, arg1, arg2); |
| } |
| return null; |
| } |
| |
| public Indent logAndIndent(String format, Object arg1, int arg2) { |
| return logAndIndent(BASIC_LEVEL, format, arg1, arg2); |
| } |
| |
| /** |
| * @see #logAndIndent(int, String, Object) |
| */ |
| public Indent logAndIndent(int logLevel, String format, Object arg1, int arg2) { |
| if (currentScope != null && isLogEnabled(logLevel)) { |
| return logvAndIndentInternal(logLevel, format, arg1, arg2); |
| } |
| return null; |
| } |
| |
| public Indent logAndIndent(String format, int arg1, int arg2) { |
| return logAndIndent(BASIC_LEVEL, format, arg1, arg2); |
| } |
| |
| /** |
| * @see #logAndIndent(int, String, Object) |
| */ |
| public Indent logAndIndent(int logLevel, String format, int arg1, int arg2) { |
| if (currentScope != null && isLogEnabled(logLevel)) { |
| return logvAndIndentInternal(logLevel, format, arg1, arg2); |
| } |
| return null; |
| } |
| |
| public Indent logAndIndent(String format, Object arg1, Object arg2) { |
| return logAndIndent(BASIC_LEVEL, format, arg1, arg2); |
| } |
| |
| /** |
| * @see #logAndIndent(int, String, Object) |
| */ |
| public Indent logAndIndent(int logLevel, String format, Object arg1, Object arg2) { |
| if (currentScope != null && isLogEnabled(logLevel)) { |
| return logvAndIndentInternal(logLevel, format, arg1, arg2); |
| } |
| return null; |
| } |
| |
| public Indent logAndIndent(String format, Object arg1, Object arg2, Object arg3) { |
| return logAndIndent(BASIC_LEVEL, format, arg1, arg2, arg3); |
| } |
| |
| /** |
| * @see #logAndIndent(int, String, Object) |
| */ |
| public Indent logAndIndent(int logLevel, String format, Object arg1, Object arg2, Object arg3) { |
| if (currentScope != null && isLogEnabled(logLevel)) { |
| return logvAndIndentInternal(logLevel, format, arg1, arg2, arg3); |
| } |
| return null; |
| } |
| |
| public Indent logAndIndent(String format, int arg1, int arg2, int arg3) { |
| return logAndIndent(BASIC_LEVEL, format, arg1, arg2, arg3); |
| } |
| |
| /** |
| * @see #logAndIndent(int, String, Object) |
| */ |
| public Indent logAndIndent(int logLevel, String format, int arg1, int arg2, int arg3) { |
| if (currentScope != null && isLogEnabled(logLevel)) { |
| return logvAndIndentInternal(logLevel, format, arg1, arg2, arg3); |
| } |
| return null; |
| } |
| |
| public Indent logAndIndent(String format, Object arg1, int arg2, int arg3) { |
| return logAndIndent(BASIC_LEVEL, format, arg1, arg2, arg3); |
| } |
| |
| /** |
| * @see #logAndIndent(int, String, Object) |
| */ |
| public Indent logAndIndent(int logLevel, String format, Object arg1, int arg2, int arg3) { |
| if (currentScope != null && isLogEnabled(logLevel)) { |
| return logvAndIndentInternal(logLevel, format, arg1, arg2, arg3); |
| } |
| return null; |
| } |
| |
| public Indent logAndIndent(String format, Object arg1, Object arg2, Object arg3, Object arg4) { |
| return logAndIndent(BASIC_LEVEL, format, arg1, arg2, arg3, arg4); |
| } |
| |
| /** |
| * @see #logAndIndent(int, String, Object) |
| */ |
| public Indent logAndIndent(int logLevel, String format, Object arg1, Object arg2, Object arg3, Object arg4) { |
| if (currentScope != null && isLogEnabled(logLevel)) { |
| return logvAndIndentInternal(logLevel, format, arg1, arg2, arg3, arg4); |
| } |
| return null; |
| } |
| |
| public Indent logAndIndent(String format, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5) { |
| return logAndIndent(BASIC_LEVEL, format, arg1, arg2, arg3, arg4, arg5); |
| } |
| |
| /** |
| * @see #logAndIndent(int, String, Object) |
| */ |
| public Indent logAndIndent(int logLevel, String format, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5) { |
| if (currentScope != null && isLogEnabled(logLevel)) { |
| return logvAndIndentInternal(logLevel, format, arg1, arg2, arg3, arg4, arg5); |
| } |
| return null; |
| } |
| |
| public Indent logAndIndent(String format, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6) { |
| return logAndIndent(BASIC_LEVEL, format, arg1, arg2, arg3, arg4, arg5, arg6); |
| } |
| |
| /** |
| * @see #logAndIndent(int, String, Object) |
| */ |
| public Indent logAndIndent(int logLevel, String format, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6) { |
| if (currentScope != null && isLogEnabled(logLevel)) { |
| return logvAndIndentInternal(logLevel, format, arg1, arg2, arg3, arg4, arg5, arg6); |
| } |
| return null; |
| } |
| |
| /** |
| * A convenience function which combines {@link #logv(int, String, Object...)} and |
| * {@link #indent()}. |
| * |
| * @param format a format string |
| * @param args the arguments referenced by the format specifiers in {@code format} |
| * @return an object that reverts to the current indentation level when |
| * {@linkplain Indent#close() closed} or null if debugging is disabled |
| */ |
| public Indent logvAndIndent(int logLevel, String format, Object... args) { |
| if (currentScope != null) { |
| if (isLogEnabled(logLevel)) { |
| return logvAndIndentInternal(logLevel, format, args); |
| } |
| return null; |
| } |
| throw new InternalError("Use of Debug.logvAndIndent() must be guarded by a test of Debug.isEnabled()"); |
| } |
| |
| private Indent logvAndIndentInternal(int logLevel, String format, Object... args) { |
| assert currentScope != null && isLogEnabled(logLevel) : "must have checked Debug.isLogEnabled()"; |
| currentScope.log(logLevel, format, args); |
| return currentScope.pushIndentLogger(); |
| } |
| |
| /** |
| * This override exists to catch cases when {@link #logAndIndent(String, Object)} is called with |
| * one argument bound to a varargs method parameter. It will bind to this method instead of the |
| * single arg variant and produce a deprecation warning instead of silently wrapping the |
| * Object[] inside of another Object[]. |
| */ |
| @Deprecated |
| public void logAndIndent(String format, Object[] args) { |
| assert false : "shouldn't use this"; |
| logAndIndent(BASIC_LEVEL, format, args); |
| } |
| |
| /** |
| * This override exists to catch cases when {@link #logAndIndent(int, String, Object)} is called |
| * with one argument bound to a varargs method parameter. It will bind to this method instead of |
| * the single arg variant and produce a deprecation warning instead of silently wrapping the |
| * Object[] inside of another Object[]. |
| */ |
| @Deprecated |
| public void logAndIndent(int logLevel, String format, Object[] args) { |
| assert false : "shouldn't use this"; |
| logvAndIndent(logLevel, format, args); |
| } |
| |
| public Iterable<Object> context() { |
| if (currentScope != null) { |
| return currentScope.getCurrentContext(); |
| } else { |
| return Collections.emptyList(); |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| public <T> List<T> contextSnapshot(Class<T> clazz) { |
| if (currentScope != null) { |
| List<T> result = new ArrayList<>(); |
| for (Object o : context()) { |
| if (clazz.isInstance(o)) { |
| result.add((T) o); |
| } |
| } |
| return result; |
| } else { |
| return Collections.emptyList(); |
| } |
| } |
| |
| /** |
| * Searches the current debug scope, bottom up, for a context object that is an instance of a |
| * given type. The first such object found is returned. |
| */ |
| @SuppressWarnings("unchecked") |
| public <T> T contextLookup(Class<T> clazz) { |
| if (currentScope != null) { |
| for (Object o : context()) { |
| if (clazz.isInstance(o)) { |
| return ((T) o); |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Searches the current debug scope, top down, for a context object that is an instance of a |
| * given type. The first such object found is returned. |
| */ |
| @SuppressWarnings("unchecked") |
| public <T> T contextLookupTopdown(Class<T> clazz) { |
| if (currentScope != null) { |
| T found = null; |
| for (Object o : context()) { |
| if (clazz.isInstance(o)) { |
| found = (T) o; |
| } |
| } |
| return found; |
| } |
| return null; |
| } |
| |
| /** |
| * Creates a {@linkplain MemUseTrackerKey memory use tracker}. |
| */ |
| public static MemUseTrackerKey memUseTracker(CharSequence name) { |
| return createMemUseTracker("%s", name, null); |
| } |
| |
| /** |
| * Creates a debug memory use tracker. Invoking this method is equivalent to: |
| * |
| * <pre> |
| * Debug.memUseTracker(format, arg, null) |
| * </pre> |
| * |
| * except that the string formatting only happens if mem tracking is enabled. |
| * |
| * @see #counter(String, Object, Object) |
| */ |
| public static MemUseTrackerKey memUseTracker(String format, Object arg) { |
| return createMemUseTracker(format, arg, null); |
| } |
| |
| /** |
| * Creates a debug memory use tracker. Invoking this method is equivalent to: |
| * |
| * <pre> |
| * Debug.memUseTracker(String.format(format, arg1, arg2)) |
| * </pre> |
| * |
| * except that the string formatting only happens if memory use tracking is enabled. In |
| * addition, each argument is subject to the following type based conversion before being passed |
| * as an argument to {@link String#format(String, Object...)}: |
| * |
| * <pre> |
| * Type | Conversion |
| * ------------------+----------------- |
| * java.lang.Class | arg.getSimpleName() |
| * | |
| * </pre> |
| * |
| * @see #memUseTracker(CharSequence) |
| */ |
| public static MemUseTrackerKey memUseTracker(String format, Object arg1, Object arg2) { |
| return createMemUseTracker(format, arg1, arg2); |
| } |
| |
| private static MemUseTrackerKey createMemUseTracker(String format, Object arg1, Object arg2) { |
| return new MemUseTrackerKeyImpl(format, arg1, arg2); |
| } |
| |
| /** |
| * Creates a {@linkplain CounterKey counter}. |
| */ |
| public static CounterKey counter(CharSequence name) { |
| return createCounter("%s", name, null); |
| } |
| |
| /** |
| * Gets a tally of the metric values in this context and a given tally. |
| * |
| * @param tally the tally to which the metrics should be added |
| * @return a tally of the metric values in this context and {@code tally}. This will be |
| * {@code tally} if this context has no metric values or {@code tally} is wide enough to |
| * hold all the metric values in this context otherwise it will be a new array. |
| */ |
| public long[] addValuesTo(long[] tally) { |
| if (metricValues == null) { |
| return tally; |
| } |
| if (tally == null) { |
| return metricValues.clone(); |
| } else if (metricValues.length >= tally.length) { |
| long[] newTally = metricValues.clone(); |
| for (int i = 0; i < tally.length; i++) { |
| newTally[i] += tally[i]; |
| } |
| return newTally; |
| } else { |
| for (int i = 0; i < metricValues.length; i++) { |
| tally[i] += metricValues[i]; |
| } |
| return tally; |
| } |
| } |
| |
| /** |
| * Creates and returns a sorted map from metric names to their values in {@code values}. |
| * |
| * @param values values for metrics in the {@link KeyRegistry}. |
| */ |
| public static EconomicMap<MetricKey, Long> convertValuesToKeyValueMap(long[] values) { |
| List<MetricKey> keys = KeyRegistry.getKeys(); |
| Collections.sort(keys, MetricKey.NAME_COMPARATOR); |
| EconomicMap<MetricKey, Long> res = EconomicMap.create(keys.size()); |
| for (MetricKey key : keys) { |
| int index = ((AbstractKey) key).getIndex(); |
| if (index >= values.length) { |
| res.put(key, 0L); |
| } else { |
| res.put(key, values[index]); |
| } |
| } |
| return res; |
| } |
| |
| void setMetricValue(int keyIndex, long l) { |
| ensureMetricValuesSize(keyIndex); |
| metricValues[keyIndex] = l; |
| } |
| |
| long getMetricValue(int keyIndex) { |
| if (metricValues == null || metricValues.length <= keyIndex) { |
| return 0L; |
| } |
| return metricValues[keyIndex]; |
| } |
| |
| private void ensureMetricValuesSize(int index) { |
| if (metricValues == null) { |
| metricValues = new long[index + 1]; |
| } |
| if (metricValues.length <= index) { |
| metricValues = Arrays.copyOf(metricValues, index + 1); |
| } |
| } |
| |
| public static String applyFormattingFlagsAndWidth(String s, int flags, int width) { |
| if (flags == 0 && width < 0) { |
| return s; |
| } |
| StringBuilder sb = new StringBuilder(s); |
| |
| // apply width and justification |
| int len = sb.length(); |
| if (len < width) { |
| for (int i = 0; i < width - len; i++) { |
| if ((flags & LEFT_JUSTIFY) == LEFT_JUSTIFY) { |
| sb.append(' '); |
| } else { |
| sb.insert(0, ' '); |
| } |
| } |
| } |
| |
| String res = sb.toString(); |
| if ((flags & UPPERCASE) == UPPERCASE) { |
| res = res.toUpperCase(); |
| } |
| return res; |
| } |
| |
| /** |
| * Creates a debug counter. Invoking this method is equivalent to: |
| * |
| * <pre> |
| * Debug.counter(format, arg, null) |
| * </pre> |
| * |
| * except that the string formatting only happens if count is enabled. |
| * |
| * @see #counter(String, Object, Object) |
| */ |
| public static CounterKey counter(String format, Object arg) { |
| return createCounter(format, arg, null); |
| } |
| |
| /** |
| * Creates a debug counter. Invoking this method is equivalent to: |
| * |
| * <pre> |
| * Debug.counter(String.format(format, arg1, arg2)) |
| * </pre> |
| * |
| * except that the string formatting only happens if count is enabled. In addition, each |
| * argument is subject to the following type based conversion before being passed as an argument |
| * to {@link String#format(String, Object...)}: |
| * |
| * <pre> |
| * Type | Conversion |
| * ------------------+----------------- |
| * java.lang.Class | arg.getSimpleName() |
| * | |
| * </pre> |
| * |
| * @see #counter(CharSequence) |
| */ |
| public static CounterKey counter(String format, Object arg1, Object arg2) { |
| return createCounter(format, arg1, arg2); |
| } |
| |
| private static CounterKey createCounter(String format, Object arg1, Object arg2) { |
| return new CounterKeyImpl(format, arg1, arg2); |
| } |
| |
| public DebugConfig getConfig() { |
| return currentConfig; |
| } |
| |
| /** |
| * Creates a {@linkplain TimerKey timer}. |
| * <p> |
| * A disabled timer has virtually no overhead. |
| */ |
| public static TimerKey timer(CharSequence name) { |
| return createTimer("%s", name, null); |
| } |
| |
| /** |
| * Creates a debug timer. Invoking this method is equivalent to: |
| * |
| * <pre> |
| * Debug.timer(format, arg, null) |
| * </pre> |
| * |
| * except that the string formatting only happens if timing is enabled. |
| * |
| * @see #timer(String, Object, Object) |
| */ |
| public static TimerKey timer(String format, Object arg) { |
| return createTimer(format, arg, null); |
| } |
| |
| /** |
| * Creates a debug timer. Invoking this method is equivalent to: |
| * |
| * <pre> |
| * Debug.timer(String.format(format, arg1, arg2)) |
| * </pre> |
| * |
| * except that the string formatting only happens if timing is enabled. In addition, each |
| * argument is subject to the following type based conversion before being passed as an argument |
| * to {@link String#format(String, Object...)}: |
| * |
| * <pre> |
| * Type | Conversion |
| * ------------------+----------------- |
| * java.lang.Class | arg.getSimpleName() |
| * | |
| * </pre> |
| * |
| * @see #timer(CharSequence) |
| */ |
| public static TimerKey timer(String format, Object arg1, Object arg2) { |
| return createTimer(format, arg1, arg2); |
| } |
| |
| /** |
| * There are paths where construction of formatted class names are common and the code below is |
| * surprisingly expensive, so compute it once and cache it. |
| */ |
| private static final ClassValue<String> formattedClassName = new ClassValue<String>() { |
| @Override |
| protected String computeValue(Class<?> c) { |
| final String simpleName = c.getSimpleName(); |
| Class<?> enclosingClass = c.getEnclosingClass(); |
| if (enclosingClass != null) { |
| String prefix = ""; |
| while (enclosingClass != null) { |
| prefix = enclosingClass.getSimpleName() + "_" + prefix; |
| enclosingClass = enclosingClass.getEnclosingClass(); |
| } |
| return prefix + simpleName; |
| } else { |
| return simpleName; |
| } |
| } |
| }; |
| |
| public static Object convertFormatArg(Object arg) { |
| if (arg instanceof Class) { |
| return formattedClassName.get((Class<?>) arg); |
| } |
| return arg; |
| } |
| |
| static String formatDebugName(String format, Object arg1, Object arg2) { |
| return String.format(format, convertFormatArg(arg1), convertFormatArg(arg2)); |
| } |
| |
| private static TimerKey createTimer(String format, Object arg1, Object arg2) { |
| return new TimerKeyImpl(format, arg1, arg2); |
| } |
| |
| /** |
| * Represents a debug scope entered by {@link DebugContext#scope(Object)} or |
| * {@link DebugContext#sandbox(CharSequence, DebugConfig, Object...)}. Leaving the scope is |
| * achieved via {@link #close()}. |
| */ |
| public interface Scope extends AutoCloseable { |
| /** |
| * Gets the names of this scope and its ancestors separated by {@code '.'}. |
| */ |
| String getQualifiedName(); |
| |
| Iterable<Object> getCurrentContext(); |
| |
| @Override |
| void close(); |
| } |
| |
| boolean isTimerEnabled(TimerKeyImpl key) { |
| if (!metricsEnabled) { |
| // Pulling this common case out of `isTimerEnabledSlow` |
| // gives C1 a better chance to inline this method. |
| return false; |
| } |
| return isTimerEnabledSlow(key); |
| } |
| |
| private boolean isTimerEnabledSlow(AbstractKey key) { |
| if (currentScope != null && currentScope.isTimeEnabled()) { |
| return true; |
| } |
| if (immutable.listMetrics) { |
| key.ensureInitialized(); |
| } |
| assert checkNoConcurrentAccess(); |
| EconomicSet<String> unscoped = immutable.unscopedTimers; |
| return unscoped != null && (unscoped.isEmpty() || unscoped.contains(key.getName())); |
| } |
| |
| /** |
| * Determines if a given timer is enabled in the current scope. |
| */ |
| boolean isCounterEnabled(CounterKeyImpl key) { |
| if (!metricsEnabled) { |
| // Pulling this common case out of `isCounterEnabledSlow` |
| // gives C1 a better chance to inline this method. |
| return false; |
| } |
| return isCounterEnabledSlow(key); |
| } |
| |
| private boolean isCounterEnabledSlow(AbstractKey key) { |
| if (currentScope != null && currentScope.isCountEnabled()) { |
| return true; |
| } |
| if (immutable.listMetrics) { |
| key.ensureInitialized(); |
| } |
| assert checkNoConcurrentAccess(); |
| EconomicSet<String> unscoped = immutable.unscopedCounters; |
| return unscoped != null && (unscoped.isEmpty() || unscoped.contains(key.getName())); |
| } |
| |
| boolean isMemUseTrackerEnabled(MemUseTrackerKeyImpl key) { |
| if (!metricsEnabled) { |
| // Pulling this common case out of `isMemUseTrackerEnabledSlow` |
| // gives C1 a better chance to inline this method. |
| return false; |
| } |
| return isMemUseTrackerEnabledSlow(key); |
| } |
| |
| private boolean isMemUseTrackerEnabledSlow(AbstractKey key) { |
| if (currentScope != null && currentScope.isMemUseTrackingEnabled()) { |
| return true; |
| } |
| if (immutable.listMetrics) { |
| key.ensureInitialized(); |
| } |
| assert checkNoConcurrentAccess(); |
| EconomicSet<String> unscoped = immutable.unscopedMemUseTrackers; |
| return unscoped != null && (unscoped.isEmpty() || unscoped.contains(key.getName())); |
| } |
| |
| public boolean areMetricsEnabled() { |
| return metricsEnabled; |
| } |
| |
| @Override |
| public void close() { |
| closeDumpHandlers(false); |
| if (description != null) { |
| printMetrics(description); |
| } |
| if (metricsEnabled && metricValues != null && globalMetrics != null) { |
| globalMetrics.add(this); |
| } |
| metricValues = null; |
| if (sharedChannel != null) { |
| try { |
| sharedChannel.realClose(); |
| } catch (IOException ex) { |
| // ignore. |
| } |
| } |
| } |
| |
| public void closeDumpHandlers(boolean ignoreErrors) { |
| if (currentConfig != null) { |
| currentConfig.closeDumpHandlers(ignoreErrors); |
| } |
| } |
| |
| /** |
| * Records how many times a given method has been compiled. |
| */ |
| private static EconomicMap<Integer, Integer> compilations; |
| |
| /** |
| * Maintains maximum buffer size used by {@link #printMetrics(Description)} to minimize buffer |
| * resizing during subsequent calls to this method. |
| */ |
| private static int metricsBufSize = 50_000; |
| |
| /** |
| * Flag that allows the first call to {@link #printMetrics(Description)} to delete the file that |
| * will be appended to. |
| */ |
| private static boolean metricsFileDeleteCheckPerformed; |
| |
| /** |
| * Prints metric values in this object to the file (if any) specified by |
| * {@link DebugOptions#MetricsFile}. |
| */ |
| public void printMetrics(Description desc) { |
| if (metricValues == null) { |
| return; |
| } |
| String metricsFile = DebugOptions.MetricsFile.getValue(getOptions()); |
| if (metricsFile != null) { |
| // Use identity to distinguish methods that have been redefined |
| // or loaded by different class loaders. |
| Object compilable = desc.compilable; |
| Integer identity = System.identityHashCode(compilable); |
| int compilationNr; |
| synchronized (PRINT_METRICS_LOCK) { |
| if (!metricsFileDeleteCheckPerformed) { |
| metricsFileDeleteCheckPerformed = true; |
| File file = new File(metricsFile); |
| if (file.exists()) { |
| // This can return false in case something like /dev/stdout |
| // is specified. If the file is unwriteable, the file open |
| // below will fail. |
| file.delete(); |
| } |
| } |
| if (compilations == null) { |
| compilationNr = 0; |
| compilations = EconomicMap.create(); |
| } else { |
| Integer value = compilations.get(identity); |
| compilationNr = value == null ? 0 : value + 1; |
| } |
| compilations.put(identity, compilationNr); |
| } |
| |
| // Release the lock while generating the content to reduce contention. |
| // This means `compilationNr` fields may show up out of order in the file. |
| ByteArrayOutputStream baos = new ByteArrayOutputStream(metricsBufSize); |
| PrintStream out = new PrintStream(baos); |
| if (metricsFile.endsWith(".csv") || metricsFile.endsWith(".CSV")) { |
| printMetricsCSV(out, compilable, identity, compilationNr, desc.identifier); |
| } else { |
| printMetrics(out, compilable, identity, compilationNr, desc.identifier); |
| } |
| |
| byte[] content = baos.toByteArray(); |
| Path path = Paths.get(metricsFile); |
| synchronized (PRINT_METRICS_LOCK) { |
| metricsBufSize = Math.max(metricsBufSize, content.length); |
| try { |
| Files.write(path, content, StandardOpenOption.CREATE, StandardOpenOption.APPEND); |
| } catch (IOException e) { |
| } |
| } |
| } |
| } |
| |
| /** |
| * Lock to serialize writes to {@link DebugOptions#MetricsFile}. |
| */ |
| private static final Object PRINT_METRICS_LOCK = new Object(); |
| |
| /** |
| * Appends metrics in CSV format to {@code out} for a single method compilation. |
| * |
| * @param identity the identity hash code of {@code compilable} |
| * @param compilationNr where this compilation lies in the ordered sequence of all compilations |
| * identified by {@code identity} |
| * @param compilationId the runtime issued identifier for the compilation |
| */ |
| private void printMetricsCSV(PrintStream out, Object compilable, Integer identity, int compilationNr, String compilationId) { |
| String compilableName = compilable instanceof JavaMethod ? ((JavaMethod) compilable).format("%H.%n(%p)%R") : String.valueOf(compilable); |
| String csvFormat = CSVUtil.buildFormatString("%s", "%s", "%d", "%s"); |
| String format = String.format(csvFormat, CSVUtil.Escape.escapeArgs(compilableName, identity, compilationNr, compilationId)); |
| char sep = CSVUtil.SEPARATOR; |
| format += sep + "%s" + sep + "%s" + sep + "%s"; |
| for (MetricKey key : KeyRegistry.getKeys()) { |
| int index = ((AbstractKey) key).getIndex(); |
| if (index < metricValues.length) { |
| Pair<String, String> valueAndUnit = key.toCSVFormat(metricValues[index]); |
| CSVUtil.Escape.println(out, format, CSVUtil.Escape.escape(key.getName()), valueAndUnit.getLeft(), valueAndUnit.getRight()); |
| } |
| } |
| } |
| |
| /** |
| * Appends metrics in a human readable format to {@code out} for a single method compilation. |
| * |
| * @param identity the identity hash code of {@code compilable} |
| * @param compilationNr where this compilation lies in the ordered sequence of all compilations |
| * identified by {@code identity} |
| * @param compilationId the runtime issued identifier for the compilation |
| */ |
| private void printMetrics(PrintStream out, Object compilable, Integer identity, int compilationNr, String compilationId) { |
| String compilableName = compilable instanceof JavaMethod ? ((JavaMethod) compilable).format("%H.%n(%p)%R") : String.valueOf(compilable); |
| int maxKeyWidth = compilableName.length(); |
| SortedMap<String, String> res = new TreeMap<>(); |
| for (MetricKey key : KeyRegistry.getKeys()) { |
| int index = ((AbstractKey) key).getIndex(); |
| if (index < metricValues.length && metricValues[index] != 0) { |
| String name = key.getName(); |
| long value = metricValues[index]; |
| String valueString; |
| if (key instanceof TimerKey) { |
| // Report timers in ms |
| TimerKey timer = (TimerKey) key; |
| long ms = timer.getTimeUnit().toMillis(value); |
| if (ms == 0) { |
| continue; |
| } |
| valueString = ms + "ms"; |
| } else { |
| valueString = String.valueOf(value); |
| } |
| res.put(name, valueString); |
| maxKeyWidth = Math.max(maxKeyWidth, name.length()); |
| } |
| } |
| |
| String title = String.format("%s [id:%s compilation:%d compilation_id:%s]", compilableName, identity, compilationNr, compilationId); |
| out.println(new String(new char[title.length()]).replace('\0', '#')); |
| out.printf("%s%n", title); |
| out.println(new String(new char[title.length()]).replace('\0', '~')); |
| |
| for (Map.Entry<String, String> e : res.entrySet()) { |
| out.printf("%-" + String.valueOf(maxKeyWidth) + "s = %20s%n", e.getKey(), e.getValue()); |
| } |
| out.println(); |
| } |
| |
| @SuppressWarnings({"unused", "unchecked"}) |
| private static <E extends Exception> E rethrowSilently(Class<E> type, Throwable ex) throws E { |
| throw (E) ex; |
| } |
| } |