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