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