001: /*/////////////////////////////////////////////////////////////////////
002:
003: Copyright (C) 2006 TiVo Inc. All rights reserved.
004:
005: Redistribution and use in source and binary forms, with or without
006: modification, are permitted provided that the following conditions are met:
007:
008: + Redistributions of source code must retain the above copyright notice,
009: this list of conditions and the following disclaimer.
010: + Redistributions in binary form must reproduce the above copyright notice,
011: this list of conditions and the following disclaimer in the documentation
012: and/or other materials provided with the distribution.
013: + Neither the name of TiVo Inc nor the names of its contributors may be
014: used to endorse or promote products derived from this software without
015: specific prior written permission.
016:
017: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
018: AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
019: IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
020: ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
021: LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
022: CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
023: SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
024: INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
025: CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
026: ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
027: POSSIBILITY OF SUCH DAMAGE.
028:
029: /////////////////////////////////////////////////////////////////////*/
030:
031: /*/////////////////////////////////////////////////////////////////////
032: BUGS & RFES
033:
034: swing-specific cluelessness on my part
035: * where are the headers on self section? some layout problem.
036: * i don't think i should have to add keyListeners to all components
037: "manually" to handle the keys globally. is there something i can
038: override?
039:
040: * set default sort orders? (either explicitly or in the way i fill
041: in the tables)
042: * some coloring?
043: * some filtering?
044:
045: * add a pane for connecting to a remote profiler,
046: clearing, starting, stopping, and fetching results.
047:
048: * make call tree more useful?
049: * make stats more regular
050: * color?
051: * auto-expand?
052: * all
053: * smart (by cost)
054: * and select based on selection in another view...
055: * filter (by cost)
056:
057: * display class allocation information.
058:
059: * why so many interactions? combine?
060: * it'd be cool if we could label "interactions"
061: * it'd be cool if we could compress "similar" interactions
062:
063: Suggestions for jip output:
064: * either consistently use the class name table or ditch it
065: (i'm inclined to ditch it)
066:
067: Suggestions for jip runtime:
068: * make it so we can fetch the xml over the client socket
069: /////////////////////////////////////////////////////////////////////*/
070:
071: package com.tivo.jipviewer;
072:
073: import java.awt.Dimension;
074: import java.awt.Rectangle;
075: import java.awt.event.KeyEvent;
076: import java.awt.event.KeyListener;
077: import java.util.ArrayList;
078: import java.util.Collection;
079: import java.util.Collections;
080: import java.util.Comparator;
081: import java.util.List;
082:
083: import javax.swing.JFrame;
084: import javax.swing.JScrollPane;
085: import javax.swing.JSplitPane;
086: import javax.swing.JTabbedPane;
087: import javax.swing.JTable;
088: import javax.swing.JTree;
089: import javax.swing.ListSelectionModel;
090: import javax.swing.event.ListSelectionEvent;
091: import javax.swing.event.ListSelectionListener;
092: import javax.swing.event.TreeSelectionEvent;
093: import javax.swing.event.TreeSelectionListener;
094: import javax.swing.tree.DefaultMutableTreeNode;
095: import javax.swing.tree.TreePath;
096: import javax.swing.tree.TreeSelectionModel;
097:
098: /**
099: * Use JipViewer to open a top-level window viewing the contents of a jip
100: * profile.xml file.
101: */
102: public class JipViewer extends JFrame implements TreeSelectionListener,
103: ListSelectionListener, KeyListener, ChangeListener {
104: private JTable mTable;
105: private JTree mCallTree;
106: private JTable mMethods;
107: private ByPackageViewer mPkgViewer;
108: private RemoteController mRemoteController;
109:
110: private TreeNode mCallTreeRoot;
111: private ValueModel<JipMethod> mMethodModel = new ValueModel<JipMethod>();
112: private MethodRowTableModel mAllMethodsModel = new MethodRowTableModel();
113: private TableSorter mAllMethodsSorterModel;
114:
115: public static void main(String[] args) throws Exception {
116: String filename = "";
117: if (args.length == 1) {
118: filename = args[0];
119: } else {
120: System.out.println("usage: JipViewer filename");
121: System.exit(1); // error;
122: }
123: JipRun run = JipParser.parse(filename);
124: //System.out.println(run);
125:
126: long totalTimeForAllThreads = run.getTotalTimeForAllThreads();
127: long msec = (long) Math.floor(toMsec(totalTimeForAllThreads));
128: String title = "" + msec + " msec -- " + filename;
129:
130: new JipViewer(title, run);
131: }
132:
133: public JipViewer(String title, JipRun run) {
134: super (title);
135:
136: addKeyListener(this );
137: mMethodModel.addChangeListener(this );
138:
139: // build the call tree
140: mCallTreeRoot = new TreeNode(title);
141: buildTree(run, mCallTreeRoot);
142:
143: mCallTree = new JTree(mCallTreeRoot);
144: mCallTree.getSelectionModel().setSelectionMode(
145: TreeSelectionModel.SINGLE_TREE_SELECTION);
146: mCallTree.addTreeSelectionListener(this );
147: mCallTree.addKeyListener(this );
148:
149: // build the allMethods table
150: Collection<JipRun.PerMethodInfo> perMethodInfos = run
151: .perMethodsInTotalTimeOrder();
152: long totalTimeForAllThreads = run.getTotalTimeForAllThreads();
153: for (JipRun.PerMethodInfo perMethod : perMethodInfos) {
154: MethodRow row = new MethodRow(perMethod.getMethod());
155: for (JipFrame frame : perMethod.allFrames()) {
156: if (!frame.isReentrant()) {
157: row.addFrame(frame);
158: }
159: row.setTimeDenominator(totalTimeForAllThreads);
160: }
161: mAllMethodsModel.add(row);
162: }
163: mMethods = MethodViewer
164: .makeTableForMethodRows(mAllMethodsModel);
165: mMethods.getSelectionModel().addListSelectionListener(this );
166: mMethods.addKeyListener(this );
167: mAllMethodsSorterModel = (TableSorter) mMethods.getModel();
168:
169: // make the ByPackageViewer
170: mPkgViewer = new ByPackageViewer(run);
171: mPkgViewer.addKeyListener(this );
172:
173: // make the RemoteController
174: mRemoteController = new RemoteController();
175: mRemoteController.addKeyListener(this );
176:
177: // make the methodViewer
178: MethodViewer methodViewer = new MethodViewer(run, mMethodModel);
179:
180: // combine all the views
181: JTabbedPane tabPane = new JTabbedPane();
182: tabPane.addTab("call tree", new JScrollPane(mCallTree));
183: tabPane.addTab("methods", new JScrollPane(mMethods));
184: tabPane.addTab("by package", new JScrollPane(mPkgViewer));
185: tabPane.addTab("remote control", mRemoteController);
186: tabPane.addTab("help", new HelpViewer());
187: tabPane.addKeyListener(this );
188: tabPane.setMinimumSize(new Dimension(100, 200));
189:
190: JSplitPane split = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
191: tabPane, methodViewer);
192: setContentPane(split);
193:
194: pack();
195: setSize(new Dimension(1024, 768));
196: setVisible(true);
197: setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
198: }
199:
200: public void valueChanged(ListSelectionEvent e) {
201: if (e.getValueIsAdjusting()) {
202: return;
203: }
204:
205: JipMethod method = null;
206: ListSelectionModel selectionModel = mMethods
207: .getSelectionModel();
208: if (!selectionModel.isSelectionEmpty()) {
209: int iSelected = selectionModel.getMinSelectionIndex();
210: iSelected = mAllMethodsSorterModel.modelIndex(iSelected);
211: MethodRow row = mAllMethodsModel.getRow(iSelected);
212: method = row.getMethod();
213: }
214: //System.out.println("valueChanged("+perMethod+")");
215: mMethodModel.setValue(method);
216: }
217:
218: static class TreeNode extends DefaultMutableTreeNode {
219: // we either represent a frame or a random node.
220: // if we represent a frame, mFrame is set.
221: // otherwise, mLabel is set.
222: JipFrame mFrame;
223:
224: String mLabel;
225:
226: TreeNode(JipFrame frame) {
227: mFrame = frame;
228: }
229:
230: TreeNode(String label) {
231: mLabel = label;
232: }
233:
234: JipFrame getFrameOrNull() {
235: return mFrame;
236: }
237:
238: JipMethod getMethodOrNull() {
239: return (mFrame == null) ? null : mFrame.getMethod();
240: }
241:
242: public String toString() {
243: if (mFrame == null) {
244: return mLabel;
245: }
246:
247: String label = ("(" + toMsec(mFrame.getTotalTime()) + " "
248: + toMsec(mFrame.getNetTime()) + " "
249: + mFrame.getCount() + ") " + mFrame.getMethod()
250: .getMethodName());
251: return label;
252: }
253:
254: // returns the first node that has the given method (or null if none)
255: private TreeNode findNodeForMethod(TreeNode node,
256: JipMethod seekingMethod) {
257: JipMethod method = node.getMethodOrNull();
258: if ((method != null) && method.equals(seekingMethod)) {
259: return node;
260: }
261:
262: int nKid = node.getChildCount();
263: for (int iKid = 0; iKid < nKid; iKid++) {
264: TreeNode kidNode = (TreeNode) node.getChildAt(iKid);
265: TreeNode match = findNodeForMethod(kidNode,
266: seekingMethod);
267: if (match != null) {
268: return match;
269: }
270: }
271:
272: return null;
273: }
274:
275: // returns TreePath to the first TreeNode for seekingMethod
276: // or null if none.
277: TreePath findPathForMethod(JipMethod seekingMethod) {
278: TreeNode match = findNodeForMethod(this , seekingMethod);
279: if (match == null) {
280: return null;
281: }
282:
283: List<TreeNode> vNode = new ArrayList<TreeNode>();
284:
285: TreeNode scan = match;
286: while (scan != null) {
287: vNode.add(scan);
288: scan = (TreeNode) scan.getParent();
289: }
290: Collections.reverse(vNode);
291:
292: return new TreePath(vNode.toArray());
293: }
294: }
295:
296: private void buildTree(JipRun run, DefaultMutableTreeNode root) {
297: for (Long threadId : run.threads()) {
298: int i = 1;
299: for (JipFrame f : run.interactions(threadId)) {
300: String label = ("thread " + threadId + " interaction "
301: + i + " (" + toMsec(f.getTotalTime()) + " msec)");
302: TreeNode interactionNode = new TreeNode(label);
303: root.add(interactionNode);
304:
305: buildFrameTree(interactionNode, f);
306: i++;
307: }
308: }
309: }
310:
311: private static double toMsec(long time) {
312: final double timeToMsec = 1000.0 * 1000.0;
313: return Math.floor((time / timeToMsec) * 10) / 10.0;
314: }
315:
316: private void buildFrameTree(TreeNode parent, JipFrame frame) {
317: // compare for reverse total time.
318: Comparator cmp = new Comparator<JipFrame>() {
319: public int compare(JipFrame a, JipFrame b) {
320: long timeA = a.getTotalTime();
321: long timeB = b.getTotalTime();
322: if (timeA > timeB) {
323: return -1;
324: } else if (timeA < timeB) {
325: return 1;
326: } else {
327: String nameA = a.getMethod().getMethodName();
328: String nameB = a.getMethod().getMethodName();
329: return nameB.compareToIgnoreCase(nameA);
330: }
331: }
332: };
333:
334: TreeNode frameNode = new TreeNode(frame);
335: parent.add(frameNode);
336:
337: List<JipFrame> vKid = frame.getChildren();
338: Collections.sort(vKid, cmp);
339: for (JipFrame childFrame : vKid) {
340: buildFrameTree(frameNode, childFrame);
341: }
342: }
343:
344: public void valueChanged(TreeSelectionEvent e) {
345: TreeNode node = (TreeNode) e.getPath().getLastPathComponent();
346: //System.out.println("valueChanged("+node+")");
347:
348: JipFrame frame = node.getFrameOrNull();
349: JipMethod method = (frame == null) ? null : frame.getMethod();
350: mMethodModel.setValue(method);
351: }
352:
353: //
354: // KeyListener
355: //
356:
357: public void keyPressed(KeyEvent e) {
358: }
359:
360: public void keyReleased(KeyEvent e) {
361: }
362:
363: public void keyTyped(KeyEvent e) {
364: if (e.getKeyChar() == 'q') {
365: System.exit(0);
366: }
367: }
368:
369: //
370: // ChangeListener
371: //
372:
373: public void changed(Object source) {
374: if (source != mMethodModel) {
375: throw new RuntimeException("i wish i used asserts!");
376: }
377:
378: JipMethod selectedMethod = mMethodModel.getValue();
379: selectInAllMethods(selectedMethod);
380: selectInCallTree(selectedMethod);
381: }
382:
383: private void selectInAllMethods(JipMethod selectedMethod) {
384:
385: if (selectedMethod == null) {
386: mMethods.clearSelection();
387: return;
388: }
389:
390: // which row should we select?
391: boolean foundIt = false;
392: int nRow = mAllMethodsModel.getRowCount();
393: int iRow;
394: for (iRow = 0; iRow < nRow; iRow++) {
395: MethodRow scan = mAllMethodsModel.getRow(iRow);
396: if (scan.getMethod().equals(selectedMethod)) {
397: foundIt = true;
398: break;
399: }
400: }
401: if (!foundIt) {
402: System.out.println("couldn't find "
403: + selectedMethod.getName());
404: return;
405: }
406:
407: // update the listSelectionModel
408: int iRowInView = mAllMethodsSorterModel.viewIndex(iRow);
409: mMethods.getSelectionModel().setSelectionInterval(iRowInView,
410: iRowInView);
411:
412: // scroll to contain the new selection
413: Rectangle selectionRect = mMethods.getCellRect(iRowInView, 0,
414: true);
415: mMethods.scrollRectToVisible(selectionRect);
416: }
417:
418: private void selectInCallTree(JipMethod selectedMethod) {
419:
420: if (selectedMethod == null) {
421: mCallTree.clearSelection();
422: return;
423: }
424:
425: // is this method already selected?
426: TreePath curPath = mCallTree.getSelectionPath();
427: if (curPath != null) {
428: TreeNode curNode = (TreeNode) curPath
429: .getLastPathComponent();
430: JipMethod curMethod = curNode.getMethodOrNull();
431: if (curMethod != null && curMethod.equals(selectedMethod)) {
432: // we're done.
433: return;
434: }
435: }
436:
437: TreePath newPath = mCallTreeRoot
438: .findPathForMethod(selectedMethod);
439: if (newPath == null) {
440: System.out.println("no path to " + selectedMethod);
441: return;
442: }
443:
444: mCallTree.setSelectionPath(newPath);
445: mCallTree.scrollPathToVisible(newPath);
446: }
447: }
|