| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package android.metrics; |
| |
| import android.annotation.SystemApi; |
| import android.util.EventLog; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.logging.MetricsLogger; |
| import com.android.internal.logging.nano.MetricsProto.MetricsEvent; |
| |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.LinkedList; |
| import java.util.Queue; |
| import java.util.concurrent.TimeUnit; |
| |
| /** |
| * Read platform logs. |
| * |
| * @hide |
| */ |
| @SystemApi |
| public class MetricsReader { |
| private Queue<LogMaker> mPendingQueue = new LinkedList<>(); |
| private Queue<LogMaker> mSeenQueue = new LinkedList<>(); |
| private int[] LOGTAGS = {MetricsLogger.LOGTAG}; |
| |
| private LogReader mReader = new LogReader(); |
| private int mCheckpointTag = -1; |
| |
| /** |
| * Set the reader to isolate unit tests from the framework |
| * |
| * @hide |
| */ |
| @VisibleForTesting |
| public void setLogReader(LogReader reader) { |
| mReader = reader; |
| } |
| |
| /** |
| * Read the available logs into a new session. |
| * |
| * The session will contain events starting from the oldest available |
| * log on the system up to the most recent at the time of this call. |
| * |
| * A call to {@link #checkpoint()} will cause the session to contain |
| * only events that occured after that call. |
| * |
| * This call will not return until the system buffer overflows the |
| * specified timestamp. If the specified timestamp is 0, then the |
| * call will return immediately since any logs 1970 have already been |
| * overwritten (n.b. if the underlying system has the capability to |
| * store many decades of system logs, this call may fail in |
| * interesting ways.) |
| * |
| * @param horizonMs block until this timestamp is overwritten, 0 for non-blocking read. |
| */ |
| public void read(long horizonMs) { |
| ArrayList<Event> nativeEvents = new ArrayList<>(); |
| try { |
| mReader.readEvents(LOGTAGS, horizonMs, nativeEvents); |
| } catch (IOException e) { |
| e.printStackTrace(); |
| } |
| mPendingQueue.clear(); |
| mSeenQueue.clear(); |
| for (Event event : nativeEvents) { |
| final long eventTimestampMs = event.getTimeMillis(); |
| Object data = event.getData(); |
| Object[] objects; |
| if (data instanceof Object[]) { |
| objects = (Object[]) data; |
| } else { |
| // wrap scalar objects |
| objects = new Object[1]; |
| objects[0] = data; |
| } |
| final LogMaker log = new LogMaker(objects) |
| .setTimestamp(eventTimestampMs) |
| .setUid(event.getUid()) |
| .setProcessId(event.getProcessId()); |
| if (log.getCategory() == MetricsEvent.METRICS_CHECKPOINT) { |
| if (log.getSubtype() == mCheckpointTag) { |
| mPendingQueue.clear(); |
| } |
| } else { |
| mPendingQueue.offer(log); |
| } |
| } |
| } |
| |
| /** |
| * Empties the session and causes the next {@link #read(long)} to |
| * yeild a session containing only events that occur after this call. |
| */ |
| public void checkpoint() { |
| // write a checkpoint into the log stream |
| mCheckpointTag = (int) (System.currentTimeMillis() % 0x7fffffff); |
| mReader.writeCheckpoint(mCheckpointTag); |
| // any queued event is now too old, so drop them. |
| mPendingQueue.clear(); |
| mSeenQueue.clear(); |
| } |
| |
| /** |
| * Rewind the session to the beginning of time and replay all available logs. |
| */ |
| public void reset() { |
| // flush the rest of hte pending events |
| mSeenQueue.addAll(mPendingQueue); |
| mPendingQueue.clear(); |
| mCheckpointTag = -1; |
| |
| // swap queues |
| Queue<LogMaker> tmp = mPendingQueue; |
| mPendingQueue = mSeenQueue; |
| mSeenQueue = tmp; |
| } |
| |
| /* Does the current log session have another entry? */ |
| public boolean hasNext() { |
| return !mPendingQueue.isEmpty(); |
| } |
| |
| /* Return the next entry in the current log session. */ |
| public LogMaker next() { |
| final LogMaker next = mPendingQueue.poll(); |
| if (next != null) { |
| mSeenQueue.offer(next); |
| } |
| return next; |
| } |
| |
| /** |
| * Wrapper for the Event object, to facilitate testing. |
| * |
| * @hide |
| */ |
| @VisibleForTesting |
| public static class Event { |
| long mTimeMillis; |
| int mPid; |
| int mUid; |
| Object mData; |
| |
| public Event(long timeMillis, int pid, int uid, Object data) { |
| mTimeMillis = timeMillis; |
| mPid = pid; |
| mUid = uid; |
| mData = data; |
| } |
| |
| Event(EventLog.Event nativeEvent) { |
| mTimeMillis = TimeUnit.MILLISECONDS.convert( |
| nativeEvent.getTimeNanos(), TimeUnit.NANOSECONDS); |
| mPid = nativeEvent.getProcessId(); |
| mUid = nativeEvent.getUid(); |
| mData = nativeEvent.getData(); |
| } |
| |
| public long getTimeMillis() { |
| return mTimeMillis; |
| } |
| |
| public int getProcessId() { |
| return mPid; |
| } |
| |
| public int getUid() { |
| return mUid; |
| } |
| |
| public Object getData() { |
| return mData; |
| } |
| |
| public void setData(Object data) { |
| mData = data; |
| } |
| } |
| |
| /** |
| * Wrapper for the Event reader, to facilitate testing. |
| * |
| * @hide |
| */ |
| @VisibleForTesting |
| public static class LogReader { |
| public void readEvents(int[] tags, long horizonMs, Collection<Event> events) |
| throws IOException { |
| // Testing in Android: the Static Final Class Strikes Back! |
| ArrayList<EventLog.Event> nativeEvents = new ArrayList<>(); |
| long horizonNs = TimeUnit.NANOSECONDS.convert(horizonMs, TimeUnit.MILLISECONDS); |
| EventLog.readEventsOnWrapping(tags, horizonNs, nativeEvents); |
| for (EventLog.Event nativeEvent : nativeEvents) { |
| Event event = new Event(nativeEvent); |
| events.add(event); |
| } |
| } |
| |
| public void writeCheckpoint(int tag) { |
| MetricsLogger logger = new MetricsLogger(); |
| logger.action(MetricsEvent.METRICS_CHECKPOINT, tag); |
| } |
| } |
| } |