1 /* 2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 package org.graalvm.compiler.printer; 24 25 import static org.graalvm.compiler.debug.DebugOptions.PrintBinaryGraphPort; 26 import static org.graalvm.compiler.debug.DebugOptions.PrintBinaryGraphs; 27 import static org.graalvm.compiler.debug.DebugOptions.PrintGraphHost; 28 import static org.graalvm.compiler.debug.DebugOptions.PrintXmlGraphPort; 29 import static org.graalvm.compiler.debug.DebugOptions.ShowDumpFiles; 30 31 import java.io.File; 32 import java.io.IOException; 33 import java.io.InterruptedIOException; 34 import java.net.InetSocketAddress; 35 import java.net.Socket; 36 import java.nio.channels.ClosedByInterruptException; 37 import java.nio.channels.FileChannel; 38 import java.nio.channels.SocketChannel; 39 import java.nio.file.FileAlreadyExistsException; 40 import java.nio.file.Files; 41 import java.nio.file.InvalidPathException; 42 import java.nio.file.Path; 43 import java.nio.file.Paths; 44 import java.nio.file.StandardOpenOption; 45 import java.util.ArrayList; 46 import java.util.List; 47 import java.util.concurrent.atomic.AtomicInteger; 48 49 import org.graalvm.compiler.api.replacements.SnippetReflectionProvider; 50 import org.graalvm.compiler.core.common.CompilationIdentifier; 51 import org.graalvm.compiler.debug.Assertions; 52 import org.graalvm.compiler.debug.DebugContext; 53 import org.graalvm.compiler.debug.DebugDumpHandler; 54 import org.graalvm.compiler.debug.DebugHandler; 55 import org.graalvm.compiler.debug.DebugHandlersFactory; 56 import org.graalvm.compiler.debug.DebugOptions; 57 import org.graalvm.compiler.debug.TTY; 58 import org.graalvm.compiler.debug.PathUtilities; 59 import org.graalvm.compiler.graph.Graph; 60 import org.graalvm.compiler.graph.Node; 61 import org.graalvm.compiler.nodeinfo.Verbosity; 62 import org.graalvm.compiler.nodes.StructuredGraph; 63 import org.graalvm.compiler.nodes.util.GraphUtil; 64 import org.graalvm.compiler.options.OptionValues; 65 import org.graalvm.compiler.serviceprovider.ServiceProvider; 66 67 @ServiceProvider(DebugHandlersFactory.class) 68 public class GraalDebugHandlersFactory implements DebugHandlersFactory { 69 70 private final SnippetReflectionProvider snippetReflection; 71 72 public GraalDebugHandlersFactory() { 73 this.snippetReflection = null; 74 } 75 76 public GraalDebugHandlersFactory(SnippetReflectionProvider snippetReflection) { 77 this.snippetReflection = snippetReflection; 78 } 79 80 @Override 81 public List<DebugHandler> createHandlers(OptionValues options) { 82 List<DebugHandler> handlers = new ArrayList<>(); 83 if (DebugOptions.PrintGraphFile.getValue(options)) { 84 handlers.add(new GraphPrinterDumpHandler((graph) -> createFilePrinter(graph, options, snippetReflection))); 85 } else { 86 handlers.add(new GraphPrinterDumpHandler((graph) -> createNetworkPrinter(graph, options, snippetReflection))); 87 } 88 if (DebugOptions.PrintCanonicalGraphStrings.getValue(options)) { 89 handlers.add(new GraphPrinterDumpHandler((graph) -> createStringPrinter(snippetReflection))); 90 } 91 handlers.add(new NodeDumper()); 92 if (DebugOptions.PrintCFG.getValue(options) || DebugOptions.PrintBackendCFG.getValue(options)) { 93 if (DebugOptions.PrintBinaryGraphs.getValue(options) && DebugOptions.PrintCFG.getValue(options)) { 94 TTY.out.println("Complete C1Visualizer dumping slows down PrintBinaryGraphs: use -Dgraal.PrintCFG=false to disable it"); 95 } 96 handlers.add(new CFGPrinterObserver()); 97 } 98 handlers.add(new NoDeadCodeVerifyHandler()); 99 return handlers; 100 } 101 102 private static class NodeDumper implements DebugDumpHandler { 103 @Override 104 public void dump(DebugContext debug, Object object, String format, Object... arguments) { 105 if (object instanceof Node) { 106 Node node = (Node) object; 107 String location = GraphUtil.approxSourceLocation(node); 108 String nodeName = node.toString(Verbosity.Debugger); 109 if (location != null) { 110 debug.log("Context obj %s (approx. location: %s)", nodeName, location); 111 } else { 112 debug.log("Context obj %s", nodeName); 113 } 114 } 115 } 116 } 117 118 private static CanonicalStringGraphPrinter createStringPrinter(SnippetReflectionProvider snippetReflection) { 119 return new CanonicalStringGraphPrinter(snippetReflection); 120 } 121 122 public static String sanitizedFileName(String n) { 123 /* 124 * First ensure that the name does not contain the directory separator (which would be 125 * considered a valid path). 126 */ 127 String name = n.replace(File.separatorChar, '_'); 128 129 try { 130 Paths.get(name); 131 return name; 132 } catch (InvalidPathException e) { 133 // fall through 134 } 135 StringBuilder buf = new StringBuilder(name.length()); 136 for (int i = 0; i < name.length(); i++) { 137 char c = name.charAt(i); 138 try { 139 Paths.get(String.valueOf(c)); 140 } catch (InvalidPathException e) { 141 buf.append('_'); 142 } 143 buf.append(c); 144 } 145 return buf.toString(); 146 } 147 148 private static GraphPrinter createNetworkPrinter(Graph graph, OptionValues options, SnippetReflectionProvider snippetReflection) throws IOException { 149 String host = PrintGraphHost.getValue(options); 150 int port = PrintBinaryGraphs.getValue(options) ? PrintBinaryGraphPort.getValue(options) : PrintXmlGraphPort.getValue(options); 151 try { 152 GraphPrinter printer; 153 if (DebugOptions.PrintBinaryGraphs.getValue(options)) { 154 printer = new BinaryGraphPrinter(SocketChannel.open(new InetSocketAddress(host, port)), snippetReflection); 155 } else { 156 printer = new IdealGraphPrinter(new Socket(host, port).getOutputStream(), true, snippetReflection); 157 } 158 TTY.println("Connected to the IGV on %s:%d", host, port); 159 return printer; 160 } catch (ClosedByInterruptException | InterruptedIOException e) { 161 /* 162 * Interrupts should not count as errors because they may be caused by a cancelled Graal 163 * compilation. ClosedByInterruptException occurs if the SocketChannel could not be 164 * opened. InterruptedIOException occurs if new Socket(..) was interrupted. 165 */ 166 return null; 167 } catch (IOException e) { 168 if (!DebugOptions.PrintGraphFile.hasBeenSet(options)) { 169 return createFilePrinter(graph, options, snippetReflection); 170 } else { 171 throw new IOException(String.format("Could not connect to the IGV on %s:%d", host, port), e); 172 } 173 } 174 } 175 176 private static final AtomicInteger unknownCompilationId = new AtomicInteger(); 177 178 /** 179 * Creates a new file or directory for dumping based on a given graph and a file extension. 180 * 181 * @param graph a base path name is derived from {@code graph} 182 * @param extension a suffix which if non-null and non-empty added to the end of the returned 183 * path separated by a {@code "."} 184 * @param createDirectory specifies if this is a request to create a directory instead of a file 185 * @return the created directory or file 186 * @throws IOException if there was an error creating the directory or file 187 */ 188 static Path createDumpPath(OptionValues options, Graph graph, String extension, boolean createDirectory) throws IOException { 189 CompilationIdentifier compilationId = CompilationIdentifier.INVALID_COMPILATION_ID; 190 String id = null; 191 String label = null; 192 if (graph instanceof StructuredGraph) { 193 StructuredGraph sgraph = (StructuredGraph) graph; 194 label = getGraphName(sgraph); 195 compilationId = sgraph.compilationId(); 196 if (compilationId == CompilationIdentifier.INVALID_COMPILATION_ID) { 197 id = graph.getClass().getSimpleName() + "-" + sgraph.graphId(); 198 } else { 199 id = compilationId.toString(CompilationIdentifier.Verbosity.ID); 200 } 201 } else { 202 label = graph == null ? null : graph.name != null ? graph.name : graph.toString(); 203 id = "UnknownCompilation-" + unknownCompilationId.incrementAndGet(); 204 } 205 String ext = PathUtilities.formatExtension(extension); 206 Path result = createUnique(DebugOptions.getDumpDirectory(options), id, label, ext, createDirectory); 207 if (ShowDumpFiles.getValue(options) || Assertions.assertionsEnabled()) { 208 TTY.println("Dumping debug output to %s", result.toAbsolutePath().toString()); 209 } 210 return result; 211 } 212 213 /** 214 * A maximum file name length supported by most file systems. There is no platform independent 215 * way to get this in Java. 216 */ 217 private static final int MAX_FILE_NAME_LENGTH = 255; 218 219 private static final String ELLIPSIS = "..."; 220 221 private static Path createUnique(Path dumpDir, String id, String label, String ext, boolean createDirectory) throws IOException { 222 String timestamp = ""; 223 for (;;) { 224 int fileNameLengthWithoutLabel = timestamp.length() + ext.length() + id.length() + "[]".length(); 225 int labelLengthLimit = MAX_FILE_NAME_LENGTH - fileNameLengthWithoutLabel; 226 String fileName; 227 if (labelLengthLimit < ELLIPSIS.length()) { 228 // This means `id` is very long 229 String suffix = timestamp + ext; 230 int idLengthLimit = Math.min(MAX_FILE_NAME_LENGTH - suffix.length(), id.length()); 231 fileName = sanitizedFileName(id.substring(0, idLengthLimit) + suffix); 232 } else { 233 if (label == null) { 234 fileName = sanitizedFileName(id + timestamp + ext); 235 } else { 236 String adjustedLabel = label; 237 if (label.length() > labelLengthLimit) { 238 adjustedLabel = label.substring(0, labelLengthLimit - ELLIPSIS.length()) + ELLIPSIS; 239 } 240 fileName = sanitizedFileName(id + '[' + adjustedLabel + ']' + timestamp + ext); 241 } 242 } 243 Path result = dumpDir.resolve(fileName); 244 try { 245 if (createDirectory) { 246 return Files.createDirectory(result); 247 } else { 248 return Files.createFile(result); 249 } 250 } catch (FileAlreadyExistsException e) { 251 timestamp = "_" + Long.toString(System.currentTimeMillis()); 252 } 253 } 254 } 255 256 private static String getGraphName(StructuredGraph graph) { 257 if (graph.name != null) { 258 return graph.name; 259 } else if (graph.method() != null) { 260 return graph.method().format("%h.%n(%p)").replace(" ", ""); 261 } else { 262 return graph.toString(); 263 } 264 } 265 266 private static GraphPrinter createFilePrinter(Graph graph, OptionValues options, SnippetReflectionProvider snippetReflection) throws IOException { 267 Path path = createDumpPath(options, graph, PrintBinaryGraphs.getValue(options) ? "bgv" : "gv.xml", false); 268 try { 269 GraphPrinter printer; 270 if (DebugOptions.PrintBinaryGraphs.getValue(options)) { 271 printer = new BinaryGraphPrinter(FileChannel.open(path, StandardOpenOption.WRITE), snippetReflection); 272 } else { 273 printer = new IdealGraphPrinter(Files.newOutputStream(path), true, snippetReflection); 274 } 275 return printer; 276 } catch (IOException e) { 277 throw new IOException(String.format("Failed to open %s to dump IGV graphs", path), e); 278 } 279 } 280 }