1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ddmlib; 18 19 import java.util.ArrayList; 20 import java.util.Arrays; 21 import java.util.Iterator; 22 import java.util.List; 23 import java.util.regex.Matcher; 24 import java.util.regex.Pattern; 25 26 /** 27 * Stores native allocation information. 28 * <p/>Contains number of allocations, their size and the stack trace. 29 * <p/>Note: the ddmlib does not resolve the stack trace automatically. While this class provides 30 * storage for resolved stack trace, this is merely for convenience. 31 */ 32 public final class NativeAllocationInfo { 33 /* Keywords used as delimiters in the string representation of a NativeAllocationInfo */ 34 public static final String END_STACKTRACE_KW = "EndStacktrace"; 35 public static final String BEGIN_STACKTRACE_KW = "BeginStacktrace:"; 36 public static final String TOTAL_SIZE_KW = "TotalSize:"; 37 public static final String SIZE_KW = "Size:"; 38 public static final String ALLOCATIONS_KW = "Allocations:"; 39 40 /* constants for flag bits */ 41 private static final int FLAG_ZYGOTE_CHILD = (1<<31); 42 private static final int FLAG_MASK = (FLAG_ZYGOTE_CHILD); 43 44 /** Libraries whose methods will be assumed to be not part of the user code. */ 45 private static final List<String> FILTERED_LIBRARIES = Arrays.asList(new String[] { 46 "libc.so", 47 "libc_malloc_debug_leak.so", 48 }); 49 50 /** Method names that should be assumed to be not part of the user code. */ 51 private static final List<Pattern> FILTERED_METHOD_NAME_PATTERNS = Arrays.asList(new Pattern[] { 52 Pattern.compile("malloc", Pattern.CASE_INSENSITIVE), 53 Pattern.compile("calloc", Pattern.CASE_INSENSITIVE), 54 Pattern.compile("realloc", Pattern.CASE_INSENSITIVE), 55 Pattern.compile("operator new", Pattern.CASE_INSENSITIVE), 56 Pattern.compile("memalign", Pattern.CASE_INSENSITIVE), 57 }); 58 59 private final int mSize; 60 61 private final boolean mIsZygoteChild; 62 63 private final int mAllocations; 64 65 private final ArrayList<Long> mStackCallAddresses = new ArrayList<Long>(); 66 67 private ArrayList<NativeStackCallInfo> mResolvedStackCall = null; 68 69 private boolean mIsStackCallResolved = false; 70 71 /** 72 * Constructs a new {@link NativeAllocationInfo}. 73 * @param size The size of the allocations. 74 * @param allocations the allocation count 75 */ NativeAllocationInfo(int size, int allocations)76 public NativeAllocationInfo(int size, int allocations) { 77 this.mSize = size & ~FLAG_MASK; 78 this.mIsZygoteChild = ((size & FLAG_ZYGOTE_CHILD) != 0); 79 this.mAllocations = allocations; 80 } 81 82 /** 83 * Adds a stack call address for this allocation. 84 * @param address The address to add. 85 */ addStackCallAddress(long address)86 public void addStackCallAddress(long address) { 87 mStackCallAddresses.add(address); 88 } 89 90 /** 91 * Returns the total size of this allocation. 92 */ getSize()93 public int getSize() { 94 return mSize; 95 } 96 97 /** 98 * Returns whether the allocation happened in a child of the zygote 99 * process. 100 */ isZygoteChild()101 public boolean isZygoteChild() { 102 return mIsZygoteChild; 103 } 104 105 /** 106 * Returns the allocation count. 107 */ getAllocationCount()108 public int getAllocationCount() { 109 return mAllocations; 110 } 111 112 /** 113 * Returns whether the stack call addresses have been resolved into 114 * {@link NativeStackCallInfo} objects. 115 */ isStackCallResolved()116 public boolean isStackCallResolved() { 117 return mIsStackCallResolved; 118 } 119 120 /** 121 * Returns the stack call of this allocation as raw addresses. 122 * @return the list of addresses where the allocation happened. 123 */ getStackCallAddresses()124 public List<Long> getStackCallAddresses() { 125 return mStackCallAddresses; 126 } 127 128 /** 129 * Sets the resolved stack call for this allocation. 130 * <p/> 131 * If <code>resolvedStackCall</code> is non <code>null</code> then 132 * {@link #isStackCallResolved()} will return <code>true</code> after this call. 133 * @param resolvedStackCall The list of {@link NativeStackCallInfo}. 134 */ setResolvedStackCall(List<NativeStackCallInfo> resolvedStackCall)135 public synchronized void setResolvedStackCall(List<NativeStackCallInfo> resolvedStackCall) { 136 if (mResolvedStackCall == null) { 137 mResolvedStackCall = new ArrayList<NativeStackCallInfo>(); 138 } else { 139 mResolvedStackCall.clear(); 140 } 141 mResolvedStackCall.addAll(resolvedStackCall); 142 mIsStackCallResolved = mResolvedStackCall.size() != 0; 143 } 144 145 /** 146 * Returns the resolved stack call. 147 * @return An array of {@link NativeStackCallInfo} or <code>null</code> if the stack call 148 * was not resolved. 149 * @see #setResolvedStackCall(ArrayList) 150 * @see #isStackCallResolved() 151 */ getResolvedStackCall()152 public synchronized List<NativeStackCallInfo> getResolvedStackCall() { 153 if (mIsStackCallResolved) { 154 return mResolvedStackCall; 155 } 156 157 return null; 158 } 159 160 /** 161 * Indicates whether some other object is "equal to" this one. 162 * @param obj the reference object with which to compare. 163 * @return <code>true</code> if this object is equal to the obj argument; 164 * <code>false</code> otherwise. 165 * @see java.lang.Object#equals(java.lang.Object) 166 */ 167 @Override equals(Object obj)168 public boolean equals(Object obj) { 169 if (obj == this) 170 return true; 171 if (obj instanceof NativeAllocationInfo) { 172 NativeAllocationInfo mi = (NativeAllocationInfo)obj; 173 // quick compare of size, alloc, and stackcall size 174 if (mSize != mi.mSize || mAllocations != mi.mAllocations || 175 mStackCallAddresses.size() != mi.mStackCallAddresses.size()) { 176 return false; 177 } 178 // compare the stack addresses 179 int count = mStackCallAddresses.size(); 180 for (int i = 0 ; i < count ; i++) { 181 long a = mStackCallAddresses.get(i); 182 long b = mi.mStackCallAddresses.get(i); 183 if (a != b) { 184 return false; 185 } 186 } 187 188 return true; 189 } 190 return false; 191 } 192 193 194 @Override hashCode()195 public int hashCode() { 196 // Follow Effective Java's recipe re hash codes. 197 // Includes all the fields looked at by equals(). 198 199 int result = 17; // arbitrary starting point 200 201 result = 31 * result + mSize; 202 result = 31 * result + mAllocations; 203 result = 31 * result + mStackCallAddresses.size(); 204 205 for (long addr : mStackCallAddresses) { 206 result = 31 * result + (int) (addr ^ (addr >>> 32)); 207 } 208 209 return result; 210 } 211 212 /** 213 * Returns a string representation of the object. 214 * @see java.lang.Object#toString() 215 */ 216 @Override toString()217 public String toString() { 218 StringBuffer buffer = new StringBuffer(); 219 buffer.append(ALLOCATIONS_KW); 220 buffer.append(' '); 221 buffer.append(mAllocations); 222 buffer.append('\n'); 223 224 buffer.append(SIZE_KW); 225 buffer.append(' '); 226 buffer.append(mSize); 227 buffer.append('\n'); 228 229 buffer.append(TOTAL_SIZE_KW); 230 buffer.append(' '); 231 buffer.append(mSize * mAllocations); 232 buffer.append('\n'); 233 234 if (mResolvedStackCall != null) { 235 buffer.append(BEGIN_STACKTRACE_KW); 236 buffer.append('\n'); 237 for (NativeStackCallInfo source : mResolvedStackCall) { 238 long addr = source.getAddress(); 239 if (addr == 0) { 240 continue; 241 } 242 243 if (source.getLineNumber() != -1) { 244 buffer.append(String.format("\t%1$08x\t%2$s --- %3$s --- %4$s:%5$d\n", addr, 245 source.getLibraryName(), source.getMethodName(), 246 source.getSourceFile(), source.getLineNumber())); 247 } else { 248 buffer.append(String.format("\t%1$08x\t%2$s --- %3$s --- %4$s\n", addr, 249 source.getLibraryName(), source.getMethodName(), source.getSourceFile())); 250 } 251 } 252 buffer.append(END_STACKTRACE_KW); 253 buffer.append('\n'); 254 } 255 256 return buffer.toString(); 257 } 258 259 /** 260 * Returns the first {@link NativeStackCallInfo} that is relevant. 261 * <p/> 262 * A relevant <code>NativeStackCallInfo</code> is a stack call that is not deep in the 263 * lower level of the libc, but the actual method that performed the allocation. 264 * @return a <code>NativeStackCallInfo</code> or <code>null</code> if the stack call has not 265 * been processed from the raw addresses. 266 * @see #setResolvedStackCall(ArrayList) 267 * @see #isStackCallResolved() 268 */ getRelevantStackCallInfo()269 public synchronized NativeStackCallInfo getRelevantStackCallInfo() { 270 if (mIsStackCallResolved && mResolvedStackCall != null) { 271 for (NativeStackCallInfo info : mResolvedStackCall) { 272 if (isRelevantLibrary(info.getLibraryName()) 273 && isRelevantMethod(info.getMethodName())) { 274 return info; 275 } 276 } 277 278 // couldnt find a relevant one, so we'll return the first one if it exists. 279 if (mResolvedStackCall.size() > 0) 280 return mResolvedStackCall.get(0); 281 } 282 283 return null; 284 } 285 isRelevantLibrary(String libPath)286 private boolean isRelevantLibrary(String libPath) { 287 for (String l : FILTERED_LIBRARIES) { 288 if (libPath.endsWith(l)) { 289 return false; 290 } 291 } 292 293 return true; 294 } 295 isRelevantMethod(String methodName)296 private boolean isRelevantMethod(String methodName) { 297 for (Pattern p : FILTERED_METHOD_NAME_PATTERNS) { 298 Matcher m = p.matcher(methodName); 299 if (m.find()) { 300 return false; 301 } 302 } 303 304 return true; 305 } 306 } 307