1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.location.eventlog;
18 
19 import static java.lang.Integer.bitCount;
20 
21 import android.annotation.Nullable;
22 
23 import com.android.internal.annotations.GuardedBy;
24 import com.android.internal.annotations.VisibleForTesting;
25 import com.android.internal.util.Preconditions;
26 
27 import java.lang.reflect.Array;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.ConcurrentModificationException;
31 import java.util.NoSuchElementException;
32 import java.util.Objects;
33 
34 /**
35  * An in-memory event log to support historical event information. The log is of a constant size,
36  * and new events will overwrite old events as the log fills up.
37  */
38 public class LocalEventLog<T> {
39 
40     /** Consumer of log events for iterating over the log. */
41     public interface LogConsumer<T> {
42         /** Invoked with a time and a logEvent. */
acceptLog(long time, T logEvent)43         void acceptLog(long time, T logEvent);
44     }
45 
46     // masks for the entries field. 1 bit is used to indicate whether this is a filler event or not,
47     // and 31 bits to store the time delta.
48     private static final int IS_FILLER_MASK  = 0b10000000000000000000000000000000;
49     private static final int TIME_DELTA_MASK = 0b01111111111111111111111111111111;
50 
51     private static final int IS_FILLER_OFFSET = countTrailingZeros(IS_FILLER_MASK);
52     private static final int TIME_DELTA_OFFSET = countTrailingZeros(TIME_DELTA_MASK);
53 
54     @VisibleForTesting
55     static final int MAX_TIME_DELTA = (1 << bitCount(TIME_DELTA_MASK)) - 1;
56 
countTrailingZeros(int i)57     private static int countTrailingZeros(int i) {
58         int c = 0;
59         while (i != 0 && (i & 1) == 0) {
60             c++;
61             i = i >>> 1;
62         }
63         return c;
64     }
65 
createEntry(boolean isFiller, int timeDelta)66     private static int createEntry(boolean isFiller, int timeDelta) {
67         Preconditions.checkArgument(timeDelta >= 0 && timeDelta <= MAX_TIME_DELTA);
68         return (((isFiller ? 1 : 0) << IS_FILLER_OFFSET) & IS_FILLER_MASK)
69                 | ((timeDelta << TIME_DELTA_OFFSET) & TIME_DELTA_MASK);
70     }
71 
getTimeDelta(int entry)72     static int getTimeDelta(int entry) {
73         return (entry & TIME_DELTA_MASK) >>> TIME_DELTA_OFFSET;
74     }
75 
isFiller(int entry)76     static boolean isFiller(int entry) {
77         return (entry & IS_FILLER_MASK) != 0;
78     }
79 
80     // circular buffer of log entries and events. each entry corresponds to the log event at the
81     // same index. the log entry holds the filler status and time delta according to the bit masks
82     // above, and the log event is the log event.
83 
84     @GuardedBy("this")
85     final int[] mEntries;
86 
87     @GuardedBy("this")
88     final @Nullable T[] mLogEvents;
89 
90     @GuardedBy("this")
91     int mLogSize;
92 
93     @GuardedBy("this")
94     int mLogEndIndex;
95 
96     // invalid if log is empty
97 
98     @GuardedBy("this")
99     long mStartTime;
100 
101     @GuardedBy("this")
102     long mLastLogTime;
103 
104     @GuardedBy("this")
105     long mModificationCount;
106 
107     @SuppressWarnings("unchecked")
LocalEventLog(int size, Class<T> clazz)108     public LocalEventLog(int size, Class<T> clazz) {
109         Preconditions.checkArgument(size > 0);
110 
111         mEntries = new int[size];
112         mLogEvents = (T[]) Array.newInstance(clazz, size);
113         mLogSize = 0;
114         mLogEndIndex = 0;
115 
116         mStartTime = -1;
117         mLastLogTime = -1;
118     }
119 
120     /** Call to add a new log event at the given time. */
addLog(long time, T logEvent)121     protected synchronized void addLog(long time, T logEvent) {
122         Preconditions.checkArgument(logEvent != null);
123 
124         // calculate delta
125         long delta = 0;
126         if (!isEmpty()) {
127             delta = time - mLastLogTime;
128 
129             // if the delta is negative, or if the delta is great enough using filler elements would
130             // result in an empty log anyways, just clear the log and continue, otherwise insert
131             // filler elements until we have a reasonable delta
132             if (delta < 0 || (delta / MAX_TIME_DELTA) >= mEntries.length - 1) {
133                 clear();
134                 delta = 0;
135             } else {
136                 while (delta >= MAX_TIME_DELTA) {
137                     addLogEventInternal(true, MAX_TIME_DELTA, null);
138                     delta -= MAX_TIME_DELTA;
139                 }
140             }
141         }
142 
143         // for first log entry, set initial times
144         if (isEmpty()) {
145             mStartTime = time;
146             mLastLogTime = mStartTime;
147             mModificationCount++;
148         }
149 
150         addLogEventInternal(false, (int) delta, logEvent);
151     }
152 
153     @GuardedBy("this")
addLogEventInternal(boolean isFiller, int timeDelta, @Nullable T logEvent)154     private void addLogEventInternal(boolean isFiller, int timeDelta, @Nullable T logEvent) {
155         Preconditions.checkArgument(isFiller || logEvent != null);
156         Preconditions.checkState(mStartTime != -1 && mLastLogTime != -1);
157 
158         if (mLogSize == mEntries.length) {
159             // if log is full, size will remain the same, but update the start time
160             mStartTime += getTimeDelta(mEntries[startIndex()]);
161             mModificationCount++;
162         } else {
163             // otherwise add an item
164             mLogSize++;
165         }
166 
167         // set log and increment end index
168         mEntries[mLogEndIndex] = createEntry(isFiller, timeDelta);
169         mLogEvents[mLogEndIndex] = logEvent;
170         mLogEndIndex = incrementIndex(mLogEndIndex);
171         mLastLogTime = mLastLogTime + timeDelta;
172     }
173 
174     /** Clears the log of all entries. */
clear()175     public synchronized void clear() {
176         // clear entries to aid gc
177         Arrays.fill(mLogEvents, null);
178 
179         mLogEndIndex = 0;
180         mLogSize = 0;
181         mModificationCount++;
182 
183         mStartTime = -1;
184         mLastLogTime = -1;
185     }
186 
187     // checks if the log is empty (if empty, times are invalid)
188     @GuardedBy("this")
isEmpty()189     private boolean isEmpty() {
190         return mLogSize == 0;
191     }
192 
193     /**
194      * Iterates over the event log, passing each log event to the given consumer. Locks the log
195      * while executing so that {@link ConcurrentModificationException}s cannot occur.
196      */
iterate(LogConsumer<? super T> consumer)197     public synchronized void iterate(LogConsumer<? super T> consumer) {
198         LogIterator it = new LogIterator();
199         while (it.hasNext()) {
200             it.next();
201             consumer.acceptLog(it.getTime(), it.getLog());
202         }
203     }
204 
205     /**
206      * Iterates over all the given event logs in time order, passing each log event to the given
207      * consumer. It is the caller's responsibility to ensure that {@link
208      * ConcurrentModificationException}s cannot occur, whether through locking or other means.
209      */
210     @SafeVarargs
iterate(LogConsumer<? super T> consumer, LocalEventLog<T>... logs)211     public static <T> void iterate(LogConsumer<? super T> consumer, LocalEventLog<T>... logs) {
212         ArrayList<LocalEventLog<T>.LogIterator> its = new ArrayList<>(logs.length);
213         for (LocalEventLog<T> log : logs) {
214             LocalEventLog<T>.LogIterator it = log.new LogIterator();
215             if (it.hasNext()) {
216                 its.add(it);
217                 it.next();
218             }
219         }
220 
221         while (true) {
222             LocalEventLog<T>.LogIterator next = null;
223             for (LocalEventLog<T>.LogIterator it : its) {
224                 if (it != null && (next == null || it.getTime() < next.getTime())) {
225                     next = it;
226                 }
227             }
228 
229             if (next == null) {
230                 return;
231             }
232 
233             consumer.acceptLog(next.getTime(), next.getLog());
234 
235             if (next.hasNext()) {
236                 next.next();
237             } else {
238                 its.remove(next);
239             }
240         }
241     }
242 
243     // returns the index of the first element
244     @GuardedBy("this")
startIndex()245     int startIndex() {
246         return wrapIndex(mLogEndIndex - mLogSize);
247     }
248 
249     // returns the index after this one
250     @GuardedBy("this")
incrementIndex(int index)251     int incrementIndex(int index) {
252         if (index == -1) {
253             return startIndex();
254         } else if (index >= 0) {
255             return wrapIndex(index + 1);
256         } else {
257             throw new IllegalArgumentException();
258         }
259     }
260 
261     // rolls over the given index if necessary
262     @GuardedBy("this")
wrapIndex(int index)263     int wrapIndex(int index) {
264         // java modulo will keep negative sign, we need to rollover
265         return (index % mEntries.length + mEntries.length) % mEntries.length;
266     }
267 
268     /** Iterator over log times and events. */
269     protected final class LogIterator {
270 
271         private final long mModificationCount;
272 
273         private long mLogTime;
274         private int mIndex;
275         private int mCount;
276 
277         private long mCurrentTime;
278         private T mCurrentLogEvent;
279 
LogIterator()280         public LogIterator() {
281             synchronized (LocalEventLog.this) {
282                 mModificationCount = LocalEventLog.this.mModificationCount;
283 
284                 mLogTime = mStartTime;
285                 mIndex = -1;
286                 mCount = -1;
287 
288                 increment();
289             }
290         }
291 
hasNext()292         public boolean hasNext() {
293             synchronized (LocalEventLog.this) {
294                 checkModifications();
295                 return mCount < mLogSize;
296             }
297         }
298 
next()299         public void next() {
300             synchronized (LocalEventLog.this) {
301                 if (!hasNext()) {
302                     throw new NoSuchElementException();
303                 }
304 
305                 mCurrentTime = mLogTime + getTimeDelta(mEntries[mIndex]);
306                 mCurrentLogEvent = Objects.requireNonNull(mLogEvents[mIndex]);
307 
308                 increment();
309             }
310         }
311 
getTime()312         public long getTime() {
313             return mCurrentTime;
314         }
315 
getLog()316         public T getLog() {
317             return mCurrentLogEvent;
318         }
319 
320         @GuardedBy("LocalEventLog.this")
increment(LogIterator this)321         private void increment(LogIterator this) {
322             long nextDeltaMs = mIndex == -1 ? 0 : getTimeDelta(mEntries[mIndex]);
323             do {
324                 mLogTime += nextDeltaMs;
325                 mIndex = incrementIndex(mIndex);
326                 if (++mCount < mLogSize) {
327                     nextDeltaMs = getTimeDelta(mEntries[mIndex]);
328                 }
329             } while (mCount < mLogSize && isFiller(mEntries[mIndex]));
330         }
331 
332         @GuardedBy("LocalEventLog.this")
checkModifications()333         private void checkModifications() {
334             if (mModificationCount != LocalEventLog.this.mModificationCount) {
335                 throw new ConcurrentModificationException();
336             }
337         }
338     }
339 }
340