001 /* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at 010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE 011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE. 012 * See the License for the specific language governing permissions 013 * and limitations under the License. 014 * 015 * When distributing Covered Code, include this CDDL HEADER in each 016 * file and include the License file at 017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, 018 * add the following below this CDDL HEADER, with the fields enclosed 019 * by brackets "[]" replaced with your own identifying information: 020 * Portions Copyright [yyyy] [name of copyright owner] 021 * 022 * CDDL HEADER END 023 * 024 * 025 * Copyright 2006-2008 Sun Microsystems, Inc. 026 */ 027 package org.opends.server.plugins.profiler; 028 import org.opends.messages.Message; 029 030 031 032 import java.awt.BorderLayout; 033 import java.awt.Container; 034 import java.awt.Font; 035 import java.io.FileInputStream; 036 import java.io.IOException; 037 import java.util.ArrayList; 038 import java.util.Arrays; 039 import java.util.HashMap; 040 import javax.swing.JEditorPane; 041 import javax.swing.JFrame; 042 import javax.swing.JScrollPane; 043 import javax.swing.JSplitPane; 044 import javax.swing.JTree; 045 import javax.swing.tree.DefaultMutableTreeNode; 046 import javax.swing.tree.DefaultTreeModel; 047 import javax.swing.tree.DefaultTreeSelectionModel; 048 import javax.swing.tree.TreePath; 049 import javax.swing.tree.TreeSelectionModel; 050 import javax.swing.event.TreeSelectionEvent; 051 import javax.swing.event.TreeSelectionListener; 052 053 import org.opends.server.protocols.asn1.ASN1Element; 054 import org.opends.server.protocols.asn1.ASN1Exception; 055 import org.opends.server.protocols.asn1.ASN1Reader; 056 import org.opends.server.util.args.ArgumentException; 057 import org.opends.server.util.args.ArgumentParser; 058 import org.opends.server.util.args.BooleanArgument; 059 import org.opends.server.util.args.StringArgument; 060 061 import static org.opends.messages.PluginMessages.*; 062 import static org.opends.messages.ToolMessages.*; 063 import static org.opends.server.util.StaticUtils.*; 064 065 066 067 /** 068 * This class defines a Directory Server utility that may be used to view 069 * profile information that has been captured by the profiler plugin. It 070 * supports viewing this information in either a command-line mode or using a 071 * simple GUI. 072 */ 073 public class ProfileViewer 074 implements TreeSelectionListener 075 { 076 // The root stack frames for the profile information that has been captured. 077 private HashMap<ProfileStackFrame,ProfileStackFrame> rootFrames; 078 079 // A set of stack traces indexed by class and method name. 080 private HashMap<String,HashMap<ProfileStack,Long>> stacksByMethod; 081 082 // The editor pane that will provide detailed information about the selected 083 // stack frame. 084 private JEditorPane frameInfoPane; 085 086 // The GUI tree that will be used to hold stack frame information; 087 private JTree profileTree; 088 089 // The total length of time in milliseconds for which data is available. 090 private long totalDuration; 091 092 // The total number of profile intervals for which data is available. 093 private long totalIntervals; 094 095 096 097 /** 098 * Parses the command-line arguments and creates an instance of the profile 099 * viewer as appropriate. 100 * 101 * @param args The command-line arguments provided to this program. 102 */ 103 public static void main(String[] args) 104 { 105 // Define the command-line arguments that may be used with this program. 106 BooleanArgument displayUsage = null; 107 BooleanArgument useGUI = null; 108 StringArgument fileNames = null; 109 110 111 // Create the command-line argument parser for use with this program. 112 Message toolDescription = INFO_PROFILEVIEWER_TOOL_DESCRIPTION.get(); 113 ArgumentParser argParser = 114 new ArgumentParser("org.opends.server.plugins.profiler.ProfileViewer", 115 toolDescription, false); 116 117 118 // Initialize all the command-line argument types and register them with the 119 // parser. 120 try 121 { 122 fileNames = 123 new StringArgument("filenames", 'f', "fileName", true, true, true, 124 INFO_FILE_PLACEHOLDER.get(), null, null, 125 INFO_PROFILEVIEWER_DESCRIPTION_FILENAMES.get()); 126 argParser.addArgument(fileNames); 127 128 useGUI = new BooleanArgument( 129 "usegui", 'g', "useGUI", 130 INFO_PROFILEVIEWER_DESCRIPTION_USE_GUI.get()); 131 argParser.addArgument(useGUI); 132 133 displayUsage = new BooleanArgument( 134 "help", 'H', "help", 135 INFO_PROFILEVIEWER_DESCRIPTION_USAGE.get()); 136 argParser.addArgument(displayUsage); 137 argParser.setUsageArgument(displayUsage); 138 } 139 catch (ArgumentException ae) 140 { 141 Message message = 142 ERR_PROFILEVIEWER_CANNOT_INITIALIZE_ARGS.get(ae.getMessage()); 143 144 System.err.println(message); 145 System.exit(1); 146 } 147 148 149 // Parse the command-line arguments provided to this program. 150 try 151 { 152 argParser.parseArguments(args); 153 } 154 catch (ArgumentException ae) 155 { 156 Message message = 157 ERR_PROFILEVIEWER_ERROR_PARSING_ARGS.get(ae.getMessage()); 158 159 System.err.println(message); 160 System.err.println(argParser.getUsage()); 161 System.exit(1); 162 } 163 164 165 // If we should just display usage or versionn information, 166 // then print it and exit. 167 if (argParser.usageOrVersionDisplayed()) 168 { 169 System.exit(0); 170 } 171 172 173 // Create the profile viewer and read in the data files. 174 ProfileViewer viewer = new ProfileViewer(); 175 for (String filename : fileNames.getValues()) 176 { 177 try 178 { 179 viewer.processDataFile(filename); 180 } 181 catch (Exception e) 182 { 183 Message message = 184 ERR_PROFILEVIEWER_CANNOT_PROCESS_DATA_FILE.get(filename, 185 stackTraceToSingleLineString(e)); 186 System.err.println(message); 187 } 188 } 189 190 191 // Write the captured information to standard output or display it in a GUI. 192 if (useGUI.isPresent()) 193 { 194 viewer.displayGUI(); 195 } 196 else 197 { 198 viewer.printProfileData(); 199 } 200 } 201 202 203 204 /** 205 * Creates a new profile viewer object without any data. It should be 206 * populated with one or more calls to <CODE>processDataFile</CODE> 207 */ 208 public ProfileViewer() 209 { 210 rootFrames = new HashMap<ProfileStackFrame,ProfileStackFrame>(); 211 stacksByMethod = new HashMap<String,HashMap<ProfileStack,Long>>(); 212 totalDuration = 0; 213 totalIntervals = 0; 214 } 215 216 217 218 /** 219 * Reads and processes the information in the provided data file into this 220 * profile viewer. 221 * 222 * @param filename The path to the file containing the data to be read. 223 * 224 * @throws IOException If a problem occurs while trying to read from the 225 * data file. 226 * 227 * @throws ASN1Exception If an error occurs while trying to decode the 228 * contents of the file into profile stack objects. 229 */ 230 public void processDataFile(String filename) 231 throws IOException, ASN1Exception 232 { 233 // Try to open the file for reading. 234 ASN1Reader reader = new ASN1Reader(new FileInputStream(filename)); 235 236 237 try 238 { 239 // The first element in the file must be a sequence with the header 240 // information. 241 ASN1Element element = reader.readElement(); 242 ArrayList<ASN1Element> elements = element.decodeAsSequence().elements(); 243 totalIntervals += elements.get(0).decodeAsLong().longValue(); 244 245 long startTime = elements.get(1).decodeAsLong().longValue(); 246 long stopTime = elements.get(2).decodeAsLong().longValue(); 247 totalDuration += (stopTime - startTime); 248 249 250 // The remaining elements will contain the stack frames. 251 while (true) 252 { 253 element = reader.readElement(); 254 if (element == null) 255 { 256 break; 257 } 258 259 260 ProfileStack stack = ProfileStack.decode(element); 261 262 element = reader.readElement(); 263 long count = element.decodeAsLong().longValue(); 264 265 int pos = stack.getNumFrames() - 1; 266 if (pos < 0) 267 { 268 continue; 269 } 270 271 String[] classNames = stack.getClassNames(); 272 String[] methodNames = stack.getMethodNames(); 273 int[] lineNumbers = stack.getLineNumbers(); 274 275 ProfileStackFrame frame = new ProfileStackFrame(classNames[pos], 276 methodNames[pos]); 277 278 ProfileStackFrame existingFrame = rootFrames.get(frame); 279 if (existingFrame == null) 280 { 281 existingFrame = frame; 282 } 283 284 String classAndMethod = classNames[pos] + "." + methodNames[pos]; 285 HashMap<ProfileStack,Long> stackMap = 286 stacksByMethod.get(classAndMethod); 287 if (stackMap == null) 288 { 289 stackMap = new HashMap<ProfileStack,Long>(); 290 stacksByMethod.put(classAndMethod, stackMap); 291 } 292 stackMap.put(stack, count); 293 294 existingFrame.updateLineNumberCount(lineNumbers[pos], count); 295 rootFrames.put(existingFrame, existingFrame); 296 297 existingFrame.recurseSubFrames(stack, pos-1, count, stacksByMethod); 298 } 299 } 300 finally 301 { 302 try 303 { 304 reader.close(); 305 } catch (Exception e) {} 306 } 307 } 308 309 310 311 /** 312 * Retrieves an array containing the root frames for the profile information. 313 * The array will be sorted in descending order of matching stacks. The 314 * elements of this array will be the leaf method names with sub-frames 315 * holding information about the callers of those methods. 316 * 317 * @return An array containing the root frames for the profile information. 318 */ 319 public ProfileStackFrame[] getRootFrames() 320 { 321 ProfileStackFrame[] frames = new ProfileStackFrame[0]; 322 frames = rootFrames.values().toArray(frames); 323 324 Arrays.sort(frames); 325 326 return frames; 327 } 328 329 330 331 /** 332 * Retrieves the total number of sample intervals for which profile data is 333 * available. 334 * 335 * @return The total number of sample intervals for which profile data is 336 * available. 337 */ 338 public long getTotalIntervals() 339 { 340 return totalIntervals; 341 } 342 343 344 345 /** 346 * Retrieves the total duration in milliseconds covered by the profile data. 347 * 348 * @return The total duration in milliseconds covered by the profile data. 349 */ 350 public long getTotalDuration() 351 { 352 return totalDuration; 353 } 354 355 356 357 /** 358 * Prints the profile information to standard output in a human-readable 359 * form. 360 */ 361 public void printProfileData() 362 { 363 System.out.println("Total Intervals: " + totalIntervals); 364 System.out.println("Total Duration: " + totalDuration); 365 366 System.out.println(); 367 System.out.println(); 368 369 for (ProfileStackFrame frame : getRootFrames()) 370 { 371 printFrame(frame, 0); 372 } 373 } 374 375 376 377 /** 378 * Prints the provided stack frame and its subordinates using the provided 379 * indent. 380 * 381 * @param frame The stack frame to be printed, followed by recursive 382 * information about all its subordinates. 383 * @param indent The number of tabs to indent the stack frame information. 384 */ 385 private static void printFrame(ProfileStackFrame frame, int indent) 386 { 387 for (int i=0; i < indent; i++) 388 { 389 System.out.print("\t"); 390 } 391 392 System.out.print(frame.getTotalCount()); 393 System.out.print("\t"); 394 System.out.print(frame.getClassName()); 395 System.out.print("."); 396 System.out.println(frame.getMethodName()); 397 398 if (frame.hasSubFrames()) 399 { 400 for (ProfileStackFrame f : frame.getSubordinateFrames()) 401 { 402 printFrame(f, indent+1); 403 } 404 } 405 } 406 407 408 409 /** 410 * Displays a simple GUI with the profile data. 411 */ 412 public void displayGUI() 413 { 414 JFrame appWindow = new JFrame("Directory Server Profile Data"); 415 appWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 416 417 JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); 418 419 Container contentPane = appWindow.getContentPane(); 420 contentPane.setLayout(new BorderLayout()); 421 contentPane.setFont(new Font("Monospaced", Font.PLAIN, 12)); 422 423 String blankHTML = "<HTML><BODY><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR>" + 424 "</BODY></HTML>"; 425 frameInfoPane = new JEditorPane("text/html", blankHTML); 426 splitPane.setBottomComponent(new JScrollPane(frameInfoPane)); 427 428 String label = "Profile Data: " + totalIntervals + " sample intervals " + 429 "captured over " + totalDuration + " milliseconds"; 430 DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(label, true); 431 432 ProfileStackFrame[] rootFrames = getRootFrames(); 433 if (rootFrames.length == 0) 434 { 435 System.err.println("ERROR: No data available for viewing."); 436 return; 437 } 438 439 for (ProfileStackFrame frame : getRootFrames()) 440 { 441 boolean hasChildren = frame.hasSubFrames(); 442 443 DefaultMutableTreeNode frameNode = 444 new DefaultMutableTreeNode(frame, hasChildren); 445 recurseTreeNodes(frame, frameNode); 446 447 rootNode.add(frameNode); 448 } 449 450 profileTree = new JTree(new DefaultTreeModel(rootNode, true)); 451 profileTree.setFont(new Font("Monospaced", Font.PLAIN, 12)); 452 453 DefaultTreeSelectionModel selectionModel = new DefaultTreeSelectionModel(); 454 selectionModel.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); 455 profileTree.setSelectionModel(selectionModel); 456 profileTree.addTreeSelectionListener(this); 457 profileTree.setSelectionPath(new TreePath(rootNode.getFirstChild())); 458 valueChanged(null); 459 460 splitPane.setTopComponent(new JScrollPane(profileTree)); 461 splitPane.setResizeWeight(0.5); 462 splitPane.setOneTouchExpandable(true); 463 contentPane.add(splitPane, BorderLayout.CENTER); 464 465 appWindow.pack(); 466 appWindow.setVisible(true); 467 } 468 469 470 471 /** 472 * Recursively adds subordinate nodes to the provided parent node with the 473 * provided information. 474 * 475 * @param parentFrame The stack frame whose children are to be added as 476 * subordinate nodes of the provided tree node. 477 * @param parentNode The tree node to which the subordinate nodes are to be 478 * added. 479 */ 480 private void recurseTreeNodes(ProfileStackFrame parentFrame, 481 DefaultMutableTreeNode parentNode) 482 { 483 ProfileStackFrame[] subFrames = parentFrame.getSubordinateFrames(); 484 if (subFrames.length == 0) 485 { 486 return; 487 } 488 489 String largestCountString = String.valueOf(subFrames[0].getTotalCount()); 490 491 for (ProfileStackFrame subFrame : subFrames) 492 { 493 boolean hasChildren = parentFrame.hasSubFrames(); 494 495 DefaultMutableTreeNode subNode = 496 new DefaultMutableTreeNode(subFrame, hasChildren); 497 if (hasChildren) 498 { 499 recurseTreeNodes(subFrame, subNode); 500 } 501 502 parentNode.add(subNode); 503 } 504 } 505 506 507 508 /** 509 * Formats the provided count, padding with leading spaces as necessary. 510 * 511 * @param count The count value to be formatted. 512 * @param length The total length for the string to return. 513 * 514 * @return The formatted count string. 515 */ 516 private String formatCount(long count, int length) 517 { 518 StringBuilder buffer = new StringBuilder(length); 519 520 buffer.append(count); 521 while (buffer.length() < length) 522 { 523 buffer.insert(0, ' '); 524 } 525 526 return buffer.toString(); 527 } 528 529 530 531 /** 532 * Indicates that a node in the tree has been selected or deselected and that 533 * any appropriate action should be taken. 534 * 535 * @param tse The tree selection event with information about the selection 536 * or deselection that occurred. 537 */ 538 public void valueChanged(TreeSelectionEvent tse) 539 { 540 try 541 { 542 TreePath path = profileTree.getSelectionPath(); 543 if (path == null) 544 { 545 // Nothing is selected, so we'll use use an empty panel. 546 frameInfoPane.setText(""); 547 return; 548 } 549 550 551 DefaultMutableTreeNode selectedNode = 552 (DefaultMutableTreeNode) path.getLastPathComponent(); 553 if (selectedNode == null) 554 { 555 // No tree node is selected, so we'll just use an empty panel. 556 frameInfoPane.setText(""); 557 return; 558 } 559 560 561 // It is possible that this is the root node, in which case we'll empty 562 // the info pane. 563 Object selectedObject = selectedNode.getUserObject(); 564 if (! (selectedObject instanceof ProfileStackFrame)) 565 { 566 frameInfoPane.setText(""); 567 return; 568 } 569 570 571 // There is a tree node selected, so we should convert it to a stack 572 // frame and display information about it. 573 ProfileStackFrame frame = (ProfileStackFrame) selectedObject; 574 575 StringBuilder html = new StringBuilder(); 576 html.append("<HTML><BODY><PRE>"); 577 html.append("Information for stack frame <B>"); 578 html.append(frame.getClassName()); 579 html.append("."); 580 html.append(frame.getHTMLSafeMethodName()); 581 html.append("</B><BR><BR>Occurrences by Source Line Number:<BR>"); 582 583 HashMap<Integer,Long> lineNumbers = frame.getLineNumbers(); 584 for (Integer lineNumber : lineNumbers.keySet()) 585 { 586 html.append(" "); 587 588 long count = lineNumbers.get(lineNumber); 589 590 if (lineNumber == ProfileStack.LINE_NUMBER_NATIVE) 591 { 592 html.append("<native>"); 593 } 594 else if (lineNumber == ProfileStack.LINE_NUMBER_UNKNOWN) 595 { 596 html.append("<unknown>"); 597 } 598 else 599 { 600 html.append("Line "); 601 html.append(lineNumber); 602 } 603 604 html.append(": "); 605 html.append(count); 606 607 if (count == 1) 608 { 609 html.append(" occurrence<BR>"); 610 } 611 else 612 { 613 html.append(" occurrences<BR>"); 614 } 615 } 616 617 html.append("<BR><BR>"); 618 html.append("<HR>Stack Traces Including this Method:"); 619 620 String classAndMethod = frame.getClassName() + "." + 621 frame.getMethodName(); 622 HashMap<ProfileStack,Long> stacks = stacksByMethod.get(classAndMethod); 623 624 for (ProfileStack stack : stacks.keySet()) 625 { 626 html.append("<BR><BR>"); 627 html.append(stacks.get(stack)); 628 html.append(" occurrence(s):"); 629 630 appendHTMLStack(stack, html, classAndMethod); 631 } 632 633 634 html.append("</PRE></BODY></HTML>"); 635 636 frameInfoPane.setText(html.toString()); 637 frameInfoPane.setSelectionStart(0); 638 frameInfoPane.setSelectionEnd(0); 639 } 640 catch (Exception e) 641 { 642 e.printStackTrace(); 643 frameInfoPane.setText(""); 644 } 645 } 646 647 648 649 /** 650 * Appends an HTML representation of the provided stack to the given buffer. 651 * 652 * @param stack The stack trace to represent in HTML. 653 * @param html The buffer to which the HTML version of 654 * the stack should be appended. 655 * @param highlightClassAndMethod The name of the class and method that 656 * should be highlighted in the stack frame. 657 */ 658 private void appendHTMLStack(ProfileStack stack, StringBuilder html, 659 String highlightClassAndMethod) 660 { 661 int numFrames = stack.getNumFrames(); 662 for (int i=(numFrames-1); i >= 0; i--) 663 { 664 html.append("<BR> "); 665 666 String className = stack.getClassName(i); 667 String methodName = stack.getMethodName(i); 668 int lineNumber = stack.getLineNumber(i); 669 670 String safeMethod = 671 (methodName.equals("<init>") ? "<init>" : methodName); 672 673 String classAndMethod = className + "." + methodName; 674 if (classAndMethod.equals(highlightClassAndMethod)) 675 { 676 html.append("<B>"); 677 html.append(className); 678 html.append("."); 679 html.append(safeMethod); 680 html.append(":"); 681 682 if (lineNumber == ProfileStack.LINE_NUMBER_NATIVE) 683 { 684 html.append("<native>"); 685 } 686 else if (lineNumber == ProfileStack.LINE_NUMBER_UNKNOWN) 687 { 688 html.append("<unknown>"); 689 } 690 else 691 { 692 html.append(lineNumber); 693 } 694 695 html.append("</B>"); 696 } 697 else 698 { 699 html.append(className); 700 html.append("."); 701 html.append(safeMethod); 702 html.append(":"); 703 704 if (lineNumber == ProfileStack.LINE_NUMBER_NATIVE) 705 { 706 html.append("<native>"); 707 } 708 else if (lineNumber == ProfileStack.LINE_NUMBER_UNKNOWN) 709 { 710 html.append("<unknown>"); 711 } 712 else 713 { 714 html.append(lineNumber); 715 } 716 } 717 } 718 } 719 } 720