/* * 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 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 countSet = HashMultiset.create(); for (String callsite : mCallStack.values()) { countSet.add(callsite); } for (Multiset.Entry 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(); } }