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