001/*
002 * Copyright (c) 2011, 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.printer;
024
025import static com.oracle.graal.compiler.common.GraalOptions.*;
026import static com.oracle.graal.debug.GraalDebugConfig.*;
027
028import java.io.*;
029import java.net.*;
030import java.nio.channels.*;
031import java.nio.file.*;
032import java.text.*;
033import java.util.*;
034
035import com.oracle.graal.debug.*;
036import com.oracle.graal.debug.Debug.*;
037
038import jdk.internal.jvmci.meta.*;
039
040import com.oracle.graal.graph.*;
041import com.oracle.graal.phases.schedule.*;
042
043//JaCoCo Exclude
044
045/**
046 * Observes compilation events and uses {@link IdealGraphPrinter} to generate a graph representation
047 * that can be inspected with the <a href="http://kenai.com/projects/igv">Ideal Graph
048 * Visualizer</a>.
049 */
050public class GraphPrinterDumpHandler implements DebugDumpHandler {
051
052    protected GraphPrinter printer;
053    private List<String> previousInlineContext;
054    private int[] dumpIds = {};
055    private int failuresCount;
056
057    /**
058     * Creates a new {@link GraphPrinterDumpHandler}.
059     */
060    public GraphPrinterDumpHandler() {
061        previousInlineContext = new ArrayList<>();
062    }
063
064    private void ensureInitialized() {
065        if (printer == null) {
066            if (failuresCount > 8) {
067                return;
068            }
069            previousInlineContext.clear();
070            createPrinter();
071        }
072    }
073
074    protected void createPrinter() {
075        if (PrintIdealGraphFile.getValue()) {
076            initializeFilePrinter();
077        } else {
078            initializeNetworkPrinter();
079        }
080    }
081
082    private int nextDumpId() {
083        int depth = previousInlineContext.size();
084        if (dumpIds.length < depth) {
085            dumpIds = Arrays.copyOf(dumpIds, depth);
086        }
087        return dumpIds[depth - 1]++;
088    }
089
090    // This field must be lazily initialized as this class may be loaded in a version
091    // VM startup phase at which not all required features (such as system properties)
092    // are online.
093    private static volatile SimpleDateFormat sdf;
094
095    private void initializeFilePrinter() {
096        String ext;
097        if (PrintBinaryGraphs.getValue()) {
098            ext = ".bgv";
099        } else {
100            ext = ".gv.xml";
101        }
102        if (sdf == null) {
103            sdf = new SimpleDateFormat("YYYY-MM-dd-HHmm");
104        }
105
106        // DateFormats are inherently unsafe for multi-threaded use. Use a synchronized block.
107        String prefix;
108        synchronized (sdf) {
109            prefix = "Graphs-" + Thread.currentThread().getName() + "-" + sdf.format(new Date());
110        }
111
112        String num = "";
113        File file;
114        int i = 0;
115        while ((file = new File(prefix + num + ext)).exists()) {
116            num = "-" + Integer.toString(++i);
117        }
118        try {
119            if (PrintBinaryGraphs.getValue()) {
120                printer = new BinaryGraphPrinter(FileChannel.open(file.toPath(), StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW));
121            } else {
122                printer = new IdealGraphPrinter(new FileOutputStream(file), true);
123            }
124            TTY.println("Dumping IGV graphs to %s", file.getName());
125        } catch (IOException e) {
126            TTY.println("Failed to open %s to dump IGV graphs : %s", file.getName(), e);
127            failuresCount++;
128            printer = null;
129        }
130    }
131
132    private void initializeNetworkPrinter() {
133        String host = PrintIdealGraphAddress.getValue();
134        int port = PrintBinaryGraphs.getValue() ? PrintBinaryGraphPort.getValue() : PrintIdealGraphPort.getValue();
135        try {
136            if (PrintBinaryGraphs.getValue()) {
137                printer = new BinaryGraphPrinter(SocketChannel.open(new InetSocketAddress(host, port)));
138            } else {
139                IdealGraphPrinter xmlPrinter = new IdealGraphPrinter(new Socket(host, port).getOutputStream(), true);
140                printer = xmlPrinter;
141            }
142            TTY.println("Connected to the IGV on %s:%d", host, port);
143        } catch (ClosedByInterruptException | InterruptedIOException e) {
144            /*
145             * Interrupts should not count as errors because they may be caused by a cancelled Graal
146             * compilation. ClosedByInterruptException occurs if the SocketChannel could not be
147             * opened. InterruptedIOException occurs if new Socket(..) was interrupted.
148             */
149            printer = null;
150        } catch (IOException e) {
151            TTY.println("Could not connect to the IGV on %s:%d : %s", host, port, e);
152            failuresCount++;
153            printer = null;
154        }
155    }
156
157    @Override
158    public void dump(Object object, final String message) {
159        if (object instanceof Graph && PrintIdealGraph.getValue()) {
160            ensureInitialized();
161            if (printer == null) {
162                return;
163            }
164            final Graph graph = (Graph) object;
165
166            if (printer != null) {
167                // Get all current JavaMethod instances in the context.
168                List<String> inlineContext = getInlineContext();
169
170                // Reverse list such that inner method comes after outer method.
171                Collections.reverse(inlineContext);
172
173                // Check for method scopes that must be closed since the previous dump.
174                for (int i = 0; i < previousInlineContext.size(); ++i) {
175                    if (i >= inlineContext.size() || !inlineContext.get(i).equals(previousInlineContext.get(i))) {
176                        for (int inlineDepth = previousInlineContext.size() - 1; inlineDepth >= i; --inlineDepth) {
177                            closeScope(inlineDepth);
178                        }
179                        break;
180                    }
181                }
182
183                // Check for method scopes that must be opened since the previous dump.
184                for (int i = 0; i < inlineContext.size(); ++i) {
185                    if (i >= previousInlineContext.size() || !inlineContext.get(i).equals(previousInlineContext.get(i))) {
186                        for (int inlineDepth = i; inlineDepth < inlineContext.size(); ++inlineDepth) {
187                            openScope(inlineContext.get(inlineDepth), inlineDepth);
188                        }
189                        break;
190                    }
191                }
192
193                // Save inline context for next dump.
194                previousInlineContext = inlineContext;
195
196                final SchedulePhase predefinedSchedule = getPredefinedSchedule();
197                try (Scope s = Debug.sandbox("PrintingGraph", null)) {
198                    // Finally, output the graph.
199                    printer.print(graph, nextDumpId() + ":" + message, predefinedSchedule);
200                } catch (IOException e) {
201                    failuresCount++;
202                    printer = null;
203                } catch (Throwable e) {
204                    throw Debug.handle(e);
205                }
206            }
207        }
208    }
209
210    private static List<String> getInlineContext() {
211        List<String> result = new ArrayList<>();
212        Object lastMethodOrGraph = null;
213        for (Object o : Debug.context()) {
214            JavaMethod method = asJavaMethod(o);
215            if (method != null) {
216                if (lastMethodOrGraph == null || asJavaMethod(lastMethodOrGraph) == null || !asJavaMethod(lastMethodOrGraph).equals(method)) {
217                    result.add(method.format("%H::%n(%p)"));
218                } else {
219                    // This prevents multiple adjacent method context objects for the same method
220                    // from resulting in multiple IGV tree levels. This works on the
221                    // assumption that real inlining debug scopes will have a graph
222                    // context object between the inliner and inlinee context objects.
223                }
224            } else if (o instanceof DebugDumpScope) {
225                DebugDumpScope debugDumpScope = (DebugDumpScope) o;
226                if (debugDumpScope.decorator && !result.isEmpty()) {
227                    result.set(result.size() - 1, debugDumpScope.name + ":" + result.get(result.size() - 1));
228                } else {
229                    result.add(debugDumpScope.name);
230                }
231            }
232            if (o instanceof JavaMethod || o instanceof Graph) {
233                lastMethodOrGraph = o;
234            }
235        }
236        if (result.isEmpty()) {
237            result.add("Top Scope");
238        }
239        return result;
240    }
241
242    private static SchedulePhase getPredefinedSchedule() {
243        SchedulePhase result = null;
244        for (Object o : Debug.context()) {
245            if (o instanceof SchedulePhase) {
246                result = (SchedulePhase) o;
247            }
248        }
249        return result;
250    }
251
252    private void openScope(String name, int inlineDepth) {
253        String prefix = inlineDepth == 0 ? Thread.currentThread().getName() + ":" : "";
254        try {
255            printer.beginGroup(prefix + name, name, Debug.contextLookup(ResolvedJavaMethod.class), -1);
256        } catch (IOException e) {
257            failuresCount++;
258            printer = null;
259        }
260    }
261
262    private void closeScope(int inlineDepth) {
263        dumpIds[inlineDepth] = 0;
264        try {
265            printer.endGroup();
266        } catch (IOException e) {
267            failuresCount++;
268            printer = null;
269        }
270    }
271
272    @Override
273    public void close() {
274        for (int inlineDepth = 0; inlineDepth < previousInlineContext.size(); inlineDepth++) {
275            closeScope(inlineDepth);
276        }
277        if (printer != null) {
278            printer.close();
279            printer = null;
280        }
281    }
282}