Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2c3dc9b1 authored by Suprabh Shukla's avatar Suprabh Shukla
Browse files

Removing TimeSparseArray

The only added value in this subclass comes from the added methods to
binary search on its keys. Pulling these methods up to the base class
LongSparseArray so they can be useful for all its instances.

Renamed variables and methods to be more generic and replaced the
handwritten binary search with an equivalent call to
Arrays.binarySearch to reduce maintenance overhead.

All occurrences of TimeSparseArray have been replaced with
LongSparseArray. No behavior change is expected.

Test: Builds, boots.
Test: atest FrameworksCoreTests:LongSparseArrayTest

Bug: 282264041
Change-Id: I6e87560ad5afe85118c9ffaac9ea8a506a181af0
parent 45622354
Loading
Loading
Loading
Loading
+33 −0
Original line number Original line Diff line number Diff line
@@ -208,6 +208,39 @@ public class LongSparseArray<E> implements Cloneable {
        // Log.e("SparseArray", "gc end with " + mSize);
        // Log.e("SparseArray", "gc end with " + mSize);
    }
    }


    /**
     * Returns the index of the first element whose key is greater than or equal to the given key.
     *
     * @param key The key for which to search the array.
     * @return The smallest {@code index} for which {@code (keyAt(index) >= key)} is
     * {@code true}, or {@link #size() size} if no such {@code index} exists.
     * @hide
     */
    public int firstIndexOnOrAfter(long key) {
        if (mGarbage) {
            gc();
        }
        final int index = Arrays.binarySearch(mKeys, 0, size(), key);
        return (index >= 0) ? index : -index - 1;
    }

    /**
     * Returns the index of the last element whose key is less than or equal to the given key.
     *
     * @param key The key for which to search the array.
     * @return The largest {@code index} for which {@code (keyAt(index) <= key)} is
     * {@code true}, or {@code -1} if no such {@code index} exists.
     * @hide
     */
    public int lastIndexOnOrBefore(long key) {
        final int index = firstIndexOnOrAfter(key);

        if (index < size() && keyAt(index) == key) {
            return index;
        }
        return index - 1;
    }

    /**
    /**
     * Adds a mapping from the specified key to the specified value,
     * Adds a mapping from the specified key to the specified value,
     * replacing the previous mapping from the specified key if there
     * replacing the previous mapping from the specified key if there
+0 −99
Original line number Original line Diff line number Diff line
/**
 * Copyright (C) 2014 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;

import java.util.Objects;

/**
 * An array that indexes by a long timestamp, representing milliseconds since the epoch.
 * @param <E> The type of values this container maps to a timestamp.
 *
 * {@hide}
 */
public class TimeSparseArray<E> extends LongSparseArray<E> {
    private static final String TAG = TimeSparseArray.class.getSimpleName();

    private boolean mWtfReported;

    /**
     * Finds the index of the first element whose timestamp is greater or equal to
     * the given time.
     *
     * @param time The timestamp for which to search the array.
     * @return The smallest {@code index} for which {@code (keyAt(index) >= timeStamp)} is
     * {@code true}, or {@link #size() size} if no such {@code index} exists.
     */
    public int closestIndexOnOrAfter(long time) {
        final int size = size();
        int result = size;
        int lo = 0;
        int hi = size - 1;
        while (lo <= hi) {
            final int mid = lo + ((hi - lo) / 2);
            final long key = keyAt(mid);

            if (time > key) {
                lo = mid + 1;
            } else if (time < key) {
                hi = mid - 1;
                result = mid;
            } else {
                return mid;
            }
        }
        return result;
    }

    /**
     * {@inheritDoc}
     *
     * <p> This container can store only one value for each timestamp. And so ideally, the caller
     * should ensure that there are no collisions. Reporting a {@link Slog#wtf(String, String)}
     * if that happens, as that will lead to the previous value being overwritten.
     */
    @Override
    public void put(long key, E value) {
        final int index = indexOfKey(key);
        if (index >= 0) {
            final E curValue = valueAt(index);
            if (Objects.equals(curValue, value)) {
                Log.w(TAG, "Overwriting value at " + key + " by equal value " + value);
            } else if (!mWtfReported) {
                Slog.wtf(TAG, "Overwriting value " + curValue + " by " + value + " at " + key);
                mWtfReported = true;
            }
        }
        super.put(key, value);
    }

    /**
     * Finds the index of the first element whose timestamp is less than or equal to
     * the given time.
     *
     * @param time The timestamp for which to search the array.
     * @return The largest {@code index} for which {@code (keyAt(index) <= timeStamp)} is
     * {@code true}, or -1 if no such {@code index} exists.
     */
    public int closestIndexOnOrBefore(long time) {
        final int index = closestIndexOnOrAfter(time);

        if (index < size() && keyAt(index) == time) {
            return index;
        }
        return index - 1;
    }
}
+69 −0
Original line number Original line Diff line number Diff line
@@ -51,4 +51,73 @@ public class LongSparseArrayTest {
                    .isFalse();
                    .isFalse();
        }
        }
    }
    }

    @Test
    public void firstIndexOnOrAfter() {
        final LongSparseArray<Object> longSparseArray = new LongSparseArray<>();

        // Values don't matter for this test.
        longSparseArray.put(51, new Object());
        longSparseArray.put(10, new Object());
        longSparseArray.put(59, new Object());

        assertThat(longSparseArray.size()).isEqualTo(3);

        // Testing any number arbitrarily smaller than 10.
        assertThat(longSparseArray.firstIndexOnOrAfter(-141213)).isEqualTo(0);
        for (long time = -43; time <= 10; time++) {
            assertThat(longSparseArray.firstIndexOnOrAfter(time)).isEqualTo(0);
        }

        for (long time = 11; time <= 51; time++) {
            assertThat(longSparseArray.firstIndexOnOrAfter(time)).isEqualTo(1);
        }

        for (long time = 52; time <= 59; time++) {
            assertThat(longSparseArray.firstIndexOnOrAfter(time)).isEqualTo(2);
        }

        for (long time = 60; time <= 102; time++) {
            assertThat(longSparseArray.firstIndexOnOrAfter(time)).isEqualTo(3);
        }
        // Testing any number arbitrarily larger than 59.
        assertThat(longSparseArray.firstIndexOnOrAfter(15332)).isEqualTo(3);
    }

    @Test
    public void lastIndexOnOrBefore() {
        final LongSparseArray<Object> longSparseArray = new LongSparseArray<>();

        // Values don't matter for this test.
        longSparseArray.put(21, new Object());
        longSparseArray.put(4, new Object());
        longSparseArray.put(91, new Object());
        longSparseArray.put(39, new Object());

        assertThat(longSparseArray.size()).isEqualTo(4);

        // Testing any number arbitrarily smaller than 4.
        assertThat(longSparseArray.lastIndexOnOrBefore(-1478133)).isEqualTo(-1);
        for (long time = -42; time < 4; time++) {
            assertThat(longSparseArray.lastIndexOnOrBefore(time)).isEqualTo(-1);
        }

        for (long time = 4; time < 21; time++) {
            assertThat(longSparseArray.lastIndexOnOrBefore(time)).isEqualTo(0);
        }

        for (long time = 21; time < 39; time++) {
            assertThat(longSparseArray.lastIndexOnOrBefore(time)).isEqualTo(1);
        }

        for (long time = 39; time < 91; time++) {
            assertThat(longSparseArray.lastIndexOnOrBefore(time)).isEqualTo(2);
        }

        for (long time = 91; time < 109; time++) {
            assertThat(longSparseArray.lastIndexOnOrBefore(time)).isEqualTo(3);
        }
        // Testing any number arbitrarily larger than 91.
        assertThat(longSparseArray.lastIndexOnOrBefore(1980732)).isEqualTo(3);
    }
}
}
+0 −103
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2020 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;

import static com.google.common.truth.Truth.assertThat;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;

import org.junit.Test;
import org.junit.runner.RunWith;

/**
 * Tests for {@link TimeSparseArray}.
 * This class only tests subclass specific functionality. Tests for the super class
 * {@link LongSparseArray} should be covered under {@link LongSparseArrayTest}.
 */
@SmallTest
@RunWith(AndroidJUnit4.class)
public class TimeSparseArrayTest {

    @Test
    public void closestIndexOnOrAfter() {
        final TimeSparseArray<Object> timeSparseArray = new TimeSparseArray<>();

        // Values don't matter for this test.
        timeSparseArray.put(51, new Object());
        timeSparseArray.put(10, new Object());
        timeSparseArray.put(59, new Object());

        assertThat(timeSparseArray.size()).isEqualTo(3);

        // Testing any number arbitrarily smaller than 10.
        assertThat(timeSparseArray.closestIndexOnOrAfter(-141213)).isEqualTo(0);
        for (long time = -43; time <= 10; time++) {
            assertThat(timeSparseArray.closestIndexOnOrAfter(time)).isEqualTo(0);
        }

        for (long time = 11; time <= 51; time++) {
            assertThat(timeSparseArray.closestIndexOnOrAfter(time)).isEqualTo(1);
        }

        for (long time = 52; time <= 59; time++) {
            assertThat(timeSparseArray.closestIndexOnOrAfter(time)).isEqualTo(2);
        }

        for (long time = 60; time <= 102; time++) {
            assertThat(timeSparseArray.closestIndexOnOrAfter(time)).isEqualTo(3);
        }
        // Testing any number arbitrarily larger than 59.
        assertThat(timeSparseArray.closestIndexOnOrAfter(15332)).isEqualTo(3);
    }

    @Test
    public void closestIndexOnOrBefore() {
        final TimeSparseArray<Object> timeSparseArray = new TimeSparseArray<>();

        // Values don't matter for this test.
        timeSparseArray.put(21, new Object());
        timeSparseArray.put(4, new Object());
        timeSparseArray.put(91, new Object());
        timeSparseArray.put(39, new Object());

        assertThat(timeSparseArray.size()).isEqualTo(4);

        // Testing any number arbitrarily smaller than 4.
        assertThat(timeSparseArray.closestIndexOnOrBefore(-1478133)).isEqualTo(-1);
        for (long time = -42; time < 4; time++) {
            assertThat(timeSparseArray.closestIndexOnOrBefore(time)).isEqualTo(-1);
        }

        for (long time = 4; time < 21; time++) {
            assertThat(timeSparseArray.closestIndexOnOrBefore(time)).isEqualTo(0);
        }

        for (long time = 21; time < 39; time++) {
            assertThat(timeSparseArray.closestIndexOnOrBefore(time)).isEqualTo(1);
        }

        for (long time = 39; time < 91; time++) {
            assertThat(timeSparseArray.closestIndexOnOrBefore(time)).isEqualTo(2);
        }

        for (long time = 91; time < 109; time++) {
            assertThat(timeSparseArray.closestIndexOnOrBefore(time)).isEqualTo(3);
        }
        // Testing any number arbitrarily larger than 91.
        assertThat(timeSparseArray.closestIndexOnOrBefore(1980732)).isEqualTo(3);
    }
}
+18 −18
Original line number Original line Diff line number Diff line
@@ -32,12 +32,12 @@ import android.os.Trace;
import android.os.UserHandle;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig;
import android.util.IndentingPrintWriter;
import android.util.IndentingPrintWriter;
import android.util.LongSparseArray;
import android.util.Slog;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.SparseIntArray;
import android.util.SparseLongArray;
import android.util.SparseLongArray;
import android.util.TimeSparseArray;
import android.util.TimeUtils;
import android.util.TimeUtils;


import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting;
@@ -74,12 +74,12 @@ public class CpuWakeupStats {
    private final WakingActivityHistory mRecentWakingActivity;
    private final WakingActivityHistory mRecentWakingActivity;


    @VisibleForTesting
    @VisibleForTesting
    final TimeSparseArray<Wakeup> mWakeupEvents = new TimeSparseArray<>();
    final LongSparseArray<Wakeup> mWakeupEvents = new LongSparseArray<>();


    /* Maps timestamp -> {subsystem  -> {uid -> procState}} */
    /* Maps timestamp -> {subsystem  -> {uid -> procState}} */
    @VisibleForTesting
    @VisibleForTesting
    final TimeSparseArray<SparseArray<SparseIntArray>> mWakeupAttribution =
    final LongSparseArray<SparseArray<SparseIntArray>> mWakeupAttribution =
            new TimeSparseArray<>();
            new LongSparseArray<>();


    final SparseIntArray mUidProcStates = new SparseIntArray();
    final SparseIntArray mUidProcStates = new SparseIntArray();
    private final SparseIntArray mReusableUidProcStates = new SparseIntArray(4);
    private final SparseIntArray mReusableUidProcStates = new SparseIntArray(4);
@@ -218,11 +218,11 @@ public class CpuWakeupStats {
        // the last wakeup and its attribution (if computed) is always stored, even if that wakeup
        // the last wakeup and its attribution (if computed) is always stored, even if that wakeup
        // had occurred before retentionDuration.
        // had occurred before retentionDuration.
        final long retentionDuration = mConfig.WAKEUP_STATS_RETENTION_MS;
        final long retentionDuration = mConfig.WAKEUP_STATS_RETENTION_MS;
        int lastIdx = mWakeupEvents.closestIndexOnOrBefore(elapsedRealtime - retentionDuration);
        int lastIdx = mWakeupEvents.lastIndexOnOrBefore(elapsedRealtime - retentionDuration);
        for (int i = lastIdx; i >= 0; i--) {
        for (int i = lastIdx; i >= 0; i--) {
            mWakeupEvents.removeAt(i);
            mWakeupEvents.removeAt(i);
        }
        }
        lastIdx = mWakeupAttribution.closestIndexOnOrBefore(elapsedRealtime - retentionDuration);
        lastIdx = mWakeupAttribution.lastIndexOnOrBefore(elapsedRealtime - retentionDuration);
        for (int i = lastIdx; i >= 0; i--) {
        for (int i = lastIdx; i >= 0; i--) {
            mWakeupAttribution.removeAt(i);
            mWakeupAttribution.removeAt(i);
        }
        }
@@ -273,9 +273,9 @@ public class CpuWakeupStats {
            SparseIntArray uidProcStates) {
            SparseIntArray uidProcStates) {
        final long matchingWindowMillis = mConfig.WAKEUP_MATCHING_WINDOW_MS;
        final long matchingWindowMillis = mConfig.WAKEUP_MATCHING_WINDOW_MS;


        final int startIdx = mWakeupEvents.closestIndexOnOrAfter(
        final int startIdx = mWakeupEvents.firstIndexOnOrAfter(
                activityElapsed - matchingWindowMillis);
                activityElapsed - matchingWindowMillis);
        final int endIdx = mWakeupEvents.closestIndexOnOrBefore(
        final int endIdx = mWakeupEvents.lastIndexOnOrBefore(
                activityElapsed + matchingWindowMillis);
                activityElapsed + matchingWindowMillis);


        for (int wakeupIdx = startIdx; wakeupIdx <= endIdx; wakeupIdx++) {
        for (int wakeupIdx = startIdx; wakeupIdx <= endIdx; wakeupIdx++) {
@@ -404,7 +404,7 @@ public class CpuWakeupStats {
    static final class WakingActivityHistory {
    static final class WakingActivityHistory {
        private LongSupplier mRetentionSupplier;
        private LongSupplier mRetentionSupplier;
        @VisibleForTesting
        @VisibleForTesting
        final SparseArray<TimeSparseArray<SparseIntArray>> mWakingActivity = new SparseArray<>();
        final SparseArray<LongSparseArray<SparseIntArray>> mWakingActivity = new SparseArray<>();


        WakingActivityHistory(LongSupplier retentionSupplier) {
        WakingActivityHistory(LongSupplier retentionSupplier) {
            mRetentionSupplier = retentionSupplier;
            mRetentionSupplier = retentionSupplier;
@@ -414,9 +414,9 @@ public class CpuWakeupStats {
            if (uidProcStates == null) {
            if (uidProcStates == null) {
                return;
                return;
            }
            }
            TimeSparseArray<SparseIntArray> wakingActivity = mWakingActivity.get(subsystem);
            LongSparseArray<SparseIntArray> wakingActivity = mWakingActivity.get(subsystem);
            if (wakingActivity == null) {
            if (wakingActivity == null) {
                wakingActivity = new TimeSparseArray<>();
                wakingActivity = new LongSparseArray<>();
                mWakingActivity.put(subsystem, wakingActivity);
                mWakingActivity.put(subsystem, wakingActivity);
            }
            }
            final SparseIntArray uidsToBlame = wakingActivity.get(elapsedRealtime);
            final SparseIntArray uidsToBlame = wakingActivity.get(elapsedRealtime);
@@ -435,7 +435,7 @@ public class CpuWakeupStats {
            // Limit activity history per subsystem to the last retention period as supplied by
            // Limit activity history per subsystem to the last retention period as supplied by
            // mRetentionSupplier. Note that the last activity is always present, even if it
            // mRetentionSupplier. Note that the last activity is always present, even if it
            // occurred before the retention period.
            // occurred before the retention period.
            final int endIdx = wakingActivity.closestIndexOnOrBefore(
            final int endIdx = wakingActivity.lastIndexOnOrBefore(
                    elapsedRealtime - mRetentionSupplier.getAsLong());
                    elapsedRealtime - mRetentionSupplier.getAsLong());
            for (int i = endIdx; i >= 0; i--) {
            for (int i = endIdx; i >= 0; i--) {
                wakingActivity.removeAt(i);
                wakingActivity.removeAt(i);
@@ -445,11 +445,11 @@ public class CpuWakeupStats {
        SparseIntArray removeBetween(int subsystem, long startElapsed, long endElapsed) {
        SparseIntArray removeBetween(int subsystem, long startElapsed, long endElapsed) {
            final SparseIntArray uidsToReturn = new SparseIntArray();
            final SparseIntArray uidsToReturn = new SparseIntArray();


            final TimeSparseArray<SparseIntArray> activityForSubsystem =
            final LongSparseArray<SparseIntArray> activityForSubsystem =
                    mWakingActivity.get(subsystem);
                    mWakingActivity.get(subsystem);
            if (activityForSubsystem != null) {
            if (activityForSubsystem != null) {
                final int startIdx = activityForSubsystem.closestIndexOnOrAfter(startElapsed);
                final int startIdx = activityForSubsystem.firstIndexOnOrAfter(startElapsed);
                final int endIdx = activityForSubsystem.closestIndexOnOrBefore(endElapsed);
                final int endIdx = activityForSubsystem.lastIndexOnOrBefore(endElapsed);
                for (int i = endIdx; i >= startIdx; i--) {
                for (int i = endIdx; i >= startIdx; i--) {
                    final SparseIntArray uidsForTime = activityForSubsystem.valueAt(i);
                    final SparseIntArray uidsForTime = activityForSubsystem.valueAt(i);
                    for (int j = 0; j < uidsForTime.size(); j++) {
                    for (int j = 0; j < uidsForTime.size(); j++) {
@@ -463,8 +463,8 @@ public class CpuWakeupStats {
                    activityForSubsystem.removeAt(i);
                    activityForSubsystem.removeAt(i);
                }
                }
                // Generally waking activity is a high frequency occurrence for any subsystem, so we
                // Generally waking activity is a high frequency occurrence for any subsystem, so we
                // don't delete the TimeSparseArray even if it is now empty, to avoid object churn.
                // don't delete the LongSparseArray even if it is now empty, to avoid object churn.
                // This will leave one TimeSparseArray per subsystem, which are few right now.
                // This will leave one LongSparseArray per subsystem, which are few right now.
            }
            }
            return uidsToReturn.size() > 0 ? uidsToReturn : null;
            return uidsToReturn.size() > 0 ? uidsToReturn : null;
        }
        }
@@ -474,7 +474,7 @@ public class CpuWakeupStats {
            pw.increaseIndent();
            pw.increaseIndent();
            for (int i = 0; i < mWakingActivity.size(); i++) {
            for (int i = 0; i < mWakingActivity.size(); i++) {
                pw.println("Subsystem " + subsystemToString(mWakingActivity.keyAt(i)) + ":");
                pw.println("Subsystem " + subsystemToString(mWakingActivity.keyAt(i)) + ":");
                final TimeSparseArray<SparseIntArray> wakingActivity = mWakingActivity.valueAt(i);
                final LongSparseArray<SparseIntArray> wakingActivity = mWakingActivity.valueAt(i);
                if (wakingActivity == null) {
                if (wakingActivity == null) {
                    continue;
                    continue;
                }
                }
Loading