1 /*
   2  * Copyright (c) 2014, 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.debug.internal;
  24 
  25 import static org.graalvm.compiler.debug.GraalDebugConfig.Options.DebugValueFile;
  26 import static org.graalvm.compiler.debug.GraalDebugConfig.Options.DebugValueHumanReadable;
  27 import static org.graalvm.compiler.debug.GraalDebugConfig.Options.DebugValueSummary;
  28 import static org.graalvm.compiler.debug.GraalDebugConfig.Options.DebugValueThreadFilter;
  29 import static org.graalvm.compiler.debug.GraalDebugConfig.Options.SuppressZeroDebugValues;
  30 
  31 import java.io.FileNotFoundException;
  32 import java.io.FileOutputStream;
  33 import java.io.PrintStream;
  34 import java.util.ArrayList;
  35 import java.util.Collections;
  36 import java.util.List;
  37 import java.util.regex.Pattern;
  38 import java.util.stream.Collectors;
  39 
  40 import org.graalvm.compiler.debug.CSVUtil;
  41 import org.graalvm.compiler.debug.GraalError;
  42 import org.graalvm.compiler.debug.LogStream;
  43 import org.graalvm.compiler.debug.TTY;
  44 import org.graalvm.compiler.debug.internal.method.MethodMetricsImpl;
  45 import org.graalvm.compiler.debug.internal.method.MethodMetricsPrinter;
  46 
  47 /**
  48  * Facility for printing the {@linkplain KeyRegistry#getDebugValues() values} collected across all
  49  * {@link DebugValueMap#getTopLevelMaps() threads}.
  50  */
  51 public class DebugValuesPrinter {
  52     private static final String COMPUTER_READABLE_FMT = CSVUtil.buildFormatString("%s", "%s", "%s", "%s");
  53     private static final char SCOPE_DELIMITER = '.';
  54     private final MethodMetricsPrinter mmPrinter;
  55 
  56     public DebugValuesPrinter() {
  57         this(null);
  58     }
  59 
  60     public DebugValuesPrinter(MethodMetricsPrinter mmPrinter) {
  61         this.mmPrinter = mmPrinter;
  62     }
  63 
  64     public void printDebugValues() throws GraalError {
  65         TTY.println();
  66         TTY.println("<DebugValues>");
  67         List<DebugValueMap> topLevelMaps = DebugValueMap.getTopLevelMaps();
  68         List<DebugValue> debugValues = KeyRegistry.getDebugValues();
  69         if (debugValues.size() > 0) {
  70             try {
  71                 ArrayList<DebugValue> sortedValues = new ArrayList<>(debugValues);
  72                 Collections.sort(sortedValues);
  73 
  74                 String summary = DebugValueSummary.getValue();
  75                 if (summary == null) {
  76                     summary = "Complete";
  77                 }
  78                 if (DebugValueThreadFilter.getValue() != null && topLevelMaps.size() != 0) {
  79                     topLevelMaps = topLevelMaps.stream().filter(map -> Pattern.compile(DebugValueThreadFilter.getValue()).matcher(map.getName()).find()).collect(Collectors.toList());
  80                     if (topLevelMaps.size() == 0) {
  81                         TTY.println("Warning: DebugValueThreadFilter=%s eliminated all maps so nothing will be printed", DebugValueThreadFilter.getValue());
  82                     }
  83                 }
  84                 switch (summary) {
  85                     case "Name": {
  86                         LogStream log = getLogStream();
  87                         printSummary(log, topLevelMaps, sortedValues);
  88                         break;
  89                     }
  90                     case "Partial": {
  91                         DebugValueMap globalMap = new DebugValueMap("Global");
  92                         for (DebugValueMap map : topLevelMaps) {
  93                             flattenChildren(map, globalMap);
  94                         }
  95                         globalMap.normalize();
  96                         LogStream log = getLogStream();
  97                         printMap(log, new DebugValueScope(null, globalMap), sortedValues);
  98                         break;
  99                     }
 100                     case "Complete": {
 101                         DebugValueMap globalMap = new DebugValueMap("Global");
 102                         for (DebugValueMap map : topLevelMaps) {
 103                             globalMap.addChild(map);
 104                         }
 105                         globalMap.group();
 106                         globalMap.normalize();
 107                         LogStream log = getLogStream();
 108                         printMap(log, new DebugValueScope(null, globalMap), sortedValues);
 109                         break;
 110                     }
 111                     case "Thread":
 112                         for (DebugValueMap map : topLevelMaps) {
 113                             TTY.println("Showing the results for thread: " + map.getName());
 114                             map.group();
 115                             map.normalize();
 116                             LogStream log = getLogStream(map.getName().replace(' ', '_'));
 117                             printMap(log, new DebugValueScope(null, map), sortedValues);
 118                         }
 119                         break;
 120                     default:
 121                         throw new GraalError("Unknown summary type: %s", summary);
 122                 }
 123                 for (DebugValueMap topLevelMap : topLevelMaps) {
 124                     topLevelMap.reset();
 125                 }
 126             } catch (Throwable e) {
 127                 // Don't want this to change the exit status of the VM
 128                 PrintStream err = System.err;
 129                 err.println("Error while printing debug values:");
 130                 e.printStackTrace();
 131             }
 132         }
 133         if (mmPrinter != null) {
 134             mmPrinter.printMethodMetrics(MethodMetricsImpl.collectedMetrics());
 135         }
 136         TTY.println("</DebugValues>");
 137     }
 138 
 139     private static LogStream getLogStream() {
 140         return getLogStream(null);
 141     }
 142 
 143     private static LogStream getLogStream(String prefix) {
 144         String debugValueFile = DebugValueFile.getValue();
 145         if (debugValueFile != null) {
 146             try {
 147                 final String fileName;
 148                 if (prefix != null) {
 149                     fileName = prefix + '-' + debugValueFile;
 150                 } else {
 151                     fileName = debugValueFile;
 152                 }
 153                 LogStream logStream = new LogStream(new FileOutputStream(fileName));
 154                 TTY.println("Writing debug values to '%s'", fileName);
 155                 return logStream;
 156             } catch (FileNotFoundException e) {
 157                 TTY.println("Warning: Could not open debug value log file: %s (defaulting to TTY)", e.getMessage());
 158             }
 159         }
 160         return TTY.out();
 161     }
 162 
 163     private void flattenChildren(DebugValueMap map, DebugValueMap globalMap) {
 164         globalMap.addChild(map);
 165         for (DebugValueMap child : map.getChildren()) {
 166             flattenChildren(child, globalMap);
 167         }
 168         map.clearChildren();
 169     }
 170 
 171     private void printSummary(LogStream log, List<DebugValueMap> topLevelMaps, List<DebugValue> debugValues) {
 172         DebugValueMap result = new DebugValueMap("Summary");
 173         for (int i = debugValues.size() - 1; i >= 0; i--) {
 174             DebugValue debugValue = debugValues.get(i);
 175             int index = debugValue.getIndex();
 176             long total = collectTotal(topLevelMaps, index);
 177             result.setCurrentValue(index, total);
 178         }
 179         printMap(log, new DebugValueScope(null, result), debugValues);
 180     }
 181 
 182     private long collectTotal(List<DebugValueMap> maps, int index) {
 183         long total = 0;
 184         for (int i = 0; i < maps.size(); i++) {
 185             DebugValueMap map = maps.get(i);
 186             total += map.getCurrentValue(index);
 187             total += collectTotal(map.getChildren(), index);
 188         }
 189         return total;
 190     }
 191 
 192     /**
 193      * Tracks the scope when printing a {@link DebugValueMap}, allowing "empty" scopes to be
 194      * omitted. An empty scope is one in which there are no (nested) non-zero debug values.
 195      */
 196     static class DebugValueScope {
 197 
 198         final DebugValueScope parent;
 199         final int level;
 200         final DebugValueMap map;
 201         private boolean printed;
 202 
 203         DebugValueScope(DebugValueScope parent, DebugValueMap map) {
 204             this.parent = parent;
 205             this.map = map;
 206             this.level = parent == null ? 0 : parent.level + 1;
 207         }
 208 
 209         public void print(LogStream log) {
 210             if (!printed) {
 211                 printed = true;
 212                 if (parent != null) {
 213                     parent.print(log);
 214                 }
 215                 printIndent(log, level);
 216                 log.printf("%s%n", map.getName());
 217             }
 218         }
 219 
 220         public String toRawString() {
 221             return toRaw(new StringBuilder()).toString();
 222         }
 223 
 224         private StringBuilder toRaw(StringBuilder stringBuilder) {
 225             final StringBuilder sb = (parent == null) ? stringBuilder : parent.toRaw(stringBuilder).append(SCOPE_DELIMITER);
 226             return sb.append(map.getName());
 227         }
 228 
 229     }
 230 
 231     private void printMap(LogStream log, DebugValueScope scope, List<DebugValue> debugValues) {
 232         if (DebugValueHumanReadable.getValue()) {
 233             printMapHumanReadable(log, scope, debugValues);
 234         } else {
 235             printMapComputerReadable(log, scope, debugValues);
 236         }
 237     }
 238 
 239     private void printMapComputerReadable(LogStream log, DebugValueScope scope, List<DebugValue> debugValues) {
 240 
 241         for (DebugValue value : debugValues) {
 242             long l = scope.map.getCurrentValue(value.getIndex());
 243             if (l != 0 || !SuppressZeroDebugValues.getValue()) {
 244                 CSVUtil.Escape.println(log, COMPUTER_READABLE_FMT, scope.toRawString(), value.getName(), value.toRawString(l), value.rawUnit());
 245             }
 246         }
 247 
 248         List<DebugValueMap> children = scope.map.getChildren();
 249         for (int i = 0; i < children.size(); i++) {
 250             DebugValueMap child = children.get(i);
 251             printMapComputerReadable(log, new DebugValueScope(scope, child), debugValues);
 252         }
 253     }
 254 
 255     private void printMapHumanReadable(LogStream log, DebugValueScope scope, List<DebugValue> debugValues) {
 256 
 257         for (DebugValue value : debugValues) {
 258             long l = scope.map.getCurrentValue(value.getIndex());
 259             if (l != 0 || !SuppressZeroDebugValues.getValue()) {
 260                 scope.print(log);
 261                 printIndent(log, scope.level + 1);
 262                 log.println(value.getName() + "=" + value.toString(l));
 263             }
 264         }
 265 
 266         List<DebugValueMap> children = scope.map.getChildren();
 267         for (int i = 0; i < children.size(); i++) {
 268             DebugValueMap child = children.get(i);
 269             printMapHumanReadable(log, new DebugValueScope(scope, child), debugValues);
 270         }
 271     }
 272 
 273     private static void printIndent(LogStream log, int level) {
 274         for (int i = 0; i < level; ++i) {
 275             log.print("    ");
 276         }
 277         log.print("|-> ");
 278     }
 279 
 280     public void clearDebugValues() {
 281         List<DebugValueMap> topLevelMaps = DebugValueMap.getTopLevelMaps();
 282         List<DebugValue> debugValues = KeyRegistry.getDebugValues();
 283         if (debugValues.size() > 0) {
 284             for (DebugValueMap map : topLevelMaps) {
 285                 map.reset();
 286             }
 287         }
 288         if (mmPrinter != null) {
 289             MethodMetricsImpl.clearMM();
 290         }
 291     }
 292 }