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                    .setUid(event.getUid())
97                    .setProcessId(event.getProcessId());
98            if (log.getCategory() == MetricsEvent.METRICS_CHECKPOINT) {
99                if (log.getSubtype() == mCheckpointTag) {
100                    mPendingQueue.clear();
101                }
102            } else {
103                mPendingQueue.offer(log);
104            }
105        }
106    }
107
108    /**
109     * Empties the session and causes the next {@link #read(long)} to
110     * yeild a session containing only events that occur after this call.
111     */
112    public void checkpoint() {
113        // write a checkpoint into the log stream
114        mCheckpointTag = (int) (System.currentTimeMillis() % 0x7fffffff);
115        mReader.writeCheckpoint(mCheckpointTag);
116        // any queued event is now too old, so drop them.
117        mPendingQueue.clear();
118        mSeenQueue.clear();
119    }
120
121    /**
122     * Rewind the session to the beginning of time and replay all available logs.
123     */
124    public void reset() {
125        // flush the rest of hte pending events
126        mSeenQueue.addAll(mPendingQueue);
127        mPendingQueue.clear();
128        mCheckpointTag = -1;
129
130        // swap queues
131        Queue<LogMaker> tmp = mPendingQueue;
132        mPendingQueue = mSeenQueue;
133        mSeenQueue = tmp;
134    }
135
136    /* Does the current log session have another entry? */
137    public boolean hasNext() {
138        return !mPendingQueue.isEmpty();
139    }
140
141    /* Return the next entry in the current log session. */
142    public LogMaker next() {
143        final LogMaker next = mPendingQueue.poll();
144        if (next != null) {
145            mSeenQueue.offer(next);
146        }
147        return next;
148    }
149
150    /**
151     * Wrapper for the Event object, to facilitate testing.
152     *
153     * @hide
154     */
155    @VisibleForTesting
156    public static class Event {
157        long mTimeMillis;
158        int mPid;
159        int mUid;
160        Object mData;
161
162        public Event(long timeMillis, int pid, int uid, Object data) {
163            mTimeMillis = timeMillis;
164            mPid = pid;
165            mUid = uid;
166            mData = data;
167        }
168
169        Event(EventLog.Event nativeEvent) {
170            mTimeMillis = TimeUnit.MILLISECONDS.convert(
171                    nativeEvent.getTimeNanos(), TimeUnit.NANOSECONDS);
172            mPid = nativeEvent.getProcessId();
173            mUid = nativeEvent.getUid();
174            mData = nativeEvent.getData();
175        }
176
177        public long getTimeMillis() {
178            return mTimeMillis;
179        }
180
181        public int getProcessId() {
182            return mPid;
183        }
184
185        public int getUid() {
186            return mUid;
187        }
188
189        public Object getData() {
190            return mData;
191        }
192
193        public void setData(Object data) {
194            mData = data;
195        }
196    }
197
198    /**
199     * Wrapper for the Event reader, to facilitate testing.
200     *
201     * @hide
202     */
203    @VisibleForTesting
204    public static class LogReader {
205        public void readEvents(int[] tags, long horizonMs, Collection<Event> events)
206                throws IOException {
207            // Testing in Android: the Static Final Class Strikes Back!
208            ArrayList<EventLog.Event> nativeEvents = new ArrayList<>();
209            long horizonNs = TimeUnit.NANOSECONDS.convert(horizonMs, TimeUnit.MILLISECONDS);
210            EventLog.readEventsOnWrapping(tags, horizonNs, nativeEvents);
211            for (EventLog.Event nativeEvent : nativeEvents) {
212                Event event = new Event(nativeEvent);
213                events.add(event);
214            }
215        }
216
217        public void writeCheckpoint(int tag) {
218            MetricsLogger logger = new MetricsLogger();
219            logger.action(MetricsEvent.METRICS_CHECKPOINT, tag);
220        }
221    }
222}
223