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

Commit 7ecb82e2 authored by Soonil Nagarkar's avatar Soonil Nagarkar
Browse files

Simplify EventLog

Prevent mismatches when adding/removing events, simplify API.

Test: manual
Change-Id: I92c6b1c6ebf00c889a39ccf15001dfd4f19e6d75
parent 60a7931d
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -1385,7 +1385,7 @@ public class LocationManagerService extends ILocationManager.Stub implements

                ipw.println("Event Log:");
                ipw.increaseIndent();
                EVENT_LOG.iterate(manager.getName(), ipw::println);
                EVENT_LOG.iterate(ipw::println, manager.getName());
                ipw.decreaseIndent();
                return;
            }
+136 −161
Original line number Diff line number Diff line
@@ -16,217 +16,193 @@

package com.android.server.location.eventlog;

import static java.lang.Integer.bitCount;

import android.annotation.Nullable;
import android.os.SystemClock;
import android.util.TimeUtils;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;

import java.util.Iterator;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.NoSuchElementException;
import java.util.function.Consumer;
import java.util.Objects;

/**
 * An in-memory event log to support historical event information.
 * An in-memory event log to support historical event information. The log is of a constant size,
 * and new events will overwrite old events as the log fills up.
 *
 * @param <T> log event type
 */
public abstract class LocalEventLog {
public class LocalEventLog<T> {

    private interface Log {
        // true if this is a filler element that should not be queried
        boolean isFiller();
        long getTimeDeltaMs();
        String getLogString();
        boolean filter(@Nullable String filter);
    /**
     * Consumer of log events for iterating over the log.
     *
     * @param <T> log event type
     */
    public interface LogConsumer<T> {
        /** Invoked with a time and a logEvent. */
        void acceptLog(long time, T logEvent);
    }

    private static final class FillerEvent implements Log {
    // masks for the entries field. 1 bit is used to indicate whether this is a filler event or not,
    // and 31 bits to store the time delta.
    private static final int IS_FILLER_MASK = 0b10000000000000000000000000000000;
    private static final int TIME_DELTA_MASK = 0b01111111111111111111111111111111;

        static final long MAX_TIME_DELTA = (1L << 32) - 1;
    private static final int IS_FILLER_OFFSET = countTrailingZeros(IS_FILLER_MASK);
    private static final int TIME_DELTA_OFFSET = countTrailingZeros(TIME_DELTA_MASK);

        private final int mTimeDelta;
    static final int MAX_TIME_DELTA = (1 << bitCount(TIME_DELTA_MASK)) - 1;

        FillerEvent(long timeDelta) {
            Preconditions.checkArgument(timeDelta >= 0);
            mTimeDelta = (int) timeDelta;
    private static int countTrailingZeros(int i) {
        int c = 0;
        while (i != 0 && (i & 1) == 0) {
            c++;
            i = i >>> 1;
        }

        @Override
        public boolean isFiller() {
            return true;
        return c;
    }

        @Override
        public long getTimeDeltaMs() {
            return Integer.toUnsignedLong(mTimeDelta);
    private static int createEntry(boolean isFiller, int timeDelta) {
        Preconditions.checkArgument(timeDelta >= 0 && timeDelta <= MAX_TIME_DELTA);
        return (((isFiller ? 1 : 0) << IS_FILLER_OFFSET) & IS_FILLER_MASK)
                | ((timeDelta << TIME_DELTA_OFFSET) & TIME_DELTA_MASK);
    }

        @Override
        public String getLogString() {
            throw new AssertionError();
    static int getTimeDelta(int entry) {
        return (entry & TIME_DELTA_MASK) >>> TIME_DELTA_OFFSET;
    }

        @Override
        public boolean filter(String filter) {
            return false;
        }
    static boolean isFiller(int entry) {
        return (entry & IS_FILLER_MASK) != 0;
    }

    /**
     * An abstraction of a log event to be implemented by subclasses.
     */
    public abstract static class LogEvent implements Log {
    // circular buffer of log entries and events. each entry corrosponds to the log event at the
    // same index. the log entry holds the filler status and time delta according to the bit masks
    // above, and the log event is the log event.

        static final long MAX_TIME_DELTA = (1L << 32) - 1;
    @GuardedBy("this")
    final int[] mEntries;

        private final int mTimeDelta;

        protected LogEvent(long timeDelta) {
            Preconditions.checkArgument(timeDelta >= 0);
            mTimeDelta = (int) timeDelta;
        }
    @GuardedBy("this")
    final @Nullable T[] mLogEvents;

        @Override
        public final boolean isFiller() {
            return false;
        }
    @GuardedBy("this")
    int mLogSize;

        @Override
        public final long getTimeDeltaMs() {
            return Integer.toUnsignedLong(mTimeDelta);
        }
    @GuardedBy("this")
    int mLogEndIndex;

        @Override
        public boolean filter(String filter) {
            return false;
        }
    }
    // invalid if log is empty

    // circular buffer of log entries
    private final Log[] mLog;
    private int mLogSize;
    private int mLogEndIndex;
    @GuardedBy("this")
    long mStartTime;

    // invalid if log is empty
    private long mStartRealtimeMs;
    private long mLastLogRealtimeMs;
    @GuardedBy("this")
    long mLastLogTime;

    public LocalEventLog(int size) {
    @SuppressWarnings("unchecked")
    public LocalEventLog(int size, Class<T> clazz) {
        Preconditions.checkArgument(size > 0);
        mLog = new Log[size];

        mEntries = new int[size];
        mLogEvents = (T[]) Array.newInstance(clazz, size);
        mLogSize = 0;
        mLogEndIndex = 0;

        mStartRealtimeMs = -1;
        mLastLogRealtimeMs = -1;
    }

    /**
     * Should be overridden by subclasses to return a new immutable log event for the given
     * arguments (as passed into {@link #addLogEvent(int, Object...)}.
     */
    protected abstract LogEvent createLogEvent(long timeDelta, int event, Object... args);

    /**
     * May be optionally overridden by subclasses if they wish to change how log event time is
     * formatted.
     */
    protected String getTimePrefix(long timeMs) {
        return TimeUtils.logTimeOfDay(timeMs) + ": ";
        mStartTime = -1;
        mLastLogTime = -1;
    }

    /**
     * Call to add a new log event at the current time. The arguments provided here will be passed
     * into {@link #createLogEvent(long, int, Object...)} in addition to a time delta, and should be
     * used to construct an appropriate {@link LogEvent} object.
     */
    public synchronized void addLogEvent(int event, Object... args) {
        long timeMs = SystemClock.elapsedRealtime();
    /** Call to add a new log event at the given time. */
    protected synchronized void addLog(long time, T logEvent) {
        Preconditions.checkArgument(logEvent != null);

        // calculate delta
        long delta = 0;
        if (!isEmpty()) {
            delta = timeMs - mLastLogRealtimeMs;
            delta = time - mLastLogTime;

            // if the delta is invalid, or if the delta is great enough using filler elements would
            // if the delta is negative, or if the delta is great enough using filler elements would
            // result in an empty log anyways, just clear the log and continue, otherwise insert
            // filler elements until we have a reasonable delta
            if (delta < 0 || (delta / FillerEvent.MAX_TIME_DELTA) >= mLog.length - 1) {
            if (delta < 0 || (delta / MAX_TIME_DELTA) >= mEntries.length - 1) {
                clear();
                delta = 0;
            } else {
                while (delta >= LogEvent.MAX_TIME_DELTA) {
                    long timeDelta = Math.min(FillerEvent.MAX_TIME_DELTA, delta);
                    addLogEventInternal(new FillerEvent(timeDelta));
                    delta -= timeDelta;
                while (delta >= MAX_TIME_DELTA) {
                    addLogEventInternal(true, MAX_TIME_DELTA, null);
                    delta -= MAX_TIME_DELTA;
                }
            }
        }

        // for first log entry, set initial times
        if (isEmpty()) {
            mStartRealtimeMs = timeMs;
            mLastLogRealtimeMs = mStartRealtimeMs;
            mStartTime = time;
            mLastLogTime = mStartTime;
        }

        addLogEventInternal(createLogEvent(delta, event, args));
        addLogEventInternal(false, (int) delta, logEvent);
    }

    private void addLogEventInternal(Log event) {
        Preconditions.checkState(mStartRealtimeMs != -1 && mLastLogRealtimeMs != -1);
    @GuardedBy("this")
    private void addLogEventInternal(boolean isFiller, int timeDelta, @Nullable T logEvent) {
        Preconditions.checkArgument(isFiller || logEvent != null);
        Preconditions.checkState(mStartTime != -1 && mLastLogTime != -1);

        if (mLogSize == mLog.length) {
        if (mLogSize == mEntries.length) {
            // if log is full, size will remain the same, but update the start time
            mStartRealtimeMs += mLog[startIndex()].getTimeDeltaMs();
            mStartTime += getTimeDelta(mEntries[startIndex()]);
        } else {
            // otherwise add an item
            mLogSize++;
        }

        // set log and increment end index
        mLog[mLogEndIndex] = event;
        mEntries[mLogEndIndex] = createEntry(isFiller, timeDelta);
        mLogEvents[mLogEndIndex] = logEvent;
        mLogEndIndex = incrementIndex(mLogEndIndex);
        mLastLogRealtimeMs = mLastLogRealtimeMs + event.getTimeDeltaMs();
        mLastLogTime = mLastLogTime + timeDelta;
    }

    /** Clears the log of all entries. */
    public synchronized void clear() {
        // clear entries to allow gc
        Arrays.fill(mLogEvents, null);

        mLogEndIndex = 0;
        mLogSize = 0;

        mStartRealtimeMs = -1;
        mLastLogRealtimeMs = -1;
        mStartTime = -1;
        mLastLogTime = -1;
    }

    // checks if the log is empty (if empty, times are invalid)
    private synchronized boolean isEmpty() {
    @GuardedBy("this")
    private boolean isEmpty() {
        return mLogSize == 0;
    }

    /** Iterates over the event log, passing each log string to the given consumer. */
    public synchronized void iterate(Consumer<String> consumer) {
    public synchronized void iterate(LogConsumer<? super T> consumer) {
        LogIterator it = new LogIterator();
        while (it.hasNext()) {
            consumer.accept(it.next());
        }
    }

    /**
     * Iterates over the event log, passing each filter-matching log string to the given
     * consumer.
     */
    public synchronized void iterate(String filter, Consumer<String> consumer) {
        LogIterator it = new LogIterator(filter);
        while (it.hasNext()) {
            consumer.accept(it.next());
            it.next();
            consumer.acceptLog(it.getTime(), it.getLog());
        }
    }

    // returns the index of the first element
    @GuardedBy("this")
    private int startIndex() {
        return wrapIndex(mLogEndIndex - mLogSize);
    }

    // returns the index after this one
    @GuardedBy("this")
    private int incrementIndex(int index) {
        if (index == -1) {
            return startIndex();
@@ -238,69 +214,68 @@ public abstract class LocalEventLog {
    }

    // rolls over the given index if necessary
    @GuardedBy("this")
    private int wrapIndex(int index) {
        // java modulo will keep negative sign, we need to rollover
        return (index % mLog.length + mLog.length) % mLog.length;
        return (index % mEntries.length + mEntries.length) % mEntries.length;
    }

    private class LogIterator implements Iterator<String> {

        private final @Nullable String mFilter;

        private final long mSystemTimeDeltaMs;
    private class LogIterator {

        private long mCurrentRealtimeMs;
        private long mLogTime;
        private int mIndex;
        private int mCount;

        LogIterator() {
            this(null);
        }
        private long mCurrentTime;
        private T mCurrentLogEvent;

        LogIterator(@Nullable String filter) {
            mFilter = filter;
            mSystemTimeDeltaMs = System.currentTimeMillis() - SystemClock.elapsedRealtime();
            mCurrentRealtimeMs = mStartRealtimeMs;
        LogIterator() {
            synchronized (LocalEventLog.this) {
                mLogTime = mStartTime;
                mIndex = -1;
                mCount = -1;

                increment();
            }
        }

        @Override
        public boolean hasNext() {
            synchronized (LocalEventLog.this) {
                return mCount < mLogSize;
            }
        }

        public String next() {
        public void next() {
            synchronized (LocalEventLog.this) {
                if (!hasNext()) {
                    throw new NoSuchElementException();
                }

            Log log = mLog[mIndex];
            long timeMs = mCurrentRealtimeMs + log.getTimeDeltaMs() + mSystemTimeDeltaMs;
                mCurrentTime = mLogTime + getTimeDelta(mEntries[mIndex]);
                mCurrentLogEvent = Objects.requireNonNull(mLogEvents[mIndex]);

                increment();
            }
        }

            return getTimePrefix(timeMs) + log.getLogString();
        public long getTime() {
            return mCurrentTime;
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException();
        public T getLog() {
            return mCurrentLogEvent;
        }

        private void increment() {
            long nextDeltaMs = mIndex == -1 ? 0 : mLog[mIndex].getTimeDeltaMs();
        @GuardedBy("LocalEventLog.this")
        private void increment(LogIterator this) {
            long nextDeltaMs = mIndex == -1 ? 0 : getTimeDelta(mEntries[mIndex]);
            do {
                mCurrentRealtimeMs += nextDeltaMs;
                mLogTime += nextDeltaMs;
                mIndex = incrementIndex(mIndex);
                if (++mCount < mLogSize) {
                    nextDeltaMs = mLog[mIndex].getTimeDeltaMs();
                    nextDeltaMs = getTimeDelta(mEntries[mIndex]);
                }
            } while (mCount < mLogSize && (mLog[mIndex].isFiller() || (mFilter != null
                    && !mLog[mIndex].filter(mFilter))));
            } while (mCount < mLogSize && isFiller(mEntries[mIndex]));
        }
    }
}
+90 −143

File changed.

Preview size limit exceeded, changes collapsed.