/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.ddmlib; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Stores native allocation information. * <p/>Contains number of allocations, their size and the stack trace. * <p/>Note: the ddmlib does not resolve the stack trace automatically. While this class provides * storage for resolved stack trace, this is merely for convenience. */ public class NativeAllocationInfo { /* Keywords used as delimiters in the string representation of a NativeAllocationInfo */ public static final String END_STACKTRACE_KW = "EndStacktrace"; public static final String BEGIN_STACKTRACE_KW = "BeginStacktrace:"; public static final String TOTAL_SIZE_KW = "TotalSize:"; public static final String SIZE_KW = "Size:"; public static final String ALLOCATIONS_KW = "Allocations:"; /* constants for flag bits */ private static final int FLAG_ZYGOTE_CHILD = (1<<31); private static final int FLAG_MASK = (FLAG_ZYGOTE_CHILD); /** Libraries whose methods will be assumed to be not part of the user code. */ private static final List<String> FILTERED_LIBRARIES = Arrays.asList( "libc.so", "libc_malloc_debug_leak.so" ); /** Method names that should be assumed to be not part of the user code. */ private static final List<Pattern> FILTERED_METHOD_NAME_PATTERNS = Arrays.asList( Pattern.compile("malloc", Pattern.CASE_INSENSITIVE), Pattern.compile("calloc", Pattern.CASE_INSENSITIVE), Pattern.compile("realloc", Pattern.CASE_INSENSITIVE), Pattern.compile("operator new", Pattern.CASE_INSENSITIVE), Pattern.compile("memalign", Pattern.CASE_INSENSITIVE) ); private final int mSize; private final boolean mIsZygoteChild; private int mAllocations; private final ArrayList<Long> mStackCallAddresses = new ArrayList<Long>(); private ArrayList<NativeStackCallInfo> mResolvedStackCall = null; private boolean mIsStackCallResolved = false; /** * Constructs a new {@link NativeAllocationInfo}. * @param size The size of the allocations. * @param allocations the allocation count */ public NativeAllocationInfo(int size, int allocations) { this.mSize = size & ~FLAG_MASK; this.mIsZygoteChild = ((size & FLAG_ZYGOTE_CHILD) != 0); this.mAllocations = allocations; } /** * Adds a stack call address for this allocation. * @param address The address to add. */ public void addStackCallAddress(long address) { mStackCallAddresses.add(address); } /** * Returns the size of this allocation. */ public int getSize() { return mSize; } /** * Returns whether the allocation happened in a child of the zygote * process. */ public boolean isZygoteChild() { return mIsZygoteChild; } /** * Returns the allocation count. */ public int getAllocationCount() { return mAllocations; } /** * Returns whether the stack call addresses have been resolved into * {@link NativeStackCallInfo} objects. */ public boolean isStackCallResolved() { return mIsStackCallResolved; } /** * Returns the stack call of this allocation as raw addresses. * @return the list of addresses where the allocation happened. */ public List<Long> getStackCallAddresses() { return mStackCallAddresses; } /** * Sets the resolved stack call for this allocation. * <p/> * If <code>resolvedStackCall</code> is non <code>null</code> then * {@link #isStackCallResolved()} will return <code>true</code> after this call. * @param resolvedStackCall The list of {@link NativeStackCallInfo}. */ public synchronized void setResolvedStackCall(List<NativeStackCallInfo> resolvedStackCall) { if (mResolvedStackCall == null) { mResolvedStackCall = new ArrayList<NativeStackCallInfo>(); } else { mResolvedStackCall.clear(); } mResolvedStackCall.addAll(resolvedStackCall); mIsStackCallResolved = !mResolvedStackCall.isEmpty(); } /** * Returns the resolved stack call. * @return An array of {@link NativeStackCallInfo} or <code>null</code> if the stack call * was not resolved. * @see #setResolvedStackCall(List) * @see #isStackCallResolved() */ public synchronized List<NativeStackCallInfo> getResolvedStackCall() { if (mIsStackCallResolved) { return mResolvedStackCall; } return null; } /** * Indicates whether some other object is "equal to" this one. * @param obj the reference object with which to compare. * @return <code>true</code> if this object is equal to the obj argument; * <code>false</code> otherwise. * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (obj == this) return true; if (obj instanceof NativeAllocationInfo) { NativeAllocationInfo mi = (NativeAllocationInfo)obj; // compare of size and alloc if (mSize != mi.mSize || mAllocations != mi.mAllocations) { return false; } // compare stacks return stackEquals(mi); } return false; } public boolean stackEquals(NativeAllocationInfo mi) { if (mStackCallAddresses.size() != mi.mStackCallAddresses.size()) { return false; } int count = mStackCallAddresses.size(); for (int i = 0 ; i < count ; i++) { long a = mStackCallAddresses.get(i); long b = mi.mStackCallAddresses.get(i); if (a != b) { return false; } } return true; } @Override public int hashCode() { // Follow Effective Java's recipe re hash codes. // Includes all the fields looked at by equals(). int result = 17; // arbitrary starting point result = 31 * result + mSize; result = 31 * result + mAllocations; result = 31 * result + mStackCallAddresses.size(); for (long addr : mStackCallAddresses) { result = 31 * result + (int) (addr ^ (addr >>> 32)); } return result; } /** * Returns a string representation of the object. * @see java.lang.Object#toString() */ @Override public String toString() { StringBuilder buffer = new StringBuilder(); buffer.append(ALLOCATIONS_KW); buffer.append(' '); buffer.append(mAllocations); buffer.append('\n'); buffer.append(SIZE_KW); buffer.append(' '); buffer.append(mSize); buffer.append('\n'); buffer.append(TOTAL_SIZE_KW); buffer.append(' '); buffer.append(mSize * mAllocations); buffer.append('\n'); if (mResolvedStackCall != null) { buffer.append(BEGIN_STACKTRACE_KW); buffer.append('\n'); for (NativeStackCallInfo source : mResolvedStackCall) { long addr = source.getAddress(); if (addr == 0) { continue; } if (source.getLineNumber() != -1) { buffer.append(String.format("\t%1$08x\t%2$s --- %3$s --- %4$s:%5$d\n", addr, source.getLibraryName(), source.getMethodName(), source.getSourceFile(), source.getLineNumber())); } else { buffer.append(String.format("\t%1$08x\t%2$s --- %3$s --- %4$s\n", addr, source.getLibraryName(), source.getMethodName(), source.getSourceFile())); } } buffer.append(END_STACKTRACE_KW); buffer.append('\n'); } return buffer.toString(); } /** * Returns the first {@link NativeStackCallInfo} that is relevant. * <p/> * A relevant <code>NativeStackCallInfo</code> is a stack call that is not deep in the * lower level of the libc, but the actual method that performed the allocation. * @return a <code>NativeStackCallInfo</code> or <code>null</code> if the stack call has not * been processed from the raw addresses. * @see #setResolvedStackCall(List) * @see #isStackCallResolved() */ public synchronized NativeStackCallInfo getRelevantStackCallInfo() { if (mIsStackCallResolved && mResolvedStackCall != null) { for (NativeStackCallInfo info : mResolvedStackCall) { if (isRelevantLibrary(info.getLibraryName()) && isRelevantMethod(info.getMethodName())) { return info; } } // couldn't find a relevant one, so we'll return the first one if it exists. if (!mResolvedStackCall.isEmpty()) return mResolvedStackCall.get(0); } return null; } private boolean isRelevantLibrary(String libPath) { for (String l : FILTERED_LIBRARIES) { if (libPath.endsWith(l)) { return false; } } return true; } private boolean isRelevantMethod(String methodName) { for (Pattern p : FILTERED_METHOD_NAME_PATTERNS) { Matcher m = p.matcher(methodName); if (m.find()) { return false; } } return true; } }