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}