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 Original line Diff line number Diff line
@@ -1385,7 +1385,7 @@ public class LocationManagerService extends ILocationManager.Stub implements


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


package com.android.server.location.eventlog;
package com.android.server.location.eventlog;


import static java.lang.Integer.bitCount;

import android.annotation.Nullable;
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 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.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
     * Consumer of log events for iterating over the log.
        boolean isFiller();
     *
        long getTimeDeltaMs();
     * @param <T> log event type
        String getLogString();
     */
        boolean filter(@Nullable String filter);
    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) {
    private static int countTrailingZeros(int i) {
            Preconditions.checkArgument(timeDelta >= 0);
        int c = 0;
            mTimeDelta = (int) timeDelta;
        while (i != 0 && (i & 1) == 0) {
            c++;
            i = i >>> 1;
        }
        }

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


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


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


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


    /**
    // circular buffer of log entries and events. each entry corrosponds to the log event at the
     * An abstraction of a log event to be implemented by subclasses.
    // 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.
    public abstract static class LogEvent implements Log {


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


        private final int mTimeDelta;
    @GuardedBy("this")

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


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


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


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


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


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


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

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


        mStartRealtimeMs = -1;
        mStartTime = -1;
        mLastLogRealtimeMs = -1;
        mLastLogTime = -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) + ": ";
    }
    }


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


        // calculate delta
        // calculate delta
        long delta = 0;
        long delta = 0;
        if (!isEmpty()) {
        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
            // result in an empty log anyways, just clear the log and continue, otherwise insert
            // filler elements until we have a reasonable delta
            // 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();
                clear();
                delta = 0;
                delta = 0;
            } else {
            } else {
                while (delta >= LogEvent.MAX_TIME_DELTA) {
                while (delta >= MAX_TIME_DELTA) {
                    long timeDelta = Math.min(FillerEvent.MAX_TIME_DELTA, delta);
                    addLogEventInternal(true, MAX_TIME_DELTA, null);
                    addLogEventInternal(new FillerEvent(timeDelta));
                    delta -= MAX_TIME_DELTA;
                    delta -= timeDelta;
                }
                }
            }
            }
        }
        }


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


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


    private void addLogEventInternal(Log event) {
    @GuardedBy("this")
        Preconditions.checkState(mStartRealtimeMs != -1 && mLastLogRealtimeMs != -1);
    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
            // if log is full, size will remain the same, but update the start time
            mStartRealtimeMs += mLog[startIndex()].getTimeDeltaMs();
            mStartTime += getTimeDelta(mEntries[startIndex()]);
        } else {
        } else {
            // otherwise add an item
            // otherwise add an item
            mLogSize++;
            mLogSize++;
        }
        }


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


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

        mLogEndIndex = 0;
        mLogEndIndex = 0;
        mLogSize = 0;
        mLogSize = 0;


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


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


    /** Iterates over the event log, passing each log string to the given consumer. */
    /** 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();
        LogIterator it = new LogIterator();
        while (it.hasNext()) {
        while (it.hasNext()) {
            consumer.accept(it.next());
            it.next();
        }
            consumer.acceptLog(it.getTime(), it.getLog());
    }

    /**
     * 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());
        }
        }
    }
    }


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


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


    // rolls over the given index if necessary
    // rolls over the given index if necessary
    @GuardedBy("this")
    private int wrapIndex(int index) {
    private int wrapIndex(int index) {
        // java modulo will keep negative sign, we need to rollover
        // 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 class LogIterator {

        private final @Nullable String mFilter;

        private final long mSystemTimeDeltaMs;


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


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


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


                increment();
                increment();
            }
            }
        }


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


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


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


                increment();
                increment();
            }
        }


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


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


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