/* * Copyright (C) 2017 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.systemui.util.leak; import android.os.SystemClock; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.Collection; import java.util.Map; import java.util.function.Predicate; /** * Tracks the size of collections. */ public class TrackedCollections { private static final long MILLIS_IN_MINUTE = 60 * 1000; private static final long HALFWAY_DELAY = 30 * MILLIS_IN_MINUTE; private final WeakIdentityHashMap, CollectionState> mCollections = new WeakIdentityHashMap<>(); /** * @see LeakDetector#trackCollection(Collection, String) */ public synchronized void track(Collection collection, String tag) { CollectionState collectionState = mCollections.get(collection); if (collectionState == null) { collectionState = new CollectionState(); collectionState.tag = tag; collectionState.startUptime = SystemClock.uptimeMillis(); mCollections.put(collection, collectionState); } if (collectionState.halfwayCount == -1 && SystemClock.uptimeMillis() - collectionState.startUptime > HALFWAY_DELAY) { collectionState.halfwayCount = collectionState.lastCount; } collectionState.lastCount = collection.size(); collectionState.lastUptime = SystemClock.uptimeMillis(); } private static class CollectionState { String tag; long startUptime; /** The number of elements in the collection at startUptime + HALFWAY_DELAY */ int halfwayCount = -1; /** The number of elements in the collection at lastUptime */ int lastCount = -1; long lastUptime; /** * Dump statistics about the tracked collection: * - the tag * - average elements inserted per hour during * - the first 30min of its existence * - after the first 30min * - overall * - the current size of the collection */ void dump(PrintWriter pw) { long now = SystemClock.uptimeMillis(); pw.format("%s: %.2f (start-30min) / %.2f (30min-now) / %.2f (start-now)" + " (growth rate in #/hour); %d (current size)", tag, ratePerHour(startUptime, 0, startUptime + HALFWAY_DELAY, halfwayCount), ratePerHour(startUptime + HALFWAY_DELAY, halfwayCount, now, lastCount), ratePerHour(startUptime, 0, now, lastCount), lastCount); } private float ratePerHour(long uptime1, int count1, long uptime2, int count2) { if (uptime1 >= uptime2 || count1 < 0 || count2 < 0) { return Float.NaN; } return ((float) count2 - count1) / (uptime2 - uptime1) * 60 * MILLIS_IN_MINUTE; } } public synchronized void dump(PrintWriter pw, Predicate> filter) { for (Map.Entry>, CollectionState> entry : mCollections.entrySet()) { Collection key = entry.getKey().get(); if (filter == null || key != null && filter.test(key)) { entry.getValue().dump(pw); pw.println(); } } } }