001/*
002 * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.
008 *
009 * This code is distributed in the hope that it will be useful, but WITHOUT
010 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
011 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
012 * version 2 for more details (a copy is included in the LICENSE file that
013 * accompanied this code).
014 *
015 * You should have received a copy of the GNU General Public License version
016 * 2 along with this work; if not, write to the Free Software Foundation,
017 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
018 *
019 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
020 * or visit www.oracle.com if you need additional information or have any
021 * questions.
022 */
023package com.oracle.graal.debug.internal;
024
025import java.io.*;
026import java.util.*;
027import java.util.concurrent.*;
028
029import com.oracle.graal.debug.*;
030
031public final class DebugScope implements Debug.Scope {
032
033    private final class IndentImpl implements Indent {
034
035        private static final String INDENTATION_INCREMENT = "  ";
036
037        final String indent;
038        final IndentImpl parentIndent;
039
040        IndentImpl(IndentImpl parentIndent) {
041            this.parentIndent = parentIndent;
042            this.indent = (parentIndent == null ? "" : parentIndent.indent + INDENTATION_INCREMENT);
043        }
044
045        private void printScopeName(StringBuilder str) {
046            if (logScopeName) {
047                if (parentIndent != null) {
048                    parentIndent.printScopeName(str);
049                }
050                str.append(indent).append("[thread:").append(Thread.currentThread().getId()).append("] scope: ").append(getQualifiedName()).append(System.lineSeparator());
051                logScopeName = false;
052            }
053        }
054
055        public void log(int logLevel, String msg, Object... args) {
056            if (isLogEnabled(logLevel)) {
057                StringBuilder str = new StringBuilder();
058                printScopeName(str);
059                str.append(indent);
060                String result = args.length == 0 ? msg : String.format(msg, args);
061                String lineSep = System.lineSeparator();
062                str.append(result.replace(lineSep, lineSep.concat(indent)));
063                str.append(lineSep);
064                output.append(str);
065                lastUsedIndent = this;
066            }
067        }
068
069        IndentImpl indent() {
070            lastUsedIndent = new IndentImpl(this);
071            return lastUsedIndent;
072        }
073
074        @Override
075        public void close() {
076            if (parentIndent != null) {
077                lastUsedIndent = parentIndent;
078            }
079        }
080    }
081
082    private static final ThreadLocal<DebugScope> instanceTL = new ThreadLocal<>();
083    private static final ThreadLocal<DebugScope> lastClosedTL = new ThreadLocal<>();
084    private static final ThreadLocal<DebugConfig> configTL = new ThreadLocal<>();
085    private static final ThreadLocal<Throwable> lastExceptionThrownTL = new ThreadLocal<>();
086
087    private final DebugScope parent;
088    private final DebugConfig parentConfig;
089    private final boolean sandbox;
090    private IndentImpl lastUsedIndent;
091    private boolean logScopeName;
092
093    private final Object[] context;
094
095    private DebugValueMap valueMap;
096
097    private String qualifiedName;
098    private final String unqualifiedName;
099
100    private static final char SCOPE_SEP = '.';
101
102    private boolean meterEnabled;
103    private boolean timeEnabled;
104    private boolean memUseTrackingEnabled;
105    private boolean verifyEnabled;
106
107    private int currentDumpLevel;
108    private int currentLogLevel;
109
110    private PrintStream output;
111
112    public static DebugScope getInstance() {
113        DebugScope result = instanceTL.get();
114        if (result == null) {
115            DebugScope topLevelDebugScope = new DebugScope(Thread.currentThread());
116            instanceTL.set(topLevelDebugScope);
117            return topLevelDebugScope;
118        } else {
119            return result;
120        }
121    }
122
123    public static DebugConfig getConfig() {
124        return configTL.get();
125    }
126
127    static final Object[] EMPTY_CONTEXT = new Object[0];
128
129    private DebugScope(Thread thread) {
130        this(thread.getName(), null, false);
131        computeValueMap(thread.getName());
132        DebugValueMap.registerTopLevel(getValueMap());
133    }
134
135    private DebugScope(String unqualifiedName, DebugScope parent, boolean sandbox, Object... context) {
136        this.parent = parent;
137        this.sandbox = sandbox;
138        this.parentConfig = getConfig();
139        this.context = context;
140        this.unqualifiedName = unqualifiedName;
141        if (parent != null) {
142            logScopeName = !unqualifiedName.equals("");
143        } else {
144            logScopeName = true;
145        }
146
147        this.output = TTY.out;
148        assert context != null;
149    }
150
151    private void computeValueMap(String name) {
152        if (parent != null) {
153            for (DebugValueMap child : parent.getValueMap().getChildren()) {
154                if (child.getName().equals(name)) {
155                    this.valueMap = child;
156                    return;
157                }
158            }
159            this.valueMap = new DebugValueMap(name);
160            parent.getValueMap().addChild(this.valueMap);
161        } else {
162            this.valueMap = new DebugValueMap(name);
163        }
164    }
165
166    public void close() {
167        instanceTL.set(parent);
168        configTL.set(parentConfig);
169        lastClosedTL.set(this);
170    }
171
172    public boolean isDumpEnabled(int dumpLevel) {
173        assert dumpLevel > 0;
174        return currentDumpLevel >= dumpLevel;
175    }
176
177    /**
178     * Enable dumping at the new {@code dumpLevel} for the remainder of enclosing scopes. This only
179     * works if a {@link TopLevelDebugConfig} was installed at a higher scope.
180     *
181     * @param dumpLevel
182     */
183    public static void setDumpLevel(int dumpLevel) {
184        TopLevelDebugConfig config = fetchTopLevelDebugConfig("setDebugLevel");
185        if (config != null) {
186            config.override(DelegatingDebugConfig.Level.DUMP, dumpLevel);
187            recursiveUpdateFlags();
188        }
189    }
190
191    /**
192     * Enable logging at the new {@code logLevel} for the remainder of enclosing scopes. This only
193     * works if a {@link TopLevelDebugConfig} was installed at a higher scope.
194     *
195     * @param logLevel
196     */
197    public static void setLogLevel(int logLevel) {
198        TopLevelDebugConfig config = fetchTopLevelDebugConfig("setLogLevel");
199        if (config != null) {
200            config.override(DelegatingDebugConfig.Level.LOG, logLevel);
201            config.delegate(DelegatingDebugConfig.Feature.LOG_METHOD);
202            recursiveUpdateFlags();
203        }
204    }
205
206    private static void recursiveUpdateFlags() {
207        DebugScope c = DebugScope.getInstance();
208        while (c != null) {
209            c.updateFlags();
210            c = c.parent;
211        }
212    }
213
214    private static TopLevelDebugConfig fetchTopLevelDebugConfig(String msg) {
215        DebugConfig config = getConfig();
216        if (config instanceof TopLevelDebugConfig) {
217            return (TopLevelDebugConfig) config;
218        } else {
219            if (config == null) {
220                TTY.println("DebugScope.%s ignored because debugging is disabled", msg);
221            } else {
222                TTY.println("DebugScope.%s ignored because top level delegate config missing", msg);
223            }
224            return null;
225        }
226    }
227
228    public boolean isVerifyEnabled() {
229        return verifyEnabled;
230    }
231
232    public boolean isLogEnabled(int logLevel) {
233        assert logLevel > 0;
234        return currentLogLevel >= logLevel;
235    }
236
237    public boolean isMeterEnabled() {
238        return meterEnabled;
239    }
240
241    public boolean isTimeEnabled() {
242        return timeEnabled;
243    }
244
245    public boolean isMemUseTrackingEnabled() {
246        return memUseTrackingEnabled;
247    }
248
249    public void log(int logLevel, String msg, Object... args) {
250        if (isLogEnabled(logLevel)) {
251            getLastUsedIndent().log(logLevel, msg, args);
252        }
253    }
254
255    public void dump(int dumpLevel, Object object, String formatString, Object... args) {
256        if (isDumpEnabled(dumpLevel)) {
257            DebugConfig config = getConfig();
258            if (config != null) {
259                String message = String.format(formatString, args);
260                for (DebugDumpHandler dumpHandler : config.dumpHandlers()) {
261                    dumpHandler.dump(object, message);
262                }
263            }
264        }
265    }
266
267    /**
268     * This method exists mainly to allow a debugger (e.g., Eclipse) to force dump a graph.
269     */
270    public static void forceDump(Object object, String format, Object... args) {
271        DebugConfig config = getConfig();
272        if (config != null) {
273            String message = String.format(format, args);
274            for (DebugDumpHandler dumpHandler : config.dumpHandlers()) {
275                dumpHandler.dump(object, message);
276            }
277        } else {
278            TTY.println("Forced dump ignored because debugging is disabled - use -G:Dump=xxx option");
279        }
280    }
281
282    /**
283     * @see Debug#verify(Object, String)
284     */
285    public void verify(Object object, String formatString, Object... args) {
286        if (isVerifyEnabled()) {
287            DebugConfig config = getConfig();
288            if (config != null) {
289                String message = String.format(formatString, args);
290                for (DebugVerifyHandler handler : config.verifyHandlers()) {
291                    handler.verify(object, message);
292                }
293            }
294        }
295    }
296
297    /**
298     * Creates and enters a new debug scope which is either a child of the current scope or a
299     * disjoint top level scope.
300     *
301     * @param name the name of the new scope
302     * @param sandboxConfig the configuration to use for a new top level scope, or null if the new
303     *            scope should be a child scope
304     * @param newContextObjects objects to be appended to the debug context
305     * @return the new scope which will be exited when its {@link #close()} method is called
306     */
307    public DebugScope scope(CharSequence name, DebugConfig sandboxConfig, Object... newContextObjects) {
308        DebugScope newScope = null;
309        if (sandboxConfig != null) {
310            newScope = new DebugScope(name.toString(), this, true, newContextObjects);
311            configTL.set(sandboxConfig);
312        } else {
313            newScope = this.createChild(name.toString(), newContextObjects);
314        }
315        instanceTL.set(newScope);
316        newScope.updateFlags();
317        return newScope;
318    }
319
320    public RuntimeException handle(Throwable e) {
321        DebugScope lastClosed = lastClosedTL.get();
322        assert lastClosed.parent == this : "Debug.handle() used with no matching Debug.scope(...) or Debug.sandbox(...)";
323        if (e != lastExceptionThrownTL.get()) {
324            RuntimeException newException = null;
325            instanceTL.set(lastClosed);
326            try (DebugScope s = lastClosed) {
327                newException = s.interceptException(e);
328            }
329            assert instanceTL.get() == this;
330            assert lastClosed == lastClosedTL.get();
331            if (newException == null) {
332                lastExceptionThrownTL.set(e);
333            } else {
334                lastExceptionThrownTL.set(newException);
335                throw newException;
336            }
337        }
338        if (e instanceof Error) {
339            throw (Error) e;
340        }
341        if (e instanceof RuntimeException) {
342            throw (RuntimeException) e;
343        }
344        throw new RuntimeException(e);
345    }
346
347    private void updateFlags() {
348        DebugConfig config = getConfig();
349        if (config == null) {
350            meterEnabled = false;
351            memUseTrackingEnabled = false;
352            timeEnabled = false;
353            verifyEnabled = false;
354
355            currentDumpLevel = 0;
356
357            // Be pragmatic: provide a default log stream to prevent a crash if the stream is not
358            // set while logging
359            output = TTY.out;
360        } else {
361            meterEnabled = config.isMeterEnabled();
362            memUseTrackingEnabled = config.isMemUseTrackingEnabled();
363            timeEnabled = config.isTimeEnabled();
364            verifyEnabled = config.isVerifyEnabled();
365            output = config.output();
366            currentDumpLevel = config.getDumpLevel();
367            currentLogLevel = config.getLogLevel();
368        }
369    }
370
371    private RuntimeException interceptException(final Throwable e) {
372        final DebugConfig config = getConfig();
373        if (config != null) {
374            try (DebugScope s = scope("InterceptException", null, e)) {
375                return config.interceptException(e);
376            } catch (Throwable t) {
377                return new RuntimeException("Exception while intercepting exception", t);
378            }
379        }
380        return null;
381    }
382
383    private DebugValueMap getValueMap() {
384        if (valueMap == null) {
385            computeValueMap(unqualifiedName);
386        }
387        return valueMap;
388    }
389
390    long getCurrentValue(int index) {
391        return getValueMap().getCurrentValue(index);
392    }
393
394    void setCurrentValue(int index, long l) {
395        getValueMap().setCurrentValue(index, l);
396    }
397
398    private DebugScope createChild(String newName, Object[] newContext) {
399        return new DebugScope(newName, this, false, newContext);
400    }
401
402    public Iterable<Object> getCurrentContext() {
403        final DebugScope scope = this;
404        return new Iterable<Object>() {
405
406            @Override
407            public Iterator<Object> iterator() {
408                return new Iterator<Object>() {
409
410                    DebugScope currentScope = scope;
411                    int objectIndex;
412
413                    @Override
414                    public boolean hasNext() {
415                        selectScope();
416                        return currentScope != null;
417                    }
418
419                    private void selectScope() {
420                        while (currentScope != null && currentScope.context.length <= objectIndex) {
421                            currentScope = currentScope.sandbox ? null : currentScope.parent;
422                            objectIndex = 0;
423                        }
424                    }
425
426                    @Override
427                    public Object next() {
428                        selectScope();
429                        if (currentScope != null) {
430                            return currentScope.context[objectIndex++];
431                        }
432                        throw new IllegalStateException("May only be called if there is a next element.");
433                    }
434
435                    @Override
436                    public void remove() {
437                        throw new UnsupportedOperationException("This iterator is read only.");
438                    }
439                };
440            }
441        };
442    }
443
444    public static <T> T call(Callable<T> callable) {
445        try {
446            return callable.call();
447        } catch (Exception e) {
448            if (e instanceof RuntimeException) {
449                throw (RuntimeException) e;
450            } else {
451                throw new RuntimeException(e);
452            }
453        }
454    }
455
456    public void setConfig(DebugConfig newConfig) {
457        configTL.set(newConfig);
458        updateFlags();
459    }
460
461    public String getQualifiedName() {
462        if (qualifiedName == null) {
463            if (parent == null) {
464                qualifiedName = unqualifiedName;
465            } else {
466                qualifiedName = parent.getQualifiedName() + SCOPE_SEP + unqualifiedName;
467            }
468        }
469        return qualifiedName;
470    }
471
472    public Indent pushIndentLogger() {
473        lastUsedIndent = getLastUsedIndent().indent();
474        return lastUsedIndent;
475    }
476
477    public IndentImpl getLastUsedIndent() {
478        if (lastUsedIndent == null) {
479            if (parent != null) {
480                lastUsedIndent = new IndentImpl(parent.getLastUsedIndent());
481            } else {
482                lastUsedIndent = new IndentImpl(null);
483            }
484        }
485        return lastUsedIndent;
486    }
487}