• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.annotation.Nullable;
20 import android.os.SystemClock;
21 import android.util.TimeUtils;
22 
23 import com.android.internal.util.Preconditions;
24 
25 import java.util.Iterator;
26 import java.util.NoSuchElementException;
27 import java.util.function.Consumer;
28 
29 /**
30  * An in-memory event log to support historical event information.
31  */
32 public abstract class LocalEventLog {
33 
34     private interface Log {
35         // true if this is a filler element that should not be queried
isFiller()36         boolean isFiller();
getTimeDeltaMs()37         long getTimeDeltaMs();
getLogString()38         String getLogString();
filter(@ullable String filter)39         boolean filter(@Nullable String filter);
40     }
41 
42     private static final class FillerEvent implements Log {
43 
44         static final long MAX_TIME_DELTA = (1L << 32) - 1;
45 
46         private final int mTimeDelta;
47 
FillerEvent(long timeDelta)48         FillerEvent(long timeDelta) {
49             Preconditions.checkArgument(timeDelta >= 0);
50             mTimeDelta = (int) timeDelta;
51         }
52 
53         @Override
isFiller()54         public boolean isFiller() {
55             return true;
56         }
57 
58         @Override
getTimeDeltaMs()59         public long getTimeDeltaMs() {
60             return Integer.toUnsignedLong(mTimeDelta);
61         }
62 
63         @Override
getLogString()64         public String getLogString() {
65             throw new AssertionError();
66         }
67 
68         @Override
filter(String filter)69         public boolean filter(String filter) {
70             return false;
71         }
72     }
73 
74     /**
75      * An abstraction of a log event to be implemented by subclasses.
76      */
77     public abstract static class LogEvent implements Log {
78 
79         static final long MAX_TIME_DELTA = (1L << 32) - 1;
80 
81         private final int mTimeDelta;
82 
LogEvent(long timeDelta)83         protected LogEvent(long timeDelta) {
84             Preconditions.checkArgument(timeDelta >= 0);
85             mTimeDelta = (int) timeDelta;
86         }
87 
88         @Override
isFiller()89         public final boolean isFiller() {
90             return false;
91         }
92 
93         @Override
getTimeDeltaMs()94         public final long getTimeDeltaMs() {
95             return Integer.toUnsignedLong(mTimeDelta);
96         }
97 
98         @Override
filter(String filter)99         public boolean filter(String filter) {
100             return false;
101         }
102     }
103 
104     // circular buffer of log entries
105     private final Log[] mLog;
106     private int mLogSize;
107     private int mLogEndIndex;
108 
109     // invalid if log is empty
110     private long mStartRealtimeMs;
111     private long mLastLogRealtimeMs;
112 
LocalEventLog(int size)113     public LocalEventLog(int size) {
114         Preconditions.checkArgument(size > 0);
115         mLog = new Log[size];
116         mLogSize = 0;
117         mLogEndIndex = 0;
118 
119         mStartRealtimeMs = -1;
120         mLastLogRealtimeMs = -1;
121     }
122 
123     /**
124      * Should be overridden by subclasses to return a new immutable log event for the given
125      * arguments (as passed into {@link #addLogEvent(int, Object...)}.
126      */
createLogEvent(long timeDelta, int event, Object... args)127     protected abstract LogEvent createLogEvent(long timeDelta, int event, Object... args);
128 
129     /**
130      * May be optionally overridden by subclasses if they wish to change how log event time is
131      * formatted.
132      */
getTimePrefix(long timeMs)133     protected String getTimePrefix(long timeMs) {
134         return TimeUtils.logTimeOfDay(timeMs) + ": ";
135     }
136 
137     /**
138      * Call to add a new log event at the current time. The arguments provided here will be passed
139      * into {@link #createLogEvent(long, int, Object...)} in addition to a time delta, and should be
140      * used to construct an appropriate {@link LogEvent} object.
141      */
addLogEvent(int event, Object... args)142     public synchronized void addLogEvent(int event, Object... args) {
143         long timeMs = SystemClock.elapsedRealtime();
144 
145         // calculate delta
146         long delta = 0;
147         if (!isEmpty()) {
148             delta = timeMs - mLastLogRealtimeMs;
149 
150             // if the delta is invalid, or if the delta is great enough using filler elements would
151             // result in an empty log anyways, just clear the log and continue, otherwise insert
152             // filler elements until we have a reasonable delta
153             if (delta < 0 || (delta / FillerEvent.MAX_TIME_DELTA) >= mLog.length - 1) {
154                 clear();
155                 delta = 0;
156             } else {
157                 while (delta >= LogEvent.MAX_TIME_DELTA) {
158                     long timeDelta = Math.min(FillerEvent.MAX_TIME_DELTA, delta);
159                     addLogEventInternal(new FillerEvent(timeDelta));
160                     delta -= timeDelta;
161                 }
162             }
163         }
164 
165         // for first log entry, set initial times
166         if (isEmpty()) {
167             mStartRealtimeMs = timeMs;
168             mLastLogRealtimeMs = mStartRealtimeMs;
169         }
170 
171         addLogEventInternal(createLogEvent(delta, event, args));
172     }
173 
addLogEventInternal(Log event)174     private void addLogEventInternal(Log event) {
175         Preconditions.checkState(mStartRealtimeMs != -1 && mLastLogRealtimeMs != -1);
176 
177         if (mLogSize == mLog.length) {
178             // if log is full, size will remain the same, but update the start time
179             mStartRealtimeMs += mLog[startIndex()].getTimeDeltaMs();
180         } else {
181             // otherwise add an item
182             mLogSize++;
183         }
184 
185         // set log and increment end index
186         mLog[mLogEndIndex] = event;
187         mLogEndIndex = incrementIndex(mLogEndIndex);
188         mLastLogRealtimeMs = mLastLogRealtimeMs + event.getTimeDeltaMs();
189     }
190 
191     /** Clears the log of all entries. */
clear()192     public synchronized void clear() {
193         mLogEndIndex = 0;
194         mLogSize = 0;
195 
196         mStartRealtimeMs = -1;
197         mLastLogRealtimeMs = -1;
198     }
199 
200     // checks if the log is empty (if empty, times are invalid)
isEmpty()201     private synchronized boolean isEmpty() {
202         return mLogSize == 0;
203     }
204 
205     /** Iterates over the event log, passing each log string to the given consumer. */
iterate(Consumer<String> consumer)206     public synchronized void iterate(Consumer<String> consumer) {
207         LogIterator it = new LogIterator();
208         while (it.hasNext()) {
209             consumer.accept(it.next());
210         }
211     }
212 
213     /**
214      * Iterates over the event log, passing each filter-matching log string to the given
215      * consumer.
216      */
iterate(String filter, Consumer<String> consumer)217     public synchronized void iterate(String filter, Consumer<String> consumer) {
218         LogIterator it = new LogIterator(filter);
219         while (it.hasNext()) {
220             consumer.accept(it.next());
221         }
222     }
223 
224     // returns the index of the first element
startIndex()225     private int startIndex() {
226         return wrapIndex(mLogEndIndex - mLogSize);
227     }
228 
229     // returns the index after this one
incrementIndex(int index)230     private int incrementIndex(int index) {
231         if (index == -1) {
232             return startIndex();
233         } else if (index >= 0) {
234             return wrapIndex(index + 1);
235         } else {
236             throw new IllegalArgumentException();
237         }
238     }
239 
240     // rolls over the given index if necessary
wrapIndex(int index)241     private int wrapIndex(int index) {
242         // java modulo will keep negative sign, we need to rollover
243         return (index % mLog.length + mLog.length) % mLog.length;
244     }
245 
246     private class LogIterator implements Iterator<String> {
247 
248         private final @Nullable String mFilter;
249 
250         private final long mSystemTimeDeltaMs;
251 
252         private long mCurrentRealtimeMs;
253         private int mIndex;
254         private int mCount;
255 
LogIterator()256         LogIterator() {
257             this(null);
258         }
259 
LogIterator(@ullable String filter)260         LogIterator(@Nullable String filter) {
261             mFilter = filter;
262             mSystemTimeDeltaMs = System.currentTimeMillis() - SystemClock.elapsedRealtime();
263             mCurrentRealtimeMs = mStartRealtimeMs;
264             mIndex = -1;
265             mCount = -1;
266 
267             increment();
268         }
269 
270         @Override
hasNext()271         public boolean hasNext() {
272             return mCount < mLogSize;
273         }
274 
next()275         public String next() {
276             if (!hasNext()) {
277                 throw new NoSuchElementException();
278             }
279 
280             Log log = mLog[mIndex];
281             long timeMs = mCurrentRealtimeMs + log.getTimeDeltaMs() + mSystemTimeDeltaMs;
282 
283             increment();
284 
285             return getTimePrefix(timeMs) + log.getLogString();
286         }
287 
288         @Override
remove()289         public void remove() {
290             throw new UnsupportedOperationException();
291         }
292 
increment()293         private void increment() {
294             long nextDeltaMs = mIndex == -1 ? 0 : mLog[mIndex].getTimeDeltaMs();
295             do {
296                 mCurrentRealtimeMs += nextDeltaMs;
297                 mIndex = incrementIndex(mIndex);
298                 if (++mCount < mLogSize) {
299                     nextDeltaMs = mLog[mIndex].getTimeDeltaMs();
300                 }
301             } while (mCount < mLogSize && (mLog[mIndex].isFiller() || (mFilter != null
302                     && !mLog[mIndex].filter(mFilter))));
303         }
304     }
305 }
306 
307