1 /*
   2  * Copyright (c) 2011, 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.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()) {
 240                 result.add(graph.toString());
 241                 graphSeen = true;
 242             }
 243             // Reverse list such that inner method comes after outer method.
 244             Collections.reverse(result);
 245             if (!graphSeen) {
 246                 /*
 247                  * The graph isn't in any context but is being processed within another graph so add
 248                  * it to the end of the scopes.
 249                  */
 250                 if (asJavaMethod(graph) != null) {
 251                     addMethodContext(result, graph, lastMethodOrGraph);
 252                 } else {
 253                     result.add(graph.toString());
 254                 }
 255             }
 256             inlineContextMap.put(graph, result);
 257         }
 258         return result;
 259     }
 260 
 261     private static void addMethodContext(List<String> result, Object o, Object lastMethodOrGraph) {
 262         JavaMethod method = asJavaMethod(o);
 263         if (method != null) {
 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 }