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

Commit 1a5519a3 authored by Suprabh Shukla's avatar Suprabh Shukla
Browse files

Removing legacy alarm batching algorithm

The current method of batching has shown reduced number of batches and
has been turned on for a while now. It is now time to bid farewell to
the old batching algorithm that has served us well for multiple
releases.

Test: Builds and boots normally.
Test: atest FrameworksMockingServicesTests:com.android.server.alarm
Test: atest CtsAlarmManagerTestCases

Bug: 228652215
Change-Id: Ic1f16c74b1f8b9191910cbb25c357dc9985fb240
parent a7ff23f2
Loading
Loading
Loading
Loading
+1 −27
Original line number Diff line number Diff line
@@ -670,9 +670,6 @@ public class AlarmManagerService extends SystemService {
        private static final String KEY_APP_STANDBY_RESTRICTED_WINDOW =
                "app_standby_restricted_window";

        @VisibleForTesting
        static final String KEY_LAZY_BATCHING = "lazy_batching";

        private static final String KEY_TIME_TICK_ALLOWED_WHILE_IDLE =
                "time_tick_allowed_while_idle";

@@ -722,7 +719,6 @@ public class AlarmManagerService extends SystemService {
        private static final int DEFAULT_APP_STANDBY_RESTRICTED_QUOTA = 1;
        private static final long DEFAULT_APP_STANDBY_RESTRICTED_WINDOW = INTERVAL_DAY;

        private static final boolean DEFAULT_LAZY_BATCHING = true;
        private static final boolean DEFAULT_TIME_TICK_ALLOWED_WHILE_IDLE = true;

        /**
@@ -769,7 +765,6 @@ public class AlarmManagerService extends SystemService {
        public int APP_STANDBY_RESTRICTED_QUOTA = DEFAULT_APP_STANDBY_RESTRICTED_QUOTA;
        public long APP_STANDBY_RESTRICTED_WINDOW = DEFAULT_APP_STANDBY_RESTRICTED_WINDOW;

        public boolean LAZY_BATCHING = DEFAULT_LAZY_BATCHING;
        public boolean TIME_TICK_ALLOWED_WHILE_IDLE = DEFAULT_TIME_TICK_ALLOWED_WHILE_IDLE;

        public int ALLOW_WHILE_IDLE_QUOTA = DEFAULT_ALLOW_WHILE_IDLE_QUOTA;
@@ -973,14 +968,6 @@ public class AlarmManagerService extends SystemService {
                        case KEY_APP_STANDBY_RESTRICTED_WINDOW:
                            updateStandbyWindowsLocked();
                            break;
                        case KEY_LAZY_BATCHING:
                            final boolean oldLazyBatching = LAZY_BATCHING;
                            LAZY_BATCHING = properties.getBoolean(
                                    KEY_LAZY_BATCHING, DEFAULT_LAZY_BATCHING);
                            if (oldLazyBatching != LAZY_BATCHING) {
                                migrateAlarmsToNewStoreLocked();
                            }
                            break;
                        case KEY_TIME_TICK_ALLOWED_WHILE_IDLE:
                            TIME_TICK_ALLOWED_WHILE_IDLE = properties.getBoolean(
                                    KEY_TIME_TICK_ALLOWED_WHILE_IDLE,
@@ -1091,15 +1078,6 @@ public class AlarmManagerService extends SystemService {
            }
        }

        private void migrateAlarmsToNewStoreLocked() {
            final AlarmStore newStore = LAZY_BATCHING ? new LazyAlarmStore()
                    : new BatchingAlarmStore();
            final ArrayList<Alarm> allAlarms = mAlarmStore.remove((unused) -> true);
            newStore.addAll(allAlarms);
            mAlarmStore = newStore;
            mAlarmStore.setAlarmClockRemovalListener(mAlarmClockUpdater);
        }

        private void updateDeviceIdleFuzzBoundaries() {
            final DeviceConfig.Properties properties = DeviceConfig.getProperties(
                    DeviceConfig.NAMESPACE_ALARM_MANAGER,
@@ -1237,9 +1215,6 @@ public class AlarmManagerService extends SystemService {
            TimeUtils.formatDuration(APP_STANDBY_RESTRICTED_WINDOW, pw);
            pw.println();

            pw.print(KEY_LAZY_BATCHING, LAZY_BATCHING);
            pw.println();

            pw.print(KEY_TIME_TICK_ALLOWED_WHILE_IDLE, TIME_TICK_ALLOWED_WHILE_IDLE);
            pw.println();

@@ -1875,8 +1850,7 @@ public class AlarmManagerService extends SystemService {
            mHandler = new AlarmHandler();
            mConstants = new Constants(mHandler);

            mAlarmStore = mConstants.LAZY_BATCHING ? new LazyAlarmStore()
                    : new BatchingAlarmStore();
            mAlarmStore = new LazyAlarmStore();
            mAlarmStore.setAlarmClockRemovalListener(mAlarmClockUpdater);

            mAppWakeupHistory = new AppWakeupHistory(Constants.DEFAULT_APP_STANDBY_WINDOW);
+0 −408
Original line number 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 com.android.server.alarm;

import static com.android.server.alarm.AlarmManagerService.DEBUG_BATCH;
import static com.android.server.alarm.AlarmManagerService.clampPositive;
import static com.android.server.alarm.AlarmManagerService.dumpAlarmList;
import static com.android.server.alarm.AlarmManagerService.isTimeTickAlarm;

import android.app.AlarmManager;
import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.StatLogger;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.function.Predicate;

/**
 * Batching implementation of an Alarm Store.
 * This keeps the alarms in batches, which are sorted on the start time of their delivery window.
 */
public class BatchingAlarmStore implements AlarmStore {
    @VisibleForTesting
    static final String TAG = BatchingAlarmStore.class.getSimpleName();

    private final ArrayList<Batch> mAlarmBatches = new ArrayList<>();
    private int mSize;
    private Runnable mOnAlarmClockRemoved;

    interface Stats {
        int REBATCH_ALL_ALARMS = 0;
        int GET_COUNT = 1;
    }

    final StatLogger mStatLogger = new StatLogger(TAG + " stats", new String[]{
            "REBATCH_ALL_ALARMS",
            "GET_COUNT",
    });

    private static final Comparator<Batch> sBatchOrder = Comparator.comparingLong(b -> b.mStart);

    private static final Comparator<Alarm> sIncreasingTimeOrder = Comparator.comparingLong(
            Alarm::getWhenElapsed);

    @Override
    public void add(Alarm a) {
        insertAndBatchAlarm(a);
        mSize++;
    }

    @Override
    public void addAll(ArrayList<Alarm> alarms) {
        if (alarms == null) {
            return;
        }
        for (final Alarm a : alarms) {
            add(a);
        }
    }

    @Override
    public ArrayList<Alarm> remove(Predicate<Alarm> whichAlarms) {
        final ArrayList<Alarm> removed = new ArrayList<>();
        for (int i = mAlarmBatches.size() - 1; i >= 0; i--) {
            final Batch b = mAlarmBatches.get(i);
            removed.addAll(b.remove(whichAlarms));
            if (b.size() == 0) {
                mAlarmBatches.remove(i);
            }
        }
        if (!removed.isEmpty()) {
            mSize -= removed.size();
            // Not needed if only whole batches were removed, but keeping existing behavior.
            rebatchAllAlarms();
        }
        return removed;
    }

    @Override
    public void setAlarmClockRemovalListener(Runnable listener) {
        mOnAlarmClockRemoved = listener;
    }

    @Override
    public Alarm getNextWakeFromIdleAlarm() {
        for (final Batch batch : mAlarmBatches) {
            if ((batch.mFlags & AlarmManager.FLAG_WAKE_FROM_IDLE) == 0) {
                continue;
            }
            for (int i = 0; i < batch.size(); i++) {
                final Alarm a = batch.get(i);
                if ((a.flags & AlarmManager.FLAG_WAKE_FROM_IDLE) != 0) {
                    return a;
                }
            }
        }
        return null;
    }

    private void rebatchAllAlarms() {
        final long start = mStatLogger.getTime();
        final ArrayList<Batch> oldBatches = (ArrayList<Batch>) mAlarmBatches.clone();
        mAlarmBatches.clear();
        for (final Batch batch : oldBatches) {
            for (int i = 0; i < batch.size(); i++) {
                insertAndBatchAlarm(batch.get(i));
            }
        }
        mStatLogger.logDurationStat(Stats.REBATCH_ALL_ALARMS, start);
    }

    @Override
    public int size() {
        return mSize;
    }

    @Override
    public long getNextWakeupDeliveryTime() {
        for (Batch b : mAlarmBatches) {
            if (b.hasWakeups()) {
                return b.mStart;
            }
        }
        return 0;
    }

    @Override
    public long getNextDeliveryTime() {
        if (mAlarmBatches.size() > 0) {
            return mAlarmBatches.get(0).mStart;
        }
        return 0;
    }

    @Override
    public ArrayList<Alarm> removePendingAlarms(long nowElapsed) {
        final ArrayList<Alarm> removedAlarms = new ArrayList<>();
        while (mAlarmBatches.size() > 0) {
            final Batch batch = mAlarmBatches.get(0);
            if (batch.mStart > nowElapsed) {
                break;
            }
            mAlarmBatches.remove(0);
            for (int i = 0; i < batch.size(); i++) {
                removedAlarms.add(batch.get(i));
            }
        }
        mSize -= removedAlarms.size();
        return removedAlarms;
    }

    @Override
    public boolean updateAlarmDeliveries(AlarmDeliveryCalculator deliveryCalculator) {
        boolean changed = false;
        for (final Batch b : mAlarmBatches) {
            for (int i = 0; i < b.size(); i++) {
                changed |= deliveryCalculator.updateAlarmDelivery(b.get(i));
            }
        }
        if (changed) {
            rebatchAllAlarms();
        }
        return changed;
    }

    @Override
    public ArrayList<Alarm> asList() {
        final ArrayList<Alarm> allAlarms = new ArrayList<>();
        for (final Batch batch : mAlarmBatches) {
            for (int i = 0; i < batch.size(); i++) {
                allAlarms.add(batch.get(i));
            }
        }
        return allAlarms;
    }

    @Override
    public void dump(IndentingPrintWriter ipw, long nowElapsed, SimpleDateFormat sdf) {
        ipw.print("Pending alarm batches: ");
        ipw.println(mAlarmBatches.size());
        for (Batch b : mAlarmBatches) {
            ipw.print(b);
            ipw.println(':');
            ipw.increaseIndent();
            dumpAlarmList(ipw, b.mAlarms, nowElapsed, sdf);
            ipw.decreaseIndent();
        }
        mStatLogger.dump(ipw);
    }

    @Override
    public void dumpProto(ProtoOutputStream pos, long nowElapsed) {
        for (Batch b : mAlarmBatches) {
            b.dumpDebug(pos, AlarmManagerServiceDumpProto.PENDING_ALARM_BATCHES, nowElapsed);
        }
    }

    @Override
    public String getName() {
        return TAG;
    }

    @Override
    public int getCount(Predicate<Alarm> condition) {
        long start = mStatLogger.getTime();

        int count = 0;
        for (Batch b : mAlarmBatches) {
            for (int i = 0; i < b.size(); i++) {
                if (condition.test(b.get(i))) {
                    count++;
                }
            }
        }
        mStatLogger.logDurationStat(Stats.GET_COUNT, start);
        return count;
    }

    private void insertAndBatchAlarm(Alarm alarm) {
        final int whichBatch = ((alarm.flags & AlarmManager.FLAG_STANDALONE) != 0) ? -1
                : attemptCoalesce(alarm.getWhenElapsed(), alarm.getMaxWhenElapsed());

        if (whichBatch < 0) {
            addBatch(mAlarmBatches, new Batch(alarm));
        } else {
            final Batch batch = mAlarmBatches.get(whichBatch);
            if (batch.add(alarm)) {
                // The start time of this batch advanced, so batch ordering may
                // have just been broken.  Move it to where it now belongs.
                mAlarmBatches.remove(whichBatch);
                addBatch(mAlarmBatches, batch);
            }
        }
    }

    static void addBatch(ArrayList<Batch> list, Batch newBatch) {
        int index = Collections.binarySearch(list, newBatch, sBatchOrder);
        if (index < 0) {
            index = 0 - index - 1;
        }
        list.add(index, newBatch);
    }

    // Return the index of the matching batch, or -1 if none found.
    private int attemptCoalesce(long whenElapsed, long maxWhen) {
        final int n = mAlarmBatches.size();
        for (int i = 0; i < n; i++) {
            Batch b = mAlarmBatches.get(i);
            if ((b.mFlags & AlarmManager.FLAG_STANDALONE) == 0 && b.canHold(whenElapsed, maxWhen)) {
                return i;
            }
        }
        return -1;
    }

    final class Batch {
        long mStart;     // These endpoints are always in ELAPSED
        long mEnd;
        int mFlags;      // Flags for alarms, such as FLAG_STANDALONE.

        final ArrayList<Alarm> mAlarms = new ArrayList<>();

        Batch(Alarm seed) {
            mStart = seed.getWhenElapsed();
            mEnd = clampPositive(seed.getMaxWhenElapsed());
            mFlags = seed.flags;
            mAlarms.add(seed);
        }

        int size() {
            return mAlarms.size();
        }

        Alarm get(int index) {
            return mAlarms.get(index);
        }

        boolean canHold(long whenElapsed, long maxWhen) {
            return (mEnd >= whenElapsed) && (mStart <= maxWhen);
        }

        boolean add(Alarm alarm) {
            boolean newStart = false;
            // narrows the batch if necessary; presumes that canHold(alarm) is true
            int index = Collections.binarySearch(mAlarms, alarm, sIncreasingTimeOrder);
            if (index < 0) {
                index = 0 - index - 1;
            }
            mAlarms.add(index, alarm);
            if (DEBUG_BATCH) {
                Slog.v(TAG, "Adding " + alarm + " to " + this);
            }
            if (alarm.getWhenElapsed() > mStart) {
                mStart = alarm.getWhenElapsed();
                newStart = true;
            }
            if (alarm.getMaxWhenElapsed() < mEnd) {
                mEnd = alarm.getMaxWhenElapsed();
            }
            mFlags |= alarm.flags;

            if (DEBUG_BATCH) {
                Slog.v(TAG, "    => now " + this);
            }
            return newStart;
        }

        ArrayList<Alarm> remove(Predicate<Alarm> predicate) {
            final ArrayList<Alarm> removed = new ArrayList<>();
            long newStart = 0;  // recalculate endpoints as we go
            long newEnd = Long.MAX_VALUE;
            int newFlags = 0;
            for (int i = 0; i < mAlarms.size(); ) {
                Alarm alarm = mAlarms.get(i);
                if (predicate.test(alarm)) {
                    removed.add(mAlarms.remove(i));
                    if (alarm.alarmClock != null && mOnAlarmClockRemoved != null) {
                        mOnAlarmClockRemoved.run();
                    }
                    if (isTimeTickAlarm(alarm)) {
                        // This code path is not invoked when delivering alarms, only when removing
                        // alarms due to the caller cancelling it or getting uninstalled, etc.
                        Slog.wtf(TAG, "Removed TIME_TICK alarm");
                    }
                } else {
                    if (alarm.getWhenElapsed() > newStart) {
                        newStart = alarm.getWhenElapsed();
                    }
                    if (alarm.getMaxWhenElapsed() < newEnd) {
                        newEnd = alarm.getMaxWhenElapsed();
                    }
                    newFlags |= alarm.flags;
                    i++;
                }
            }
            if (!removed.isEmpty()) {
                // commit the new batch bounds
                mStart = newStart;
                mEnd = newEnd;
                mFlags = newFlags;
            }
            return removed;
        }

        boolean hasWakeups() {
            final int n = mAlarms.size();
            for (int i = 0; i < n; i++) {
                Alarm a = mAlarms.get(i);
                if (a.wakeup) {
                    return true;
                }
            }
            return false;
        }

        @Override
        public String toString() {
            StringBuilder b = new StringBuilder(40);
            b.append("Batch{");
            b.append(Integer.toHexString(this.hashCode()));
            b.append(" num=");
            b.append(size());
            b.append(" start=");
            b.append(mStart);
            b.append(" end=");
            b.append(mEnd);
            if (mFlags != 0) {
                b.append(" flgs=0x");
                b.append(Integer.toHexString(mFlags));
            }
            b.append('}');
            return b.toString();
        }

        public void dumpDebug(ProtoOutputStream proto, long fieldId, long nowElapsed) {
            final long token = proto.start(fieldId);

            proto.write(BatchProto.START_REALTIME, mStart);
            proto.write(BatchProto.END_REALTIME, mEnd);
            proto.write(BatchProto.FLAGS, mFlags);
            for (Alarm a : mAlarms) {
                a.dumpDebug(proto, BatchProto.ALARMS, nowElapsed);
            }

            proto.end(token);
        }
    }
}
+0 −32
Original line number Diff line number Diff line
@@ -74,7 +74,6 @@ import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_W
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WINDOW;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_EXACT_ALARM_DENY_LIST;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_LAZY_BATCHING;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_LISTENER_TIMEOUT;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MAX_DEVICE_IDLE_FUZZ;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MAX_INTERVAL;
@@ -3074,37 +3073,6 @@ public class AlarmManagerServiceTest {
        optionsSentOnExpiration(false, null);
    }

    @Test
    public void alarmStoreMigration() {
        setDeviceConfigBoolean(KEY_LAZY_BATCHING, false);
        final int numAlarms = 10;
        final PendingIntent[] pis = new PendingIntent[numAlarms];
        for (int i = 0; i < numAlarms; i++) {
            pis[i] = getNewMockPendingIntent();
            setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 1, pis[i]);
        }

        final ArrayList<Alarm> alarmsBefore = mService.mAlarmStore.asList();
        assertEquals(numAlarms, alarmsBefore.size());
        for (int i = 0; i < numAlarms; i++) {
            final PendingIntent pi = pis[i];
            assertTrue(i + "th PendingIntent missing: ",
                    alarmsBefore.removeIf(a -> a.matches(pi, null)));
        }
        assertEquals(BatchingAlarmStore.TAG, mService.mAlarmStore.getName());

        setDeviceConfigBoolean(KEY_LAZY_BATCHING, true);

        final ArrayList<Alarm> alarmsAfter = mService.mAlarmStore.asList();
        assertEquals(numAlarms, alarmsAfter.size());
        for (int i = 0; i < numAlarms; i++) {
            final PendingIntent pi = pis[i];
            assertTrue(i + "th PendingIntent missing: ",
                    alarmsAfter.removeIf(a -> a.matches(pi, null)));
        }
        assertEquals(LazyAlarmStore.TAG, mService.mAlarmStore.getName());
    }

    private void registerAppIds(String[] packages, Integer[] ids) {
        assertEquals(packages.length, ids.length);

+0 −1
Original line number Diff line number Diff line
@@ -56,7 +56,6 @@ public class AlarmStoreTest {
    public static Object[] stores() {
        return new AlarmStore[]{
                new LazyAlarmStore(),
                new BatchingAlarmStore(),
        };
    }