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