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

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

Merge "Simplify EventLog"

parents 691a5d9f 7ecb82e2
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.