001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2006-2008 Sun Microsystems, Inc.
015 * Portions Copyright 2014-2015 ForgeRock AS.
016 */
017package org.opends.server.plugins.profiler;
018
019
020
021import java.util.Arrays;
022import java.util.HashMap;
023
024import org.forgerock.i18n.slf4j.LocalizedLogger;
025
026
027/**
028 * This class defines a data structure for holding information about a stack
029 * frame captured by the Directory Server profiler.  It will contain the class
030 * and method name for this frame, the set of line numbers within that method
031 * that were captured along with the number of times they were seen, as well as
032 * references to subordinate frames that were encountered.
033 */
034public class ProfileStackFrame
035       implements Comparable
036{
037  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
038
039
040
041
042  /**
043   * The mapping between the line numbers for this stack frame and the
044   * number of times that they were encountered.
045   */
046  private HashMap<Integer,Long> lineNumbers;
047
048  /**
049   * The mapping for subordinate frames.  It is mapped to itself because we
050   * use a fuzzy equality comparison and sets do not have a get method that
051   * can be used to retrieve a specified object.
052   */
053  private HashMap<ProfileStackFrame,ProfileStackFrame> subordinateFrames;
054
055  /** The class name for this stack frame. */
056  private String className;
057
058  /** The method name for this stack frame. */
059  private String methodName;
060
061
062
063  /**
064   * Creates a new profile stack frame with the provided information.
065   *
066   * @param  className   The class name for use in this stack frame.
067   * @param  methodName  The method name for use in this stack frame.
068   */
069  public ProfileStackFrame(String className, String methodName)
070  {
071    this.className  = className;
072    this.methodName = methodName;
073
074    lineNumbers       = new HashMap<>();
075    subordinateFrames = new HashMap<>();
076  }
077
078
079
080  /**
081   * Retrieves the class name for this stack frame.
082   *
083   * @return  The class name for this stack frame.
084   */
085  public String getClassName()
086  {
087    return className;
088  }
089
090
091
092  /**
093   * Retrieves the method name for this stack frame.
094   *
095   * @return  The method name for this stack frame.
096   */
097  public String getMethodName()
098  {
099    return methodName;
100  }
101
102
103
104  /**
105   * Retrieves the method name for this stack frame in a manner that will be
106   * safe for use in an HTML context.  Currently, this simply replaces angle
107   * brackets with the appropriate HTML equivalent.
108   *
109   * @return  The generated safe name.
110   */
111  public String getHTMLSafeMethodName()
112  {
113    int length = methodName.length();
114    StringBuilder buffer = new StringBuilder(length + 6);
115
116    for (int i=0; i < length; i++)
117    {
118      char c = methodName.charAt(i);
119      if (c == '<')
120      {
121        buffer.append("&lt;");
122      }
123      else if (c == '>')
124      {
125        buffer.append("&gt;");
126      }
127      else
128      {
129        buffer.append(c);
130      }
131    }
132
133    return buffer.toString();
134  }
135
136
137
138  /**
139   * Retrieves the mapping between the line numbers associated with this method
140   * and the number of occurrences for each of those line numbers.
141   *
142   * @return  The mapping between the line numbers associated with this method
143   *          and the number of occurrences for each of those line numbers.
144   */
145  public HashMap<Integer,Long> getLineNumbers()
146  {
147    return lineNumbers;
148  }
149
150
151
152  /**
153   * Updates the count for the number of occurrences of a given stack frame
154   * for the specified line number.
155   *
156   * @param  lineNumber      The line number for which to update the count.
157   * @param  numOccurrences  The number of times the specified line was
158   *                         encountered for this stack frame.
159   */
160  public void updateLineNumberCount(int lineNumber, long numOccurrences)
161  {
162    Long existingCount = lineNumbers.get(lineNumber);
163    if (existingCount == null)
164    {
165      lineNumbers.put(lineNumber, numOccurrences);
166    }
167    else
168    {
169      lineNumbers.put(lineNumber, existingCount+numOccurrences);
170    }
171  }
172
173
174
175  /**
176   * Retrieves the total number of times that a frame with this class and
177   * method name was seen by the profiler thread.
178   *
179   * @return  The total number of times that a frame with this class and method
180   *          name was seen by the profiler thread.
181   */
182  public long getTotalCount()
183  {
184    long totalCount = 0;
185
186    for (Long l : lineNumbers.values())
187    {
188      totalCount += l;
189    }
190
191    return totalCount;
192  }
193
194
195
196  /**
197   * Retrieves an array containing the subordinate frames that were seen below
198   * this frame in stack traces.  The elements of the array will be sorted in
199   * descending order of the number of occurrences.
200   *
201   * @return  An array containing the subordinate frames that were seen below
202   *          this frame in stack traces.
203   */
204  public ProfileStackFrame[] getSubordinateFrames()
205  {
206    ProfileStackFrame[] subFrames = new ProfileStackFrame[0];
207    subFrames = subordinateFrames.values().toArray(subFrames);
208
209    Arrays.sort(subFrames);
210
211    return subFrames;
212  }
213
214
215
216  /**
217   * Indicates whether this stack frame has one or more subordinate frames.
218   *
219   * @return  <CODE>true</CODE> if this stack frame has one or more subordinate
220   *          frames, or <CODE>false</CODE> if not.
221   */
222  public boolean hasSubFrames()
223  {
224    return !subordinateFrames.isEmpty();
225  }
226
227
228
229  /**
230   * Recursively processes the frames of the provided stack, adding them as
231   * nested subordinate frames of this stack frame.
232   *
233   * @param  stack           The stack trace to use to obtain the frames.
234   * @param  depth           The slot of the next frame to process in the
235   *                         provided array.
236   * @param  count           The number of occurrences for the provided stack.
237   * @param  stacksByMethod  The set of stack traces mapped from method name to
238   *                         their corresponding stack traces.
239   */
240  public void recurseSubFrames(ProfileStack stack, int depth, long count,
241                   HashMap<String,HashMap<ProfileStack,Long>> stacksByMethod)
242  {
243    if (depth < 0)
244    {
245      return;
246    }
247
248    String cName = stack.getClassName(depth);
249    String mName = stack.getMethodName(depth);
250    ProfileStackFrame f = new ProfileStackFrame(cName, mName);
251
252    int lineNumber = stack.getLineNumber(depth);
253
254    ProfileStackFrame subFrame = subordinateFrames.get(f);
255    if (subFrame == null)
256    {
257      subFrame = f;
258      subordinateFrames.put(subFrame, subFrame);
259    }
260
261    subFrame.updateLineNumberCount(lineNumber, count);
262
263
264    String classAndMethod = cName + "." + mName;
265    HashMap<ProfileStack,Long> stackMap = stacksByMethod.get(classAndMethod);
266    if (stackMap == null)
267    {
268      stackMap = new HashMap<>();
269      stacksByMethod.put(classAndMethod, stackMap);
270    }
271    stackMap.put(stack, count);
272
273    subFrame.recurseSubFrames(stack, depth-1, count, stacksByMethod);
274  }
275
276
277
278  /**
279   * Retrieves the hash code for this stack frame.  It will be the sum of the
280   * hash codes for the class and method name.
281   *
282   * @return  The hash code for this stack frame.
283   */
284  @Override
285  public int hashCode()
286  {
287    return className.hashCode() + methodName.hashCode();
288  }
289
290
291
292  /**
293   * Indicates whether the provided object is equal to this stack frame.  It
294   * will be considered equal if it is a profile stack frame with the same class
295   * and method name.
296   *
297   * @param  o  The object for which to make the determination.
298   *
299   * @return  <CODE>true</CODE> if the provided object may be considered equal
300   *          to this stack frame, or <CODE>false</CODE> if not.
301   */
302  @Override
303  public boolean equals(Object o)
304  {
305    if (o == null)
306    {
307      return false;
308    }
309    else if (this == o)
310    {
311      return true;
312    }
313
314    try
315    {
316      ProfileStackFrame f = (ProfileStackFrame) o;
317      return className.equals(f.className) && methodName.equals(f.methodName);
318    }
319    catch (Exception e)
320    {
321      logger.traceException(e);
322
323      return false;
324    }
325  }
326
327
328
329  /**
330   * Indicates the order of this profile stack frame relative to the provided
331   * object in a sorted list.  The order will be primarily based on number of
332   * occurrences, with an equivalent number of occurrences falling back on
333   * alphabetical by class and method names.
334   *
335   * @param  o  The object for which to make the comparison.
336   *
337   * @return  A negative integer if this stack frame should come before the
338   *          provided object in a sorted list, a positive integer if it should
339   *          come after the provided object, or zero if they should have
340   *          equivalent order.
341   *
342   * @throws  ClassCastException  If the provided object is not a profile stack
343   *                              frame.
344   */
345  @Override
346  public int compareTo(Object o)
347         throws ClassCastException
348  {
349    ProfileStackFrame f = (ProfileStackFrame) o;
350
351    long thisCount = getTotalCount();
352    long thatCount = f.getTotalCount();
353    if (thisCount > thatCount)
354    {
355      return -1;
356    }
357    else if (thisCount < thatCount)
358    {
359      return 1;
360    }
361
362    int value = className.compareTo(f.className);
363    if (value == 0)
364    {
365      value = methodName.compareTo(f.methodName);
366    }
367
368    return value;
369  }
370
371
372
373  /**
374   * Retrieves a string representation of this stack frame.  It will contain the
375   * total number of matching frames, the class name, and the method name.
376   *
377   * @return  A string representation of this stack frame.
378   */
379  @Override
380  public String toString()
381  {
382    StringBuilder buffer = new StringBuilder();
383    buffer.append(getTotalCount());
384    buffer.append("    ");
385    buffer.append(className);
386    buffer.append('.');
387    buffer.append(methodName);
388
389    return buffer.toString();
390  }
391}
392