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  */
16 package android.metrics;
17 
18 import android.annotation.SystemApi;
19 import android.util.EventLog;
20 
21 import com.android.internal.annotations.VisibleForTesting;
22 import com.android.internal.logging.MetricsLogger;
23 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
24 
25 import java.io.IOException;
26 import java.util.ArrayList;
27 import java.util.Collection;
28 import java.util.LinkedList;
29 import java.util.Queue;
30 import java.util.concurrent.TimeUnit;
31 
32 /**
33  * Read platform logs.
34  *
35  * @hide
36  */
37 @SystemApi
38 public 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
setLogReader(LogReader reader)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      */
read(long horizonMs)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      */
checkpoint()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      */
reset()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? */
hasNext()137     public boolean hasNext() {
138         return !mPendingQueue.isEmpty();
139     }
140 
141     /* Return the next entry in the current log session. */
next()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 
Event(long timeMillis, int pid, int uid, Object data)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 
Event(EventLog.Event nativeEvent)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 
getTimeMillis()177         public long getTimeMillis() {
178             return mTimeMillis;
179         }
180 
getProcessId()181         public int getProcessId() {
182             return mPid;
183         }
184 
getUid()185         public int getUid() {
186             return mUid;
187         }
188 
getData()189         public Object getData() {
190             return mData;
191         }
192 
setData(Object data)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 {
readEvents(int[] tags, long horizonMs, Collection<Event> events)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 
writeCheckpoint(int tag)217         public void writeCheckpoint(int tag) {
218             MetricsLogger logger = new MetricsLogger();
219             logger.action(MetricsEvent.METRICS_CHECKPOINT, tag);
220         }
221     }
222 }
223