blob: de0f757a8adab0affb072804884c3f168fce44fe [file] [log] [blame]
/*
* Copyright (C) 2018 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 android.util.imagepool;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.util.HashMap;
import java.util.Map;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.Multiset;
/**
* Useful impl for debugging reproable error.
*/
public class ImagePoolStatsDebugImpl extends ImagePoolStatsProdImpl {
private static String PACKAGE_NAME = ImagePoolStats.class.getPackage().getName();
// Used for deugging purposes only.
private final Map<Integer, String> mCallStack = new HashMap<>();
private long mRequestedTotalBytes = 0;
private long mAllocatedOutsidePoolBytes = 0;
// Used for gc-related stats.
private long mPreviousGcCollection = 0;
private long mPreviousGcTime = 0;
/** Used for policy */
@Override
public void recordBucketCreation(int widthBucket, int heightBucket) {
super.recordBucketCreation(widthBucket, heightBucket);
}
@Override
public boolean fitsMaxCacheSize(int width, int height, long maxCacheSize) {
return super.fitsMaxCacheSize(width, height, maxCacheSize);
}
@Override
public void clear() {
super.clear();
mRequestedTotalBytes = 0;
mAllocatedOutsidePoolBytes = 0;
mTooBigForPoolCount = 0;
mCallStack.clear();
}
@Override
public void tooBigForCache() {
super.tooBigForCache();
}
/** Used for Debugging only */
@Override
public void recordBucketRequest(int w, int h) {
mRequestedTotalBytes += (w * h * ESTIMATED_PIXEL_BYTES);
}
@Override
public void recordAllocOutsidePool(int width, int height) {
mAllocatedOutsidePoolBytes += (width * height * ESTIMATED_PIXEL_BYTES);
}
@Override
public void acquiredImage(Integer imageHash) {
for (int i = 1; i < Thread.currentThread().getStackTrace().length; i++) {
StackTraceElement element = Thread.currentThread().getStackTrace()[i];
String str = element.toString();
if (!str.contains(PACKAGE_NAME)) {
mCallStack.put(imageHash, str);
break;
}
}
}
@Override
public void disposeImage(Integer imageHash) {
mCallStack.remove(imageHash);
}
@Override
public void start() {
long totalGarbageCollections = 0;
long garbageCollectionTime = 0;
for (GarbageCollectorMXBean gc : ManagementFactory.getGarbageCollectorMXBeans()) {
long count = gc.getCollectionCount();
if (count >= 0) {
totalGarbageCollections += count;
}
long time = gc.getCollectionTime();
if (time >= 0) {
garbageCollectionTime += time;
}
}
mPreviousGcCollection = totalGarbageCollections;
mPreviousGcTime = garbageCollectionTime;
}
private String calculateGcStatAndReturn() {
long totalGarbageCollections = 0;
long garbageCollectionTime = 0;
for (GarbageCollectorMXBean gc : ManagementFactory.getGarbageCollectorMXBeans()) {
long count = gc.getCollectionCount();
if (count > 0) {
totalGarbageCollections += count;
}
long time = gc.getCollectionTime();
if(time > 0) {
garbageCollectionTime += time;
}
}
totalGarbageCollections -= mPreviousGcCollection;
garbageCollectionTime -= mPreviousGcTime;
StringBuilder builder = new StringBuilder();
builder.append("Total Garbage Collections: ");
builder.append(totalGarbageCollections);
builder.append("\n");
builder.append("Total Garbage Collection Time (ms): ");
builder.append(garbageCollectionTime);
builder.append("\n");
return builder.toString();
}
@Override
public String getStatistic() {
StringBuilder builder = new StringBuilder();
builder.append(calculateGcStatAndReturn());
builder.append("Memory\n");
builder.append(" requested total : ");
builder.append(mRequestedTotalBytes / 1_000_000);
builder.append(" MB\n");
builder.append(" allocated (in pool) : ");
builder.append(mAllocateTotalBytes / 1_000_000);
builder.append(" MB\n");
builder.append(" allocated (out of pool) : ");
builder.append(mAllocatedOutsidePoolBytes / 1_000_000);
builder.append(" MB\n");
double percent = (1.0 - (double) mRequestedTotalBytes / (mAllocateTotalBytes +
mAllocatedOutsidePoolBytes));
if (percent < 0.0) {
builder.append(" saved : ");
builder.append(-1.0 * percent);
builder.append("%\n");
} else {
builder.append(" wasting : ");
builder.append(percent);
builder.append("%\n");
}
builder.append("Undispose images\n");
Multiset<String> countSet = HashMultiset.create();
for (String callsite : mCallStack.values()) {
countSet.add(callsite);
}
for (Multiset.Entry<String> entry : countSet.entrySet()) {
builder.append(" - ");
builder.append(entry.getElement());
builder.append(" - missed dispose : ");
builder.append(entry.getCount());
builder.append(" times\n");
}
builder.append("Number of times requested image didn't fit the pool : ");
builder.append(mTooBigForPoolCount);
builder.append("\n");
return builder.toString();
}
}