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

Commit 70b43956 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Introducing a lazy alarm store"

parents 8e5bcf84 5c46b18d
Loading
Loading
Loading
Loading
+40 −4
Original line number Diff line number Diff line
@@ -268,6 +268,7 @@ public class AlarmManagerService extends SystemService {
     */
    Bundle mIdleOptions;

    // TODO(b/172085676): Move inside alarm store.
    private final SparseArray<AlarmManager.AlarmClockInfo> mNextAlarmClockForUser =
            new SparseArray<>();
    private final SparseArray<AlarmManager.AlarmClockInfo> mTmpSparseAlarmClockArray =
@@ -276,6 +277,9 @@ public class AlarmManagerService extends SystemService {
            new SparseBooleanArray();
    private boolean mNextAlarmClockMayChange;

    @GuardedBy("mLock")
    private final Runnable mAlarmClockUpdater = () -> mNextAlarmClockMayChange = true;

    // May only use on mHandler's thread, locking not required.
    private final SparseArray<AlarmManager.AlarmClockInfo> mHandlerSparseAlarmClockArray =
            new SparseArray<>();
@@ -410,6 +414,9 @@ 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 long DEFAULT_MIN_FUTURITY = 5 * 1000;
        private static final long DEFAULT_MIN_INTERVAL = 60 * 1000;
        private static final long DEFAULT_MAX_INTERVAL = 365 * DateUtils.DAY_IN_MILLIS;
@@ -432,6 +439,8 @@ public class AlarmManagerService extends SystemService {
        private static final int DEFAULT_APP_STANDBY_RESTRICTED_QUOTA = 1;
        private static final long DEFAULT_APP_STANDBY_RESTRICTED_WINDOW = MILLIS_IN_DAY;

        private static final boolean DEFAULT_LAZY_BATCHING = false;

        // Minimum futurity of a new alarm
        public long MIN_FUTURITY = DEFAULT_MIN_FUTURITY;

@@ -460,6 +469,8 @@ 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;

        private long mLastAllowWhileIdleWhitelistDuration = -1;

        Constants() {
@@ -538,6 +549,14 @@ 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;
                        default:
                            if (name.startsWith(KEY_PREFIX_STANDBY_QUOTA) && !standbyQuotaUpdated) {
                                // The quotas need to be updated in order, so we can't just rely
@@ -551,6 +570,15 @@ 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 updateStandbyQuotasLocked() {
            // The bucket quotas need to be read as an atomic unit but the properties passed to
            // onPropertiesChanged may only have one key populated at a time.
@@ -659,6 +687,9 @@ public class AlarmManagerService extends SystemService {
            TimeUtils.formatDuration(APP_STANDBY_RESTRICTED_WINDOW, pw);
            pw.println();

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

            pw.decreaseIndent();
        }

@@ -770,7 +801,7 @@ public class AlarmManagerService extends SystemService {
    // minimum recurrence period or alarm futurity for us to be able to fuzz it
    static final long MIN_FUZZABLE_INTERVAL = 10000;
    @GuardedBy("mLock")
    final AlarmStore mAlarmStore;
    AlarmStore mAlarmStore;

    // set to non-null if in idle mode; while in this mode, any alarms we don't want
    // to run during this time are rescehduled to go off after this alarm.
@@ -781,7 +812,6 @@ public class AlarmManagerService extends SystemService {
    AlarmManagerService(Context context, Injector injector) {
        super(context);
        mInjector = injector;
        mAlarmStore = new BatchingAlarmStore(() -> mNextAlarmClockMayChange = true);
    }

    public AlarmManagerService(Context context) {
@@ -1219,6 +1249,11 @@ public class AlarmManagerService extends SystemService {
        synchronized (mLock) {
            mHandler = new AlarmHandler();
            mConstants = new Constants();

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

            mAppWakeupHistory = new AppWakeupHistory(Constants.DEFAULT_APP_STANDBY_WINDOW);

            mNextWakeup = mNextNonWakeup = 0;
@@ -3055,12 +3090,13 @@ public class AlarmManagerService extends SystemService {

    static final void dumpAlarmList(IndentingPrintWriter ipw, ArrayList<Alarm> list,
            long nowELAPSED, SimpleDateFormat sdf) {
        for (int i = list.size() - 1; i >= 0; i--) {
        final int n = list.size();
        for (int i = n - 1; i >= 0; i--) {
            final Alarm a = list.get(i);
            final String label = Alarm.typeToString(a.type);
            ipw.print(label);
            ipw.print(" #");
            ipw.print(i);
            ipw.print(n - i);
            ipw.print(": ");
            ipw.println(a);
            ipw.increaseIndent();
+14 −1
Original line number Diff line number Diff line
@@ -39,6 +39,13 @@ public interface AlarmStore {
     */
    void add(Alarm a);

    /**
     * Adds all the given alarms to this store.
     *
     * @param alarms The alarms to add.
     */
    void addAll(ArrayList<Alarm> alarms);

    /**
     * Removes alarms that pass the given predicate.
     *
@@ -47,6 +54,12 @@ public interface AlarmStore {
     */
    ArrayList<Alarm> remove(Predicate<Alarm> whichAlarms);

    /**
     * Set a listener to be invoked whenever an alarm clock is removed by a call to
     * {@link #remove(Predicate) remove} from this store.
     */
    void setAlarmClockRemovalListener(Runnable listener);

    /**
     * Gets the earliest alarm with the flag {@link android.app.AlarmManager#FLAG_WAKE_FROM_IDLE}
     * based on {@link Alarm#getWhenElapsed()}.
+23 −36
Original line number Diff line number Diff line
@@ -41,45 +41,22 @@ import java.util.function.Predicate;
 */
public class BatchingAlarmStore implements AlarmStore {

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

    interface Stats {
        int REBATCH_ALL_ALARMS = 0;
    }

    final StatLogger mStatLogger = new StatLogger("Alarm store stats", new String[]{
    final StatLogger mStatLogger = new StatLogger("BatchingAlarmStore stats", new String[]{
            "REBATCH_ALL_ALARMS",
    });

    private static final Comparator<Batch> sBatchOrder = (b1, b2) -> {
        long when1 = b1.mStart;
        long when2 = b2.mStart;
        if (when1 > when2) {
            return 1;
        }
        if (when1 < when2) {
            return -1;
        }
        return 0;
    };

    private static final Comparator<Alarm> sIncreasingTimeOrder = (a1, a2) -> {
        long when1 = a1.getWhenElapsed();
        long when2 = a2.getWhenElapsed();
        if (when1 > when2) {
            return 1;
        }
        if (when1 < when2) {
            return -1;
        }
        return 0;
    };
    private static final Comparator<Batch> sBatchOrder = Comparator.comparingLong(b -> b.mStart);

    BatchingAlarmStore(AlarmClockRemovalListener listener) {
        mAlarmClockRemovalListener = listener;
    }
    private static final Comparator<Alarm> sIncreasingTimeOrder = Comparator.comparingLong(
            Alarm::getWhenElapsed);

    @Override
    public void add(Alarm a) {
@@ -87,6 +64,16 @@ public class BatchingAlarmStore implements AlarmStore {
        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<>();
@@ -105,6 +92,11 @@ public class BatchingAlarmStore implements AlarmStore {
        return removed;
    }

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

    @Override
    public Alarm getNextWakeFromIdleAlarm() {
        for (final Batch batch : mAlarmBatches) {
@@ -317,8 +309,8 @@ public class BatchingAlarmStore implements AlarmStore {
                Alarm alarm = mAlarms.get(i);
                if (predicate.test(alarm)) {
                    removed.add(mAlarms.remove(i));
                    if (alarm.alarmClock != null && mAlarmClockRemovalListener != null) {
                        mAlarmClockRemovalListener.onRemoved();
                    if (alarm.alarmClock != null && mOnAlarmClockRemoved != null) {
                        mOnAlarmClockRemoved.run();
                    }
                    if (isTimeTickAlarm(alarm)) {
                        // This code path is not invoked when delivering alarms, only when removing
@@ -388,9 +380,4 @@ public class BatchingAlarmStore implements AlarmStore {
            proto.end(token);
        }
    }

    @FunctionalInterface
    interface AlarmClockRemovalListener {
        void onRemoved();
    }
}
+217 −0
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.TAG;
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.util.StatLogger;

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

/**
 * Lazy implementation of an alarm store.
 * This keeps the alarms in a sorted list, and only batches them at the time of delivery.
 */
public class LazyAlarmStore implements AlarmStore {

    private final ArrayList<Alarm> mAlarms = new ArrayList<>();
    private Runnable mOnAlarmClockRemoved;

    interface Stats {
        int GET_NEXT_DELIVERY_TIME = 0;
        int GET_NEXT_WAKEUP_DELIVERY_TIME = 1;
    }

    final StatLogger mStatLogger = new StatLogger("LazyAlarmStore stats", new String[]{
            "GET_NEXT_DELIVERY_TIME",
            "GET_NEXT_WAKEUP_DELIVERY_TIME",
    });

    // Decreasing time order because it is more efficient to remove from the tail of an array list.
    private static final Comparator<Alarm> sDecreasingTimeOrder = Comparator.comparingLong(
            Alarm::getWhenElapsed).reversed();

    @Override
    public void add(Alarm a) {
        int index = Collections.binarySearch(mAlarms, a, sDecreasingTimeOrder);
        if (index < 0) {
            index = 0 - index - 1;
        }
        mAlarms.add(index, a);
    }

    @Override
    public void addAll(ArrayList<Alarm> alarms) {
        if (alarms == null) {
            return;
        }
        mAlarms.addAll(alarms);
        Collections.sort(alarms, sDecreasingTimeOrder);
    }

    @Override
    public ArrayList<Alarm> remove(Predicate<Alarm> whichAlarms) {
        final ArrayList<Alarm> removedAlarms = new ArrayList<>();
        for (int i = mAlarms.size() - 1; i >= 0; i--) {
            if (whichAlarms.test(mAlarms.get(i))) {
                final Alarm removed = mAlarms.remove(i);
                if (removed.alarmClock != null && mOnAlarmClockRemoved != null) {
                    mOnAlarmClockRemoved.run();
                }
                if (isTimeTickAlarm(removed)) {
                    // 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");
                }
                removedAlarms.add(removed);
            }
        }
        return removedAlarms;
    }

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

    @Override
    public Alarm getNextWakeFromIdleAlarm() {
        for (int i = mAlarms.size() - 1; i >= 0; i--) {
            final Alarm alarm = mAlarms.get(i);
            if ((alarm.flags & AlarmManager.FLAG_WAKE_FROM_IDLE) != 0) {
                return alarm;
            }
        }
        return null;
    }

    @Override
    public int size() {
        return mAlarms.size();
    }

    @Override
    public long getNextWakeupDeliveryTime() {
        final long start = mStatLogger.getTime();
        long nextWakeup = 0;
        for (int i = mAlarms.size() - 1; i >= 0; i--) {
            final Alarm a = mAlarms.get(i);
            if (!a.wakeup) {
                continue;
            }
            if (nextWakeup == 0) {
                nextWakeup = a.getMaxWhenElapsed();
            } else {
                if (a.getWhenElapsed() > nextWakeup) {
                    break;
                }
                nextWakeup = Math.min(nextWakeup, a.getMaxWhenElapsed());
            }
        }
        mStatLogger.logDurationStat(Stats.GET_NEXT_WAKEUP_DELIVERY_TIME, start);
        return nextWakeup;
    }

    @Override
    public long getNextDeliveryTime() {
        final long start = mStatLogger.getTime();
        final int n = mAlarms.size();
        if (n == 0) {
            return 0;
        }
        long nextDelivery = mAlarms.get(n - 1).getMaxWhenElapsed();
        for (int i = n - 2; i >= 0; i--) {
            final Alarm a = mAlarms.get(i);
            if (a.getWhenElapsed() > nextDelivery) {
                break;
            }
            nextDelivery = Math.min(nextDelivery, a.getMaxWhenElapsed());
        }
        mStatLogger.logDurationStat(Stats.GET_NEXT_DELIVERY_TIME, start);
        return nextDelivery;
    }

    @Override
    public ArrayList<Alarm> removePendingAlarms(long nowElapsed) {
        final ArrayList<Alarm> pending = new ArrayList<>();
        final ArrayList<Alarm> standAlones = new ArrayList<>();

        for (int i = mAlarms.size() - 1; i >= 0; i--) {
            final Alarm alarm = mAlarms.get(i);
            if (alarm.getWhenElapsed() > nowElapsed) {
                break;
            }
            pending.add(alarm);
            if ((alarm.flags & AlarmManager.FLAG_STANDALONE) != 0) {
                standAlones.add(alarm);
            }
        }
        if (!standAlones.isEmpty()) {
            // If there are deliverable standalone alarms, others must not go out yet.
            mAlarms.removeAll(standAlones);
            return standAlones;
        }
        mAlarms.removeAll(pending);
        return pending;
    }

    @Override
    public boolean updateAlarmDeliveries(AlarmDeliveryCalculator deliveryCalculator) {
        boolean changed = false;
        for (final Alarm alarm : mAlarms) {
            changed |= deliveryCalculator.updateAlarmDelivery(alarm);
        }
        if (changed) {
            Collections.sort(mAlarms, sDecreasingTimeOrder);
        }
        return changed;
    }

    @Override
    public ArrayList<Alarm> asList() {
        final ArrayList<Alarm> copy = new ArrayList<>(mAlarms);
        Collections.reverse(copy);
        return copy;
    }

    @Override
    public void dump(IndentingPrintWriter ipw, long nowElapsed, SimpleDateFormat sdf) {
        ipw.println(mAlarms.size() + " pending alarms: ");
        ipw.increaseIndent();
        dumpAlarmList(ipw, mAlarms, nowElapsed, sdf);
        ipw.decreaseIndent();
        mStatLogger.dump(ipw);
    }

    @Override
    public void dumpProto(ProtoOutputStream pos, long nowElapsed) {
        for (final Alarm a : mAlarms) {
            a.dumpDebug(pos, AlarmManagerServiceDumpProto.PENDING_ALARMS, nowElapsed);
        }
    }
}
+2 −0
Original line number Diff line number Diff line
@@ -144,6 +144,8 @@ message AlarmManagerServiceDumpProto {

    repeated IdleDispatchEntryProto allow_while_idle_dispatches = 40;
    repeated WakeupEventProto recent_wakeup_history = 41;

    repeated AlarmProto pending_alarms = 42;
}

// This is a soft wrapper for alarm clock information. It is not representative
Loading