/*
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.graalvm.compiler.debug.internal;
import static org.graalvm.compiler.debug.GraalDebugConfig.Options.DebugValueFile;
import static org.graalvm.compiler.debug.GraalDebugConfig.Options.DebugValueHumanReadable;
import static org.graalvm.compiler.debug.GraalDebugConfig.Options.DebugValueSummary;
import static org.graalvm.compiler.debug.GraalDebugConfig.Options.DebugValueThreadFilter;
import static org.graalvm.compiler.debug.GraalDebugConfig.Options.SuppressZeroDebugValues;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.graalvm.compiler.debug.CSVUtil;
import org.graalvm.compiler.debug.GraalError;
import org.graalvm.compiler.debug.LogStream;
import org.graalvm.compiler.debug.TTY;
import org.graalvm.compiler.debug.internal.method.MethodMetricsImpl;
import org.graalvm.compiler.debug.internal.method.MethodMetricsPrinter;
/**
* Facility for printing the {@linkplain KeyRegistry#getDebugValues() values} collected across all
* {@link DebugValueMap#getTopLevelMaps() threads}.
*/
public class DebugValuesPrinter {
private static final String COMPUTER_READABLE_FMT = CSVUtil.buildFormatString("%s", "%s", "%s", "%s");
private static final char SCOPE_DELIMITER = '.';
private final MethodMetricsPrinter mmPrinter;
public DebugValuesPrinter() {
this(null);
}
public DebugValuesPrinter(MethodMetricsPrinter mmPrinter) {
this.mmPrinter = mmPrinter;
}
public void printDebugValues() throws GraalError {
TTY.println();
TTY.println("");
List topLevelMaps = DebugValueMap.getTopLevelMaps();
List debugValues = KeyRegistry.getDebugValues();
if (debugValues.size() > 0) {
try {
ArrayList sortedValues = new ArrayList<>(debugValues);
Collections.sort(sortedValues);
String summary = DebugValueSummary.getValue();
if (summary == null) {
summary = "Complete";
}
if (DebugValueThreadFilter.getValue() != null && topLevelMaps.size() != 0) {
topLevelMaps = topLevelMaps.stream().filter(map -> Pattern.compile(DebugValueThreadFilter.getValue()).matcher(map.getName()).find()).collect(Collectors.toList());
if (topLevelMaps.size() == 0) {
TTY.println("Warning: DebugValueThreadFilter=%s eliminated all maps so nothing will be printed", DebugValueThreadFilter.getValue());
}
}
switch (summary) {
case "Name": {
LogStream log = getLogStream();
printSummary(log, topLevelMaps, sortedValues);
break;
}
case "Partial": {
DebugValueMap globalMap = new DebugValueMap("Global");
for (DebugValueMap map : topLevelMaps) {
flattenChildren(map, globalMap);
}
globalMap.normalize();
LogStream log = getLogStream();
printMap(log, new DebugValueScope(null, globalMap), sortedValues);
break;
}
case "Complete": {
DebugValueMap globalMap = new DebugValueMap("Global");
for (DebugValueMap map : topLevelMaps) {
globalMap.addChild(map);
}
globalMap.group();
globalMap.normalize();
LogStream log = getLogStream();
printMap(log, new DebugValueScope(null, globalMap), sortedValues);
break;
}
case "Thread":
for (DebugValueMap map : topLevelMaps) {
TTY.println("Showing the results for thread: " + map.getName());
map.group();
map.normalize();
LogStream log = getLogStream(map.getName().replace(' ', '_'));
printMap(log, new DebugValueScope(null, map), sortedValues);
}
break;
default:
throw new GraalError("Unknown summary type: %s", summary);
}
for (DebugValueMap topLevelMap : topLevelMaps) {
topLevelMap.reset();
}
} catch (Throwable e) {
// Don't want this to change the exit status of the VM
PrintStream err = System.err;
err.println("Error while printing debug values:");
e.printStackTrace();
}
}
if (mmPrinter != null) {
mmPrinter.printMethodMetrics(MethodMetricsImpl.collectedMetrics());
}
TTY.println("");
}
private static LogStream getLogStream() {
return getLogStream(null);
}
private static LogStream getLogStream(String prefix) {
String debugValueFile = DebugValueFile.getValue();
if (debugValueFile != null) {
try {
final String fileName;
if (prefix != null) {
fileName = prefix + '-' + debugValueFile;
} else {
fileName = debugValueFile;
}
LogStream logStream = new LogStream(new FileOutputStream(fileName));
TTY.println("Writing debug values to '%s'", fileName);
return logStream;
} catch (FileNotFoundException e) {
TTY.println("Warning: Could not open debug value log file: %s (defaulting to TTY)", e.getMessage());
}
}
return TTY.out();
}
private void flattenChildren(DebugValueMap map, DebugValueMap globalMap) {
globalMap.addChild(map);
for (DebugValueMap child : map.getChildren()) {
flattenChildren(child, globalMap);
}
map.clearChildren();
}
private void printSummary(LogStream log, List topLevelMaps, List debugValues) {
DebugValueMap result = new DebugValueMap("Summary");
for (int i = debugValues.size() - 1; i >= 0; i--) {
DebugValue debugValue = debugValues.get(i);
int index = debugValue.getIndex();
long total = collectTotal(topLevelMaps, index);
result.setCurrentValue(index, total);
}
printMap(log, new DebugValueScope(null, result), debugValues);
}
private long collectTotal(List maps, int index) {
long total = 0;
for (int i = 0; i < maps.size(); i++) {
DebugValueMap map = maps.get(i);
total += map.getCurrentValue(index);
total += collectTotal(map.getChildren(), index);
}
return total;
}
/**
* Tracks the scope when printing a {@link DebugValueMap}, allowing "empty" scopes to be
* omitted. An empty scope is one in which there are no (nested) non-zero debug values.
*/
static class DebugValueScope {
final DebugValueScope parent;
final int level;
final DebugValueMap map;
private boolean printed;
DebugValueScope(DebugValueScope parent, DebugValueMap map) {
this.parent = parent;
this.map = map;
this.level = parent == null ? 0 : parent.level + 1;
}
public void print(LogStream log) {
if (!printed) {
printed = true;
if (parent != null) {
parent.print(log);
}
printIndent(log, level);
log.printf("%s%n", map.getName());
}
}
public String toRawString() {
return toRaw(new StringBuilder()).toString();
}
private StringBuilder toRaw(StringBuilder stringBuilder) {
final StringBuilder sb = (parent == null) ? stringBuilder : parent.toRaw(stringBuilder).append(SCOPE_DELIMITER);
return sb.append(map.getName());
}
}
private void printMap(LogStream log, DebugValueScope scope, List debugValues) {
if (DebugValueHumanReadable.getValue()) {
printMapHumanReadable(log, scope, debugValues);
} else {
printMapComputerReadable(log, scope, debugValues);
}
}
private void printMapComputerReadable(LogStream log, DebugValueScope scope, List debugValues) {
for (DebugValue value : debugValues) {
long l = scope.map.getCurrentValue(value.getIndex());
if (l != 0 || !SuppressZeroDebugValues.getValue()) {
CSVUtil.Escape.println(log, COMPUTER_READABLE_FMT, scope.toRawString(), value.getName(), value.toRawString(l), value.rawUnit());
}
}
List children = scope.map.getChildren();
for (int i = 0; i < children.size(); i++) {
DebugValueMap child = children.get(i);
printMapComputerReadable(log, new DebugValueScope(scope, child), debugValues);
}
}
private void printMapHumanReadable(LogStream log, DebugValueScope scope, List debugValues) {
for (DebugValue value : debugValues) {
long l = scope.map.getCurrentValue(value.getIndex());
if (l != 0 || !SuppressZeroDebugValues.getValue()) {
scope.print(log);
printIndent(log, scope.level + 1);
log.println(value.getName() + "=" + value.toString(l));
}
}
List children = scope.map.getChildren();
for (int i = 0; i < children.size(); i++) {
DebugValueMap child = children.get(i);
printMapHumanReadable(log, new DebugValueScope(scope, child), debugValues);
}
}
private static void printIndent(LogStream log, int level) {
for (int i = 0; i < level; ++i) {
log.print(" ");
}
log.print("|-> ");
}
public void clearDebugValues() {
List topLevelMaps = DebugValueMap.getTopLevelMaps();
List debugValues = KeyRegistry.getDebugValues();
if (debugValues.size() > 0) {
for (DebugValueMap map : topLevelMaps) {
map.reset();
}
}
if (mmPrinter != null) {
MethodMetricsImpl.clearMM();
}
}
}