223 lines
6.9 KiB
Java
223 lines
6.9 KiB
Java
/*
|
|
* 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);
|
|
}
|
|
}
|
|
}
|