/*
* Copyright (c) 2007, 2012, 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 com.sun.max.vm.hosted;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
import java.util.List;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.*;
import com.sun.max.program.*;
import com.sun.max.program.option.*;
import com.sun.max.util.*;
import com.sun.max.vm.hosted.CompiledPrototype.Link;
import com.sun.max.vm.hosted.CompiledPrototype.Link.Relationship;
/**
* A mechanism for {@linkplain #saveTree(DataOutputStream, Collection) saving},
* {@linkplain #loadTree(DataInputStream) loading} and
* {@linkplain #printTree(Node, boolean, PrintWriter, boolean) printing}
* the causality spanning-tree of the {@linkplain CompiledPrototype#links() method graph} in an
* {@linkplain BootImageGenerator image}.
*/
public final class BootImageMethodTree {
private BootImageMethodTree() {
}
/**
* A node in the method tree, which contains links to subnodes. Each link has a specified relationship,
* which helps to diagnose exactly why a node was included into the graph.
*/
public static class Node implements Comparable<Node> {
private final String name;
private Node parent;
private TreeSet<Node> children;
private Relationship relationshipToParent;
/**
* Creates a new node with the specified name.
*
* @param name the name of the node
*/
public Node(String name) {
this.name = name;
}
/**
* Adds a link from this node to a child node.
*
* @param child the node that is a new child
* @param relationshipToParent the relationship that caused the child to be added
*/
void addChild(Node child, Relationship relationshipToParent) {
if (children == null) {
children = new TreeSet<Node>();
}
assert child != this : child.name + " == " + name;
children.add(child);
child.relationshipToParent = relationshipToParent;
child.parent = this;
}
/**
* Returns a sequence of all the children from this node.
*
* @return a sequence of all the nodes that are children of this node
*/
public Collection<Node> children() {
if (children == null) {
return Collections.emptyList();
}
return children;
}
/**
* Remove all children nodes of this node that do not match the specified predicate.
*
* @param predicate a predicate which decides whether a node should be included
* @return {@code null} if the predicate is not true for this object or any
* of its descendants; this node, with the children that do not match the predicate
* removed otherwise
*/
public Node prune(Predicate<Node> predicate) {
final Node pruned = new Node(name);
pruned.relationshipToParent = relationshipToParent;
if (children != null) {
for (Node child : children) {
final Node prunedChild = child == this ? child : child.prune(predicate);
if (prunedChild != null) {
pruned.addChild(prunedChild, prunedChild.relationshipToParent);
}
}
}
if (pruned.children != null || predicate.evaluate(this)) {
return pruned;
}
return null;
}
private static final String OTHER_SIBLING_PREFIX = "`-- ";
private static final String LAST_SIBLING_PREFIX = "|-- ";
private static final String OTHER_CHILD_INDENT = "| ";
private static final String LAST_CHILD_INDENT = " ";
/**
* Print the tree out in a textual form.
*
* @param node the node to print
* @param showTreeLines a boolean indicating whether to draw the tree lines
* @param printWriter the writer to which to print this tree
* @param prefix a prefix to add to this node
* @param lastChild a boolean indiciating whether to print the last child
*/
private static void printTree(Node node, boolean showTreeLines, PrintWriter printWriter, String prefix, boolean lastChild) {
printWriter.println(prefix + (showTreeLines ? (!lastChild ? LAST_SIBLING_PREFIX : OTHER_SIBLING_PREFIX) : " ") + node);
final Collection<Node> children = node.children();
if (!children.isEmpty()) {
final String childPrefix = prefix + (showTreeLines ? (lastChild ? LAST_CHILD_INDENT : OTHER_CHILD_INDENT) : " ");
for (final Iterator<Node> iterator = children.iterator(); iterator.hasNext();) {
final Node child = iterator.next();
assert child != node;
printTree(child, showTreeLines, printWriter, childPrefix, !iterator.hasNext());
printWriter.flush();
}
}
}
/**
* Converts this node to a string.
* @return a string representation of this node
*/
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
if (relationshipToParent != null) {
sb.append(relationshipToParent.asParent).append(' ');
}
sb.append(name);
return sb.toString();
}
/**
* Create a piece of GUI tree for this node and all of its children.
* @return a JTree instance representing the object tree starting at this node
*/
public MethodTreeNode buildTree() {
if (++btc % 100000 == 0) {
Trace.line(1, "node: " + btc);
}
MethodTreeNode n = new MethodTreeNode(this);
if (children != null) {
for (Node child : children) {
n.add(child.buildTree());
}
}
return n;
}
private static int btc = 0;
@Override
public int compareTo(Node o) {
return name.compareTo(o.name);
}
}
/**
* Save this tree to a file in a compact, binary format.
*
* @param dataOutputStream the data output stream to which to write the tree
* @param links the links to write to the data output stream
* @throws IOException if there is a problem writing to the output stream
*/
public static void saveTree(DataOutputStream dataOutputStream, Collection<Link> links) throws IOException {
final Map<String, Integer> methodPool = new HashMap<String, Integer>();
final List<String> methodIds = new ArrayList<String>(links.size() * 2);
int methodId = 0;
methodPool.put("", methodId++);
for (Link link : links) {
final String childId = link.childId();
if (!methodPool.containsKey(childId)) {
methodIds.add(childId);
methodPool.put(childId, methodId++);
}
final String parentId = link.parentId();
if (parentId != null && !methodPool.containsKey(parentId)) {
if (childId.equals(parentId)) {
ProgramWarning.message("link with same name for parent and child: " + childId);
}
methodIds.add(parentId);
methodPool.put(parentId, methodId++);
}
}
assert methodId == methodPool.size();
assert methodId == methodIds.size() + 1;
dataOutputStream.writeInt(methodPool.size());
final Iterator<String> iterator = methodIds.iterator();
while (iterator.hasNext()) {
dataOutputStream.writeUTF(iterator.next());
}
for (Link link : links) {
final String childId = link.childId();
dataOutputStream.writeInt(methodPool.get(childId));
final String referrerName = link.parentId();
if (referrerName != null) {
dataOutputStream.writeInt(methodPool.get(referrerName));
dataOutputStream.writeByte(link.relationship.ordinal());
} else {
dataOutputStream.writeInt(0);
}
}
}
/**
* Loads a tree that was saved by {@linkplain #saveTree(DataOutputStream, Collection) this method}.
*
* @param dataInputStream a stream containing a saved tree
* @return the roots of the loaded tree
*/
public static Set<Node> loadTree(DataInputStream dataInputStream) throws IOException {
final TreeSet<Node> roots = new TreeSet<Node>();
final int methodPoolSize = dataInputStream.readInt();
final Node[] methodPool = new Node[methodPoolSize];
for (int i = 1; i != methodPoolSize; ++i) {
final String methodName = dataInputStream.readUTF();
methodPool[i] = new Node(methodName);
}
final Relationship[] values = Relationship.values();
while (dataInputStream.available() != 0) {
final int childId = dataInputStream.readInt();
final Node child = methodPool[childId];
final int parentId = dataInputStream.readInt();
if (parentId != 0) {
final Node parent = methodPool[parentId];
final Relationship relationship = values[dataInputStream.readByte()];
parent.addChild(child, relationship);
} else {
roots.add(child);
}
}
return roots;
}
/**
* Prints the tree rooted at a given node to a given print writer. The format of the dump is similar to the output
* of the tree(1) utility that list contents of directories in a tree-like format.
*
* @param node the root of the tree to dump
* @param printWriter where to print the dump
* @param lastChild specifies if {@code node} is the last child of its parent that will be dumped
*/
public static void printTree(Node node, boolean showTreeLines, PrintWriter printWriter, boolean lastChild) {
Node.printTree(node, showTreeLines, printWriter, "", lastChild);
}
/**
* Create and show a GUI for viewing the object tree.
*/
private static void viewInGUI(final Set<Node> roots) {
// build tree and make the root objects shown
DefaultMutableTreeNode treeRoot = new DefaultMutableTreeNode("roots");
final JTree tree = new JTree(treeRoot);
for (Node root : roots) {
treeRoot.add(root.buildTree());
}
tree.expandRow(0);
tree.expandRow(1);
tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
// open GUI
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame("Method Tree");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
MethodTreeView otv = new MethodTreeView(tree);
frame.add(otv);
frame.pack();
frame.setVisible(true);
}
});
}
/**
* GUI class.
*/
static class MethodTreeView extends JPanel {
JLabel info;
JPanel navigation;
JTextField searchField;
JTree tree;
JCheckBox matchCase;
ArrayList<TreeNode> searchHits = new ArrayList<TreeNode>();
int searchHit = -1;
JLabel hitPos;
JButton prevHit;
JButton nextHit;
void redoSearch() {
ArrayList<TreeNode> searchHits = new ArrayList<TreeNode>();
String searchText = searchField.getText();
if (searchText.isEmpty()) {
searchHits.clear();
searchHit = -1;
searchField.setBackground(Color.WHITE);
} else {
if (!matchCase.isSelected()) {
searchText = searchText.toLowerCase();
}
DefaultMutableTreeNode root = (DefaultMutableTreeNode) tree.getModel().getRoot();
Enumeration nodes = root.preorderEnumeration();
while (nodes.hasMoreElements()) {
TreeNode node = (TreeNode) nodes.nextElement();
String nodeText = node.toString();
if (!matchCase.isSelected()) {
nodeText = nodeText.toLowerCase();
}
if (nodeText.contains(searchText)) {
searchHits.add(node);
}
}
searchField.setBackground(searchHits.isEmpty() ? Color.RED : Color.WHITE);
this.searchHit = searchHits.size() - 1;
this.searchHits = searchHits;
}
prevHit.setEnabled(!searchHits.isEmpty());
nextHit.setEnabled(!searchHits.isEmpty());
hitPos.setText((searchHit + 1) + " of " + searchHits.size());
}
MethodTreeView(final JTree t) {
tree = t;
setLayout(new BorderLayout());
// enable info display
info = new JLabel("Select a tree node to have its information shown here.");
// handle selection
tree.addTreeSelectionListener(new TreeSelectionListener() {
@Override
public void valueChanged(TreeSelectionEvent e) {
DefaultMutableTreeNode n = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
if (n == null) {
return;
}
String text;
if (n == tree.getModel().getRoot()) {
text = n.toString();
} else {
text = ((MethodTreeNode) n).getDetails();
}
info.setText(text);
}
});
// navigation controls
// controls for address and node inspection
navigation = new JPanel();
navigation.setLayout(new BoxLayout(navigation, BoxLayout.X_AXIS));
hitPos = new JLabel("0 of 0") {
@Override
public void setText(String text) {
while (text.length() < 25) {
text = ' ' + text;
}
super.setText(text + ' ');
}
};
navigation.add(hitPos);
searchField = new JTextField(10);
searchField.getDocument().addDocumentListener(new DocumentListener() {
public void changedUpdate(DocumentEvent e) {
}
public void insertUpdate(DocumentEvent event) {
redoSearch();
}
public void removeUpdate(DocumentEvent e) {
redoSearch();
}
});
navigation.add(searchField);
prevHit = new JButton(new AbstractAction("^") {
@Override
public void actionPerformed(ActionEvent e) {
int hits = searchHits.size();
if (hits != 0) {
if (--searchHit < 0) {
searchHit = hits - 1;
}
jumpToNode();
}
}
});
nextHit = new JButton(new AbstractAction("v") {
@Override
public void actionPerformed(ActionEvent e) {
int hits = searchHits.size();
if (hits != 0) {
if (++searchHit >= hits) {
searchHit = 0;
}
jumpToNode();
}
}
});
navigation.add(prevHit);
navigation.add(nextHit);
matchCase = new JCheckBox("Match case");
matchCase.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
redoSearch();
}
});
navigation.add(matchCase);
// control for jumping to a node's parent
navigation.add(new JSeparator(SwingConstants.VERTICAL));
navigation.add(new JButton(new AbstractAction("jump to parent") {
@Override
public void actionPerformed(ActionEvent e) {
if (tree.getSelectionPath() == null) {
return;
}
TreePath path = tree.getSelectionPath().getParentPath();
if (path != null) {
tree.setSelectionPath(path);
tree.scrollPathToVisible(path);
}
}
}));
// control for resetting tree (back to start view)
navigation.add(new JSeparator(SwingConstants.VERTICAL));
navigation.add(new JButton(new AbstractAction("reset tree view") {
@Override
public void actionPerformed(ActionEvent e) {
for (int i = tree.getRowCount() - 1; i >= 0; i--) {
tree.collapseRow(i);
}
tree.expandRow(0);
tree.expandRow(1);
tree.setSelectionPath(null);
info.setText("");
}
}));
// assemble
add(navigation, BorderLayout.NORTH);
add(new JScrollPane(tree), BorderLayout.CENTER);
JPanel p = new JPanel(new FlowLayout(FlowLayout.LEADING, 5, 5));
p.add(info);
add(p, BorderLayout.SOUTH);
redoSearch();
}
void jumpToNode() {
try {
TreeNode node = searchHits.get(searchHit);
Vector<TreeNode> path = new Vector<TreeNode>();
path.add(node);
do {
node = node.getParent();
path.add(0, node);
} while (node.getParent() != null);
TreePath treePath = new TreePath(path.toArray());
tree.setSelectionPath(treePath);
tree.scrollPathToVisible(treePath);
} catch (IndexOutOfBoundsException e) {
}
}
}
/**
* Class for representing object tree nodes in the GUI.
*/
static class MethodTreeNode extends DefaultMutableTreeNode {
Node node;
String treeStringRep;
String detailStringRep;
String shortName;
MethodTreeNode(Node node) {
this.node = node;
}
@Override
public String toString() {
if (treeStringRep == null) {
treeStringRep = node.name;
}
return treeStringRep;
}
String shortName() {
if (shortName == null) {
String s = toString();
int openParen = s.indexOf('(');
if (openParen == -1) {
shortName = s;
} else {
int i = s.lastIndexOf('.', openParen);
i = s.lastIndexOf('.', i - 1);
shortName = s.substring(i + 1, openParen) + "()";
}
}
return shortName;
}
String getDetails() {
if (detailStringRep == null) {
if (node.relationshipToParent == null) {
detailStringRep = "<html>" + shortName() + " is a <font color=green><b>VM entry point</b></font>";
} else {
MethodTreeNode parent = (MethodTreeNode) this.parent;
detailStringRep = "<html>" + shortName() + " <font color=green><b>" + node.relationshipToParent.asChild + "</b></font> " + parent.shortName();
}
}
return detailStringRep;
}
}
private static final OptionSet options = new OptionSet();
private static final Option<Integer> TRACE = options.newIntegerOption("trace", 1,
"selects the trace level of the tool");
private static final Option<File> INPUT_FILE = options.newFileOption("in", BootImageGenerator.getBootImageMethodTreeFile(null),
"the file from which to load the graph");
private static final Option<File> OUTPUT_FILE = options.newFileOption("out", getDefaultOutputFile(),
"the file to which the graph is printed");
private static final Option<String> FILTER = options.newStringOption("filter", null,
"filter for pruning the graph before printing");
private static final Option<Boolean> SHOW_TREE_LINES = options.newBooleanOption("lines", true,
"show lines (instead of indentation only) to indicate relationships between nodes");
private static final Option<Boolean> HELP = options.newBooleanOption("help", false,
"show help message and exits.");
private static final Option<Boolean> GUI = options.newBooleanOption("view", false,
"view the method tree graphically instead of generating text");
private static File getDefaultOutputFile() {
return new File(BootImageGenerator.getBootImageMethodTreeFile(null).getAbsolutePath() + ".txt");
}
/**
* Command line interface for loading a saved graph and printing it, optionally pruning it with a provided filter
* first.
*
* @param args
*/
public static void main(String[] args) throws IOException {
options.parseArguments(args);
if (HELP.getValue()) {
options.printHelp(System.out, 80);
return;
}
Trace.on(TRACE.getValue());
final InputStream inputStream = openInputFile();
if (inputStream == null) {
System.err.println("Cannot find input file " + INPUT_FILE.getValue().getAbsolutePath());
System.err.println("The input tree file is only created if the -tree option is used when creating the boot image.");
System.exit(1);
}
final DataInputStream dataInputStream = new DataInputStream(new BufferedInputStream(inputStream));
final Set<Node> roots = loadTree(dataInputStream);
inputStream.close();
if (FILTER.getValue() != null) {
final String filter = FILTER.getValue();
final Set<Node> prunedRoots = new HashSet<Node>(roots.size());
for (Node root : roots) {
final Node prunedRoot = root.prune(new Predicate<Node>() {
public boolean evaluate(Node object) {
return object.toString().contains(filter);
}
});
if (prunedRoot != null) {
prunedRoots.add(prunedRoot);
}
}
roots.clear();
roots.addAll(prunedRoots);
}
if (GUI.getValue()) {
viewInGUI(roots);
} else {
writeOutputFile(roots);
}
}
static void writeOutputFile(final Set<Node> roots) throws IOException {
final File outputFile = OUTPUT_FILE.getValue();
final Writer writer = new FileWriter(outputFile);
final PrintWriter printWriter = new PrintWriter(new BufferedWriter(writer)) {
private int counter;
@Override
public void println(String s) {
if (++counter % 100000 == 0) {
Trace.line(1, "node: " + counter);
}
super.println(s);
}
};
Trace.begin(1, "writing boot image method tree text file: " + outputFile.getAbsolutePath());
printWriter.println("VM Entry Points");
for (final Iterator<Node> iterator = roots.iterator(); iterator.hasNext();) {
final Node root = iterator.next();
Node.printTree(root, SHOW_TREE_LINES.getValue(), printWriter, "", !iterator.hasNext());
}
printWriter.close();
Trace.end(1, "writing boot image method tree text file: " + outputFile.getAbsolutePath());
}
private static FileInputStream openInputFile() {
try {
return new FileInputStream(INPUT_FILE.getValue());
} catch (FileNotFoundException e) {
return null;
}
}
}