Home | History | Annotate | Download | only in metrics
      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
     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