--- /dev/null 2017-07-07 09:32:04.000000000 -0700 +++ new/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.debug/src/org/graalvm/compiler/debug/DebugContext.java 2017-07-07 09:32:04.000000000 -0700 @@ -0,0 +1,2030 @@ +/* + * 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.ListMetrics; +import static org.graalvm.compiler.debug.DebugOptions.Log; +import static org.graalvm.compiler.debug.DebugOptions.MemUseTrackers; +import static org.graalvm.compiler.debug.DebugOptions.MethodFilter; +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 static org.graalvm.compiler.debug.DebugOptions.Verify; + +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 org.graalvm.compiler.options.OptionKey; +import org.graalvm.compiler.options.OptionValues; +import org.graalvm.util.EconomicMap; +import org.graalvm.util.EconomicSet; +import org.graalvm.util.Pair; + +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 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; + + DebugConfig currentConfig; + ScopeImpl currentScope; + CloseableCounter currentTimer; + CloseableCounter currentMemUseTracker; + Scope lastClosedScope; + Throwable lastExceptionThrown; + + /** + * Stores the {@link MetricKey} values. + */ + private long[] metricValues; + + /** + * Determines if dynamic scopes are enabled. + */ + public boolean areScopesEnabled() { + return immutable.scopesEnabled; + } + + /** + * 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 unscopedCounters; + + /** + * Names of unscoped timers. A timer is unscoped if this set is empty or contains the + * timer's name. + */ + final EconomicSet 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 unscopedMemUseTrackers; + + private static EconomicSet parseUnscopedMetricSpec(String spec, boolean unconditional, boolean accumulatedKey) { + EconomicSet 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 option, OptionValues options) { + return option.getValue(options) != null && !option.getValue(options).isEmpty(); + } + + private Immutable(OptionValues options) { + this.options = options; + this.unscopedCounters = parseUnscopedMetricSpec(Counters.getValue(options), "".equals(Count.getValue(options)), false); + this.unscopedTimers = parseUnscopedMetricSpec(Timers.getValue(options), "".equals(Time.getValue(options)), true); + this.unscopedMemUseTrackers = parseUnscopedMetricSpec(MemUseTrackers.getValue(options), "".equals(TrackMemUse.getValue(options)), true); + + 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 { + } + + 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; + } + } + + 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 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 factories) { + return new DebugContext(description, globalMetrics, logStream, Immutable.create(options), factories); + } + + private DebugContext(Description description, GlobalMetrics globalMetrics, PrintStream logStream, Immutable immutable, Iterable factories) { + this.immutable = immutable; + this.description = description; + this.globalMetrics = globalMetrics; + if (immutable.scopesEnabled) { + OptionValues options = immutable.options; + List dumpHandlers = new ArrayList<>(); + List 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, + Log.getValue(options), + Count.getValue(options), + TrackMemUse.getValue(options), + Time.getValue(options), + Dump.getValue(options), + Verify.getValue(options), + MethodFilter.getValue(options), + logStream, dumpHandlers, verifyHandlers); + currentScope = new ScopeImpl(this, Thread.currentThread()); + currentScope.updateFlags(currentConfig); + metricsEnabled = true; + } else { + metricsEnabled = immutable.hasUnscopedMetrics() || immutable.listMetrics; + } + } + + /** + * 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. + *

+ * It is recommended to use the try-with-resource statement for managing entering and leaving + * debug scopes. For example: + * + *

+     * try (Scope s = Debug.scope("InliningGraph", inlineeGraph)) {
+     *     ...
+     * } catch (Throwable e) {
+     *     throw Debug.handle(e);
+     * }
+     * 
+ * + * The {@code name} argument is subject to the following type based conversion before having + * {@link Object#toString()} called on it: + * + *
+     *     Type          | Conversion
+     * ------------------+-----------------
+     *  java.lang.Class  | arg.getSimpleName()
+     *                   |
+     * 
+ * + * @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.ENABLED ? 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("") && 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; + } + } + + /** + * Creates and enters a new debug scope which will be disjoint from the current debug scope. + *

+ * It is recommended to use the try-with-resource statement for managing entering and leaving + * debug scopes. For example: + * + *

+     * try (Scope s = Debug.sandbox("CompilingStub", null, stubGraph)) {
+     *     ...
+     * } catch (Throwable e) {
+     *     throw Debug.handle(e);
+     * }
+     * 
+ * + * @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; + } + } + + class DisabledScope implements DebugContext.Scope { + final boolean savedMetricsEnabled; + final ScopeImpl savedScope; + final DebugConfig 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 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 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 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 context() { + if (currentScope != null) { + return currentScope.getCurrentContext(); + } else { + return Collections.emptyList(); + } + } + + @SuppressWarnings("unchecked") + public List contextSnapshot(Class clazz) { + if (currentScope != null) { + List 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 contextLookup(Class 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 contextLookupTopdown(Class 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: + * + *
+     * Debug.memUseTracker(format, arg, null)
+     * 
+ * + * 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: + * + *
+     * Debug.memUseTracker(String.format(format, arg1, arg2))
+     * 
+ * + * 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...)}: + * + *
+     *     Type          | Conversion
+     * ------------------+-----------------
+     *  java.lang.Class  | arg.getSimpleName()
+     *                   |
+     * 
+ * + * @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 convertValuesToKeyValueMap(long[] values) { + List keys = KeyRegistry.getKeys(); + Collections.sort(keys, MetricKey.NAME_COMPARATOR); + EconomicMap 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: + * + *
+     * Debug.counter(format, arg, null)
+     * 
+ * + * 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: + * + *
+     * Debug.counter(String.format(format, arg1, arg2))
+     * 
+ * + * 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...)}: + * + *
+     *     Type          | Conversion
+     * ------------------+-----------------
+     *  java.lang.Class  | arg.getSimpleName()
+     *                   |
+     * 
+ * + * @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}. + *

+ * 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: + * + *

+     * Debug.timer(format, arg, null)
+     * 
+ * + * 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: + * + *
+     * Debug.timer(String.format(format, arg1, arg2))
+     * 
+ * + * 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...)}: + * + *
+     *     Type          | Conversion
+     * ------------------+-----------------
+     *  java.lang.Class  | arg.getSimpleName()
+     *                   |
+     * 
+ * + * @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 formattedClassName = new ClassValue() { + @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 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 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 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 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 && globalMetrics != null && metricValues != null) { + globalMetrics.add(this); + } + metricValues = null; + } + + public void closeDumpHandlers(boolean ignoreErrors) { + if (currentConfig != null) { + currentConfig.closeDumpHandlers(ignoreErrors); + } + } + + /** + * Records how many times a given method has been compiled. + */ + private static EconomicMap 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 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 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 e : res.entrySet()) { + out.printf("%-" + String.valueOf(maxKeyWidth) + "s = %20s%n", e.getKey(), e.getValue()); + } + out.println(); + } +}