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.GraalDebugConfig.asJavaMethod; 26 27 import java.io.IOException; 28 import java.lang.management.ManagementFactory; 29 import java.nio.channels.ClosedByInterruptException; 30 import java.util.ArrayList; 31 import java.util.Arrays; 32 import java.util.Collections; 33 import java.util.Date; 34 import java.util.HashMap; 35 import java.util.List; 36 import java.util.Map; 37 import java.util.WeakHashMap; 38 39 import org.graalvm.compiler.api.replacements.SnippetReflectionProvider; 40 import org.graalvm.compiler.debug.Debug; 41 import org.graalvm.compiler.debug.Debug.Scope; 42 import org.graalvm.compiler.debug.DebugConfig; 43 import org.graalvm.compiler.debug.DebugDumpHandler; 44 import org.graalvm.compiler.debug.DebugDumpScope; 45 import org.graalvm.compiler.debug.GraalDebugConfig; 46 import org.graalvm.compiler.debug.GraalDebugConfig.Options; 47 import org.graalvm.compiler.debug.GraalError; 48 import org.graalvm.compiler.debug.TTY; 49 import org.graalvm.compiler.debug.internal.DebugScope; 50 import org.graalvm.compiler.graph.Graph; 51 import org.graalvm.compiler.nodes.StructuredGraph; 52 import org.graalvm.compiler.phases.contract.NodeCostUtil; 53 54 import jdk.vm.ci.meta.JavaMethod; 55 import jdk.vm.ci.meta.ResolvedJavaMethod; 56 57 //JaCoCo Exclude 58 59 /** 60 * Observes compilation events and uses {@link IdealGraphPrinter} to generate a graph representation 61 * that can be inspected with the <a href="http://kenai.com/projects/igv">Ideal Graph Visualizer</a> 62 * . 63 */ 64 public class GraphPrinterDumpHandler implements DebugDumpHandler { 65 66 private static final int FAILURE_LIMIT = 8; 67 private final GraphPrinterSupplier printerSupplier; 68 protected GraphPrinter printer; 69 private SnippetReflectionProvider snippetReflection; 70 private List<String> previousInlineContext; 71 private int[] dumpIds = {}; 72 private int failuresCount; 73 private Map<Graph, List<String>> inlineContextMap; 74 private final String jvmArguments; 75 private final String sunJavaCommand; 76 77 @FunctionalInterface 78 public interface GraphPrinterSupplier { 79 GraphPrinter get() throws IOException; 80 } 81 82 /** 83 * Creates a new {@link GraphPrinterDumpHandler}. 84 * 85 * @param printerSupplier Supplier used to create the GraphPrinter. Should supply an optional or 86 * null in case of failure. 87 */ 88 public GraphPrinterDumpHandler(GraphPrinterSupplier printerSupplier) { 89 this.printerSupplier = printerSupplier; 90 /* Add the JVM and Java arguments to the graph properties to help identify it. */ 91 this.jvmArguments = String.join(" ", ManagementFactory.getRuntimeMXBean().getInputArguments()); 92 this.sunJavaCommand = System.getProperty("sun.java.command"); 93 } 94 95 private void ensureInitialized() { 96 if (printer == null) { 97 if (failuresCount >= FAILURE_LIMIT) { 98 return; 99 } 100 previousInlineContext = new ArrayList<>(); 101 inlineContextMap = new WeakHashMap<>(); 102 try { 103 printer = printerSupplier.get(); 104 if (snippetReflection != null) { 105 printer.setSnippetReflectionProvider(snippetReflection); 106 } 107 } catch (IOException e) { 108 handleException(e); 109 } 110 } 111 } 112 113 private int nextDumpId() { 114 int depth = previousInlineContext.size(); 115 if (dumpIds.length < depth) { 116 dumpIds = Arrays.copyOf(dumpIds, depth); 117 } 118 return dumpIds[depth - 1]++; 119 } 120 121 @Override 122 @SuppressWarnings("try") 123 public void dump(Object object, final String format, Object... arguments) { 124 if (object instanceof Graph && Options.PrintGraph.getValue(DebugScope.getConfig().getOptions())) { 125 ensureInitialized(); 126 if (printer == null) { 127 return; 128 } 129 final Graph graph = (Graph) object; 130 131 // Get all current JavaMethod instances in the context. 132 List<String> inlineContext = getInlineContext(graph); 133 134 if (inlineContext != previousInlineContext) { 135 Map<Object, Object> properties = new HashMap<>(); 136 properties.put("graph", graph.toString()); 137 addCompilationId(properties, graph); 138 addCFGFileName(properties); 139 if (inlineContext.equals(previousInlineContext)) { 140 /* 141 * two different graphs have the same inline context, so make sure they appear 142 * in different folders by closing and reopening the top scope. 143 */ 144 int inlineDepth = previousInlineContext.size() - 1; 145 closeScope(inlineDepth); 146 openScope(inlineContext.get(inlineDepth), inlineDepth, properties); 147 } else { 148 // Check for method scopes that must be closed since the previous dump. 149 for (int i = 0; i < previousInlineContext.size(); ++i) { 150 if (i >= inlineContext.size() || !inlineContext.get(i).equals(previousInlineContext.get(i))) { 151 for (int inlineDepth = previousInlineContext.size() - 1; inlineDepth >= i; --inlineDepth) { 152 closeScope(inlineDepth); 153 } 154 break; 155 } 156 } 157 // Check for method scopes that must be opened since the previous dump. 158 for (int i = 0; i < inlineContext.size(); ++i) { 159 if (i >= previousInlineContext.size() || !inlineContext.get(i).equals(previousInlineContext.get(i))) { 160 for (int inlineDepth = i; inlineDepth < inlineContext.size(); ++inlineDepth) { 161 openScope(inlineContext.get(inlineDepth), inlineDepth, inlineDepth == inlineContext.size() - 1 ? properties : null); 162 } 163 break; 164 } 165 } 166 } 167 } 168 169 // Save inline context for next dump. 170 previousInlineContext = inlineContext; 171 172 try (Scope s = Debug.sandbox("PrintingGraph", null)) { 173 // Finally, output the graph. 174 Map<Object, Object> properties = new HashMap<>(); 175 properties.put("graph", graph.toString()); 176 properties.put("scope", Debug.currentScope()); 177 if (graph instanceof StructuredGraph) { 178 properties.put("compilationIdentifier", ((StructuredGraph) graph).compilationId()); 179 try { 180 int size = NodeCostUtil.computeGraphSize((StructuredGraph) graph); 181 properties.put("node-cost graph size", size); 182 } catch (Throwable t) { 183 properties.put("node-cost-exception", t.getMessage()); 184 } 185 } 186 addCFGFileName(properties); 187 printer.print(graph, properties, nextDumpId(), format, arguments); 188 } catch (IOException e) { 189 handleException(e); 190 } catch (Throwable e) { 191 throw Debug.handle(e); 192 } 193 } 194 } 195 196 void handleException(IOException e) { 197 if (GraalDebugConfig.Options.DumpingErrorsAreFatal.getValue(DebugScope.getConfig().getOptions())) { 198 throw new GraalError(e); 199 } 200 if (e instanceof ClosedByInterruptException) { 201 /* 202 * The current dumping was aborted by an interrupt so treat this as a transient failure. 203 */ 204 failuresCount = 0; 205 } else { 206 failuresCount++; 207 } 208 printer = null; 209 if (failuresCount > FAILURE_LIMIT) { 210 e.printStackTrace(TTY.out); 211 TTY.println("Too many failures with dumping. Disabling dump in thread " + Thread.currentThread()); 212 } else { 213 TTY.println(e.getMessage()); 214 } 215 } 216 217 private static void addCompilationId(Map<Object, Object> properties, final Graph graph) { 218 if (graph instanceof StructuredGraph) { 219 properties.put("compilationId", ((StructuredGraph) graph).compilationId()); 220 } 221 } 222 223 private static void addCFGFileName(Map<Object, Object> properties) { 224 DebugConfig config = DebugScope.getConfig(); 225 if (config != null) { 226 for (DebugDumpHandler dumpHandler : config.dumpHandlers()) { 227 if (dumpHandler instanceof CFGPrinterObserver) { 228 CFGPrinterObserver cfg = (CFGPrinterObserver) dumpHandler; 229 String path = cfg.getDumpPath(); 230 if (path != null) { 231 properties.put("PrintCFGFileName", path); 232 } 233 return; 234 } 235 } 236 } 237 } 238 239 private List<String> getInlineContext(Graph graph) { 240 List<String> result = inlineContextMap.get(graph); 241 if (result == null) { 242 result = new ArrayList<>(); 243 Object lastMethodOrGraph = null; 244 boolean graphSeen = false; 245 for (Object o : Debug.context()) { 246 if (o == graph) { 247 graphSeen = true; 248 } 249 250 if (o instanceof DebugDumpScope) { 251 DebugDumpScope debugDumpScope = (DebugDumpScope) o; 252 if (debugDumpScope.decorator && !result.isEmpty()) { 253 result.set(result.size() - 1, debugDumpScope.name + ":" + result.get(result.size() - 1)); 254 } else { 255 result.add(debugDumpScope.name); 256 } 257 } else { 258 addMethodContext(result, o, lastMethodOrGraph); 259 } 260 if (o instanceof JavaMethod || o instanceof Graph) { 261 lastMethodOrGraph = o; 262 } 263 } 264 265 if (result.isEmpty()) { 290 /* 291 * Include the current method in the context if there was no previous JavaMethod or 292 * JavaMethodContext or if the method is different or if the method is the same but it 293 * comes from two different graphs. This ensures that recursive call patterns are 294 * handled properly. 295 */ 296 if (lastMethodOrGraph == null || asJavaMethod(lastMethodOrGraph) == null || !asJavaMethod(lastMethodOrGraph).equals(method) || 297 (lastMethodOrGraph != o && lastMethodOrGraph instanceof Graph && o instanceof Graph)) { 298 result.add(method.format("%H.%n(%p)")); 299 } else { 300 /* 301 * This prevents multiple adjacent method context objects for the same method from 302 * resulting in multiple IGV tree levels. This works on the assumption that real 303 * inlining debug scopes will have a graph context object between the inliner and 304 * inlinee context objects. 305 */ 306 } 307 } 308 } 309 310 private void openScope(String name, int inlineDepth, Map<Object, Object> properties) { 311 try { 312 Map<Object, Object> props = properties; 313 if (inlineDepth == 0) { 314 /* Include some VM specific properties at the root. */ 315 if (props == null) { 316 props = new HashMap<>(); 317 } 318 props.put("jvmArguments", jvmArguments); 319 if (sunJavaCommand != null) { 320 props.put("sun.java.command", sunJavaCommand); 321 } 322 props.put("date", new Date().toString()); 323 } 324 printer.beginGroup(name, name, Debug.contextLookup(ResolvedJavaMethod.class), -1, props); 325 } catch (IOException e) { 326 handleException(e); 327 } 328 } 329 330 private void closeScope(int inlineDepth) { 331 dumpIds[inlineDepth] = 0; 332 try { 333 printer.endGroup(); 334 } catch (IOException e) { 335 handleException(e); 336 } 337 } 338 339 @Override 340 public void close() { 341 if (previousInlineContext != null) { 342 for (int inlineDepth = 0; inlineDepth < previousInlineContext.size(); inlineDepth++) { 343 closeScope(inlineDepth); 344 } 345 } 346 if (printer != null) { 347 printer.close(); 348 printer = null; 349 } 350 } 351 352 @Override 353 public void addCapability(Object capability) { 354 if (capability instanceof SnippetReflectionProvider) { 355 snippetReflection = (SnippetReflectionProvider) capability; 356 if (printer != null && printer.getSnippetReflectionProvider() == null) { 357 printer.setSnippetReflectionProvider(snippetReflection); 358 } 359 } 360 } 361 } | 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.DebugConfig.asJavaMethod; 26 27 import java.io.IOException; 28 import java.lang.management.ManagementFactory; 29 import java.nio.channels.ClosedByInterruptException; 30 import java.util.ArrayList; 31 import java.util.Arrays; 32 import java.util.Collections; 33 import java.util.Date; 34 import java.util.HashMap; 35 import java.util.List; 36 import java.util.Map; 37 import java.util.WeakHashMap; 38 39 import org.graalvm.compiler.debug.DebugContext; 40 import org.graalvm.compiler.debug.DebugDumpHandler; 41 import org.graalvm.compiler.debug.DebugDumpScope; 42 import org.graalvm.compiler.debug.DebugOptions; 43 import org.graalvm.compiler.debug.GraalError; 44 import org.graalvm.compiler.debug.TTY; 45 import org.graalvm.compiler.graph.Graph; 46 import org.graalvm.compiler.nodes.StructuredGraph; 47 import org.graalvm.compiler.options.OptionValues; 48 import org.graalvm.compiler.phases.contract.NodeCostUtil; 49 50 import jdk.vm.ci.meta.JavaMethod; 51 import jdk.vm.ci.meta.ResolvedJavaMethod; 52 53 //JaCoCo Exclude 54 55 /** 56 * Observes compilation events and uses {@link IdealGraphPrinter} to generate a graph representation 57 * that can be inspected with the Graph Visualizer. 58 */ 59 public class GraphPrinterDumpHandler implements DebugDumpHandler { 60 61 private static final int FAILURE_LIMIT = 8; 62 private final GraphPrinterSupplier printerSupplier; 63 protected GraphPrinter printer; 64 private List<String> previousInlineContext; 65 private int[] dumpIds = {}; 66 private int failuresCount; 67 private Map<Graph, List<String>> inlineContextMap; 68 private final String jvmArguments; 69 private final String sunJavaCommand; 70 71 @FunctionalInterface 72 public interface GraphPrinterSupplier { 73 GraphPrinter get(Graph graph) throws IOException; 74 } 75 76 /** 77 * Creates a new {@link GraphPrinterDumpHandler}. 78 * 79 * @param printerSupplier Supplier used to create the GraphPrinter. Should supply an optional or 80 * null in case of failure. 81 */ 82 public GraphPrinterDumpHandler(GraphPrinterSupplier printerSupplier) { 83 this.printerSupplier = printerSupplier; 84 /* Add the JVM and Java arguments to the graph properties to help identify it. */ 85 this.jvmArguments = String.join(" ", ManagementFactory.getRuntimeMXBean().getInputArguments()); 86 this.sunJavaCommand = System.getProperty("sun.java.command"); 87 } 88 89 private void ensureInitialized(Graph graph) { 90 if (printer == null) { 91 if (failuresCount >= FAILURE_LIMIT) { 92 return; 93 } 94 previousInlineContext = new ArrayList<>(); 95 inlineContextMap = new WeakHashMap<>(); 96 DebugContext debug = graph.getDebug(); 97 try { 98 printer = printerSupplier.get(graph); 99 } catch (IOException e) { 100 handleException(debug, e); 101 } 102 } 103 } 104 105 private int nextDumpId() { 106 int depth = previousInlineContext.size(); 107 if (dumpIds.length < depth) { 108 dumpIds = Arrays.copyOf(dumpIds, depth); 109 } 110 return dumpIds[depth - 1]++; 111 } 112 113 @Override 114 @SuppressWarnings("try") 115 public void dump(DebugContext debug, Object object, final String format, Object... arguments) { 116 OptionValues options = debug.getOptions(); 117 if (object instanceof Graph && DebugOptions.PrintGraph.getValue(options)) { 118 final Graph graph = (Graph) object; 119 ensureInitialized(graph); 120 if (printer == null) { 121 return; 122 } 123 124 // Get all current JavaMethod instances in the context. 125 List<String> inlineContext = getInlineContext(graph); 126 127 if (inlineContext != previousInlineContext) { 128 Map<Object, Object> properties = new HashMap<>(); 129 properties.put("graph", graph.toString()); 130 addCompilationId(properties, graph); 131 if (inlineContext.equals(previousInlineContext)) { 132 /* 133 * two different graphs have the same inline context, so make sure they appear 134 * in different folders by closing and reopening the top scope. 135 */ 136 int inlineDepth = previousInlineContext.size() - 1; 137 closeScope(debug, inlineDepth); 138 openScope(debug, inlineContext.get(inlineDepth), inlineDepth, properties); 139 } else { 140 // Check for method scopes that must be closed since the previous dump. 141 for (int i = 0; i < previousInlineContext.size(); ++i) { 142 if (i >= inlineContext.size() || !inlineContext.get(i).equals(previousInlineContext.get(i))) { 143 for (int inlineDepth = previousInlineContext.size() - 1; inlineDepth >= i; --inlineDepth) { 144 closeScope(debug, inlineDepth); 145 } 146 break; 147 } 148 } 149 // Check for method scopes that must be opened since the previous dump. 150 for (int i = 0; i < inlineContext.size(); ++i) { 151 if (i >= previousInlineContext.size() || !inlineContext.get(i).equals(previousInlineContext.get(i))) { 152 for (int inlineDepth = i; inlineDepth < inlineContext.size(); ++inlineDepth) { 153 openScope(debug, inlineContext.get(inlineDepth), inlineDepth, inlineDepth == inlineContext.size() - 1 ? properties : null); 154 } 155 break; 156 } 157 } 158 } 159 } 160 161 // Save inline context for next dump. 162 previousInlineContext = inlineContext; 163 164 try (DebugContext.Scope s = debug.sandbox("PrintingGraph", null)) { 165 // Finally, output the graph. 166 Map<Object, Object> properties = new HashMap<>(); 167 properties.put("graph", graph.toString()); 168 properties.put("scope", debug.getCurrentScopeName()); 169 if (graph instanceof StructuredGraph) { 170 properties.put("compilationIdentifier", ((StructuredGraph) graph).compilationId()); 171 try { 172 int size = NodeCostUtil.computeGraphSize((StructuredGraph) graph); 173 properties.put("node-cost graph size", size); 174 } catch (Throwable t) { 175 properties.put("node-cost-exception", t.getMessage()); 176 } 177 } 178 printer.print(debug, graph, properties, nextDumpId(), format, arguments); 179 } catch (IOException e) { 180 handleException(debug, e); 181 } catch (Throwable e) { 182 throw debug.handle(e); 183 } 184 } 185 } 186 187 void handleException(DebugContext debug, IOException e) { 188 if (debug != null && DebugOptions.DumpingErrorsAreFatal.getValue(debug.getOptions())) { 189 throw new GraalError(e); 190 } 191 if (e instanceof ClosedByInterruptException) { 192 /* 193 * The current dumping was aborted by an interrupt so treat this as a transient failure. 194 */ 195 failuresCount = 0; 196 } else { 197 failuresCount++; 198 } 199 printer = null; 200 e.printStackTrace(TTY.out); 201 if (failuresCount > FAILURE_LIMIT) { 202 TTY.println("Too many failures with dumping. Disabling dump in thread " + Thread.currentThread()); 203 } 204 } 205 206 private static void addCompilationId(Map<Object, Object> properties, final Graph graph) { 207 if (graph instanceof StructuredGraph) { 208 properties.put("compilationId", ((StructuredGraph) graph).compilationId()); 209 } 210 } 211 212 private List<String> getInlineContext(Graph graph) { 213 List<String> result = inlineContextMap.get(graph); 214 if (result == null) { 215 result = new ArrayList<>(); 216 Object lastMethodOrGraph = null; 217 boolean graphSeen = false; 218 DebugContext debug = graph.getDebug(); 219 for (Object o : debug.context()) { 220 if (o == graph) { 221 graphSeen = true; 222 } 223 224 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 } else { 232 addMethodContext(result, o, lastMethodOrGraph); 233 } 234 if (o instanceof JavaMethod || o instanceof Graph) { 235 lastMethodOrGraph = o; 236 } 237 } 238 239 if (result.isEmpty()) { 264 /* 265 * Include the current method in the context if there was no previous JavaMethod or 266 * JavaMethodContext or if the method is different or if the method is the same but it 267 * comes from two different graphs. This ensures that recursive call patterns are 268 * handled properly. 269 */ 270 if (lastMethodOrGraph == null || asJavaMethod(lastMethodOrGraph) == null || !asJavaMethod(lastMethodOrGraph).equals(method) || 271 (lastMethodOrGraph != o && lastMethodOrGraph instanceof Graph && o instanceof Graph)) { 272 result.add(method.format("%H.%n(%p)")); 273 } else { 274 /* 275 * This prevents multiple adjacent method context objects for the same method from 276 * resulting in multiple IGV tree levels. This works on the assumption that real 277 * inlining debug scopes will have a graph context object between the inliner and 278 * inlinee context objects. 279 */ 280 } 281 } 282 } 283 284 private void openScope(DebugContext debug, String name, int inlineDepth, Map<Object, Object> properties) { 285 try { 286 Map<Object, Object> props = properties; 287 if (inlineDepth == 0) { 288 /* Include some VM specific properties at the root. */ 289 if (props == null) { 290 props = new HashMap<>(); 291 } 292 props.put("jvmArguments", jvmArguments); 293 if (sunJavaCommand != null) { 294 props.put("sun.java.command", sunJavaCommand); 295 } 296 props.put("date", new Date().toString()); 297 } 298 printer.beginGroup(debug, name, name, debug.contextLookup(ResolvedJavaMethod.class), -1, props); 299 } catch (IOException e) { 300 handleException(debug, e); 301 } 302 } 303 304 private void closeScope(DebugContext debug, int inlineDepth) { 305 dumpIds[inlineDepth] = 0; 306 try { 307 if (printer != null) { 308 printer.endGroup(); 309 } 310 } catch (IOException e) { 311 handleException(debug, e); 312 } 313 } 314 315 @Override 316 public void close() { 317 if (previousInlineContext != null) { 318 for (int inlineDepth = 0; inlineDepth < previousInlineContext.size(); inlineDepth++) { 319 closeScope(null, inlineDepth); 320 } 321 } 322 if (printer != null) { 323 printer.close(); 324 printer = null; 325 } 326 } 327 } |