/*
 * 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
/** * 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
/** * 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 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.)
Params:
  • horizonMs – block until this timestamp is overwritten, 0 for non-blocking read.
/** * 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 read(long) to yeild a session containing only events that occur after this call.
/** * 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.
/** * 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
/** * 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
/** * 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); } } }