MetricsReader.java revision 5357e642bcd0b97e6afd89ae005d17a2d813734b
1/*
2 * Copyright (C) 2017 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 */
16package android.metrics;
17
18import android.annotation.SystemApi;
19import android.util.EventLog;
20
21import com.android.internal.annotations.VisibleForTesting;
22import com.android.internal.logging.MetricsLogger;
23import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
24
25import java.io.IOException;
26import java.util.ArrayList;
27import java.util.Collection;
28import java.util.LinkedList;
29import java.util.Queue;
30import java.util.concurrent.TimeUnit;
31
32/**
33 * Read platform logs.
34 *
35 * @hide
36 */
37@SystemApi
38public class MetricsReader {
39    private Queue<LogMaker> mPendingQueue = new LinkedList<>();
40    private Queue<LogMaker> mSeenQueue = new LinkedList<>();
41    private int[] LOGTAGS = {MetricsLogger.LOGTAG};
42
43    private LogReader mReader = new LogReader();
44    private int mCheckpointTag = -1;
45
46    /**
47     * Set the reader to isolate unit tests from the framework
48     *
49     * @hide
50     */
51    @VisibleForTesting
52    public void setLogReader(LogReader reader) {
53        mReader = reader;
54    }
55
56    /**
57     * Read the available logs into a new session.
58     *
59     * The session will contain events starting from the oldest available
60     * log on the system up to the most recent at the time of this call.
61     *
62     * A call to {@link #checkpoint()} will cause the session to contain
63     * only events that occured after that call.
64     *
65     * This call will not return until the system buffer overflows the
66     * specified timestamp. If the specified timestamp is 0, then the
67     * call will return immediately since any logs 1970 have already been
68     * overwritten (n.b. if the underlying system has the capability to
69     * store many decades of system logs, this call may fail in
70     * interesting ways.)
71     *
72     * @param horizonMs block until this timestamp is overwritten, 0 for non-blocking read.
73     */
74    public void read(long horizonMs) {
75        ArrayList<Event> nativeEvents = new ArrayList<>();
76        try {
77            mReader.readEvents(LOGTAGS, horizonMs, nativeEvents);
78        } catch (IOException e) {
79            e.printStackTrace();
80        }
81        mPendingQueue.clear();
82        mSeenQueue.clear();
83        for (Event event : nativeEvents) {
84            final long eventTimestampMs = event.getTimeMillis();
85            Object data = event.getData();
86            Object[] objects;
87            if (data instanceof Object[]) {
88                objects = (Object[]) data;
89            } else {
90                // wrap scalar objects
91                objects = new Object[1];
92                objects[0] = data;
93            }
94            final LogMaker log = new LogMaker(objects)
95                    .setTimestamp(eventTimestampMs)
96                    .setProcessId(event.getProcessId());
97            if (log.getCategory() == MetricsEvent.METRICS_CHECKPOINT) {
98                if (log.getSubtype() == mCheckpointTag) {
99                    mPendingQueue.clear();
100                }
101            } else {
102                mPendingQueue.offer(log);
103            }
104        }
105    }
106
107    /**
108     * Empties the session and causes the next {@link #read(long)} to
109     * yeild a session containing only events that occur after this call.
110     */
111    public void checkpoint() {
112        // write a checkpoint into the log stream
113        mCheckpointTag = (int) (System.currentTimeMillis() % 0x7fffffff);
114        mReader.writeCheckpoint(mCheckpointTag);
115        // any queued event is now too old, so drop them.
116        mPendingQueue.clear();
117        mSeenQueue.clear();
118    }
119
120    /**
121     * Rewind the session to the beginning of time and replay all available logs.
122     */
123    public void reset() {
124        // flush the rest of hte pending events
125        mSeenQueue.addAll(mPendingQueue);
126        mPendingQueue.clear();
127        mCheckpointTag = -1;
128
129        // swap queues
130        Queue<LogMaker> tmp = mPendingQueue;
131        mPendingQueue = mSeenQueue;
132        mSeenQueue = tmp;
133    }
134
135    /* Does the current log session have another entry? */
136    public boolean hasNext() {
137        return !mPendingQueue.isEmpty();
138    }
139
140    /* Return the next entry in the current log session. */
141    public LogMaker next() {
142        final LogMaker next = mPendingQueue.poll();
143        if (next != null) {
144            mSeenQueue.offer(next);
145        }
146        return next;
147    }
148
149    /**
150     * Wrapper for the Event object, to facilitate testing.
151     *
152     * @hide
153     */
154    @VisibleForTesting
155    public static class Event {
156        long mTimeMillis;
157        int mPid;
158        Object mData;
159
160        public Event(long timeMillis, int pid, Object data) {
161            mTimeMillis = timeMillis;
162            mPid = pid;
163            mData = data;
164        }
165
166        Event(EventLog.Event nativeEvent) {
167            mTimeMillis = TimeUnit.MILLISECONDS.convert(
168                    nativeEvent.getTimeNanos(), TimeUnit.NANOSECONDS);
169            mPid = nativeEvent.getProcessId();
170            mData = nativeEvent.getData();
171        }
172
173        public long getTimeMillis() {
174            return mTimeMillis;
175        }
176
177        public int getProcessId() {
178            return mPid;
179        }
180
181        public Object getData() {
182            return mData;
183        }
184
185        public void setData(Object data) {
186            mData = data;
187        }
188    }
189
190    /**
191     * Wrapper for the Event reader, to facilitate testing.
192     *
193     * @hide
194     */
195    @VisibleForTesting
196    public static class LogReader {
197        public void readEvents(int[] tags, long horizonMs, Collection<Event> events)
198                throws IOException {
199            // Testing in Android: the Static Final Class Strikes Back!
200            ArrayList<EventLog.Event> nativeEvents = new ArrayList<>();
201            long horizonNs = TimeUnit.NANOSECONDS.convert(horizonMs, TimeUnit.MILLISECONDS);
202            EventLog.readEventsOnWrapping(tags, horizonNs, nativeEvents);
203            for (EventLog.Event nativeEvent : nativeEvents) {
204                Event event = new Event(nativeEvent);
205                events.add(event);
206            }
207        }
208
209        public void writeCheckpoint(int tag) {
210            MetricsLogger logger = new MetricsLogger();
211            logger.action(MetricsEvent.METRICS_CHECKPOINT, tag);
212        }
213    }
214}
215