/* * 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 mPendingQueue = new LinkedList<>(); private Queue 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 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 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 events) throws IOException { // Testing in Android: the Static Final Class Strikes Back! ArrayList 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); } } }