• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 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 
17 package android.util;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.SystemApi;
22 import android.compat.annotation.UnsupportedAppUsage;
23 import android.os.Build;
24 
25 import java.io.BufferedReader;
26 import java.io.FileReader;
27 import java.io.IOException;
28 import java.io.UnsupportedEncodingException;
29 import java.nio.BufferUnderflowException;
30 import java.nio.ByteBuffer;
31 import java.nio.ByteOrder;
32 import java.util.Arrays;
33 import java.util.Collection;
34 import java.util.HashMap;
35 import java.util.regex.Matcher;
36 import java.util.regex.Pattern;
37 
38 /**
39  * Access to the system diagnostic event record.  System diagnostic events are
40  * used to record certain system-level events (such as garbage collection,
41  * activity manager state, system watchdogs, and other low level activity),
42  * which may be automatically collected and analyzed during system development.
43  *
44  * <p>This is <b>not</b> the main "logcat" debugging log ({@link android.util.Log})!
45  * These diagnostic events are for system integrators, not application authors.
46  *
47  * <p>Events use integer tag codes corresponding to /system/etc/event-log-tags.
48  * They carry a payload of one or more int, long, or String values.  The
49  * event-log-tags file defines the payload contents for each type code.
50  */
51 @android.ravenwood.annotation.RavenwoodKeepWholeClass
52 @android.ravenwood.annotation.RavenwoodNativeSubstitutionClass(
53         "com.android.platform.test.ravenwood.nativesubstitution.EventLog_host")
54 public class EventLog {
EventLog()55     /** @hide */ public EventLog() {}
56 
57     private static final String TAG = "EventLog";
58 
59     private static final String TAGS_FILE = "/system/etc/event-log-tags";
60     private static final String COMMENT_PATTERN = "^\\s*(#.*)?$";
61     private static final String TAG_PATTERN = "^\\s*(\\d+)\\s+(\\w+)\\s*(\\(.*\\))?\\s*$";
62     private static HashMap<String, Integer> sTagCodes = null;
63     private static HashMap<Integer, String> sTagNames = null;
64 
65     /** A previously logged event read from the logs. Instances are thread safe. */
66     public static final class Event {
67         private final ByteBuffer mBuffer;
68         private Exception mLastWtf;
69 
70         // Layout of event log entry received from Android logger.
71         //  see system/logging/liblog/include/log/log_read.h
72         private static final int LENGTH_OFFSET = 0;
73         private static final int HEADER_SIZE_OFFSET = 2;
74         private static final int PROCESS_OFFSET = 4;
75         private static final int THREAD_OFFSET = 8;
76         private static final int SECONDS_OFFSET = 12;
77         private static final int NANOSECONDS_OFFSET = 16;
78         private static final int UID_OFFSET = 24;
79 
80         // Layout for event log v1 format, v2 and v3 use HEADER_SIZE_OFFSET
81         private static final int V1_PAYLOAD_START = 20;
82         private static final int TAG_LENGTH = 4;
83 
84         // Value types
85         private static final byte INT_TYPE    = 0;
86         private static final byte LONG_TYPE   = 1;
87         private static final byte STRING_TYPE = 2;
88         private static final byte LIST_TYPE   = 3;
89         private static final byte FLOAT_TYPE = 4;
90 
91         /** @param data containing event, read from the system */
92         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
Event(byte[] data)93         /*package*/ Event(byte[] data) {
94             mBuffer = ByteBuffer.wrap(data);
95             mBuffer.order(ByteOrder.nativeOrder());
96         }
97 
98         /** @return the process ID which wrote the log entry */
getProcessId()99         public int getProcessId() {
100             return mBuffer.getInt(PROCESS_OFFSET);
101         }
102 
103         /**
104          * @return the UID which wrote the log entry
105          * @hide
106          */
107         @SystemApi
getUid()108         public int getUid() {
109             try {
110                 return mBuffer.getInt(UID_OFFSET);
111             } catch (IndexOutOfBoundsException e) {
112                 // buffer won't contain the UID if the caller doesn't have permission.
113                 return -1;
114             }
115         }
116 
117         /** @return the thread ID which wrote the log entry */
getThreadId()118         public int getThreadId() {
119             return mBuffer.getInt(THREAD_OFFSET);
120         }
121 
122         /** @return the wall clock time when the entry was written */
getTimeNanos()123         public long getTimeNanos() {
124             return mBuffer.getInt(SECONDS_OFFSET) * 1000000000l
125                     + mBuffer.getInt(NANOSECONDS_OFFSET);
126         }
127 
128         /** @return the type tag code of the entry */
getTag()129         public int getTag() {
130             return mBuffer.getInt(getHeaderSize());
131         }
132 
getHeaderSize()133         private int getHeaderSize() {
134             int length = mBuffer.getShort(HEADER_SIZE_OFFSET);
135             if (length != 0) {
136                 return length;
137             }
138             return V1_PAYLOAD_START;
139         }
140         /** @return one of Integer, Long, Float, String, null, or Object[] of same. */
getData()141         public synchronized Object getData() {
142             try {
143                 int offset = getHeaderSize();
144                 mBuffer.limit(offset + mBuffer.getShort(LENGTH_OFFSET));
145                 if ((offset + TAG_LENGTH) >= mBuffer.limit()) {
146                     // no payload
147                     return null;
148                 }
149                 mBuffer.position(offset + TAG_LENGTH); // Just after the tag.
150                 return decodeObject();
151             } catch (IllegalArgumentException e) {
152                 Log.wtf(TAG, "Illegal entry payload: tag=" + getTag(), e);
153                 mLastWtf = e;
154                 return null;
155             } catch (BufferUnderflowException e) {
156                 Log.wtf(TAG, "Truncated entry payload: tag=" + getTag(), e);
157                 mLastWtf = e;
158                 return null;
159             }
160         }
161 
162         /**
163          * Construct a new EventLog object from the current object, copying all log metadata
164          * but replacing the actual payload with the content provided.
165          * @hide
166          */
withNewData(@ullable Object object)167         public Event withNewData(@Nullable Object object) {
168             byte[] payload = encodeObject(object);
169             if (payload.length > 65535 - TAG_LENGTH) {
170                 throw new IllegalArgumentException("Payload too long");
171             }
172             int headerLength = getHeaderSize();
173             byte[] newBytes = new byte[headerLength + TAG_LENGTH + payload.length];
174             // Copy header (including the 4 bytes of tag integer at the beginning of payload)
175             System.arraycopy(mBuffer.array(), 0, newBytes, 0, headerLength + TAG_LENGTH);
176             // Fill in encoded objects
177             System.arraycopy(payload, 0, newBytes, headerLength + TAG_LENGTH, payload.length);
178             Event result = new Event(newBytes);
179             // Patch payload length in header
180             result.mBuffer.putShort(LENGTH_OFFSET, (short) (payload.length + TAG_LENGTH));
181             return result;
182         }
183 
184         /** @return the loggable item at the current position in mBuffer. */
decodeObject()185         private Object decodeObject() {
186             byte type = mBuffer.get();
187             switch (type) {
188             case INT_TYPE:
189                 return mBuffer.getInt();
190 
191             case LONG_TYPE:
192                 return mBuffer.getLong();
193 
194             case FLOAT_TYPE:
195                 return mBuffer.getFloat();
196 
197             case STRING_TYPE:
198                 try {
199                     int length = mBuffer.getInt();
200                     int start = mBuffer.position();
201                     mBuffer.position(start + length);
202                     return new String(mBuffer.array(), start, length, "UTF-8");
203                 } catch (UnsupportedEncodingException e) {
204                     Log.wtf(TAG, "UTF-8 is not supported", e);
205                     mLastWtf = e;
206                     return null;
207                 }
208 
209             case LIST_TYPE:
210                 int length = mBuffer.get();
211                 if (length < 0) length += 256;  // treat as signed byte
212                 Object[] array = new Object[length];
213                 for (int i = 0; i < length; ++i) array[i] = decodeObject();
214                 return array;
215 
216             default:
217                 throw new IllegalArgumentException("Unknown entry type: " + type);
218             }
219         }
220 
encodeObject(@ullable Object object)221         private static @NonNull byte[] encodeObject(@Nullable Object object) {
222             if (object == null) {
223                 return new byte[0];
224             }
225             if (object instanceof Integer) {
226                 return ByteBuffer.allocate(1 + 4)
227                         .order(ByteOrder.nativeOrder())
228                         .put(INT_TYPE)
229                         .putInt((Integer) object)
230                         .array();
231             } else if (object instanceof Long) {
232                 return ByteBuffer.allocate(1 + 8)
233                         .order(ByteOrder.nativeOrder())
234                         .put(LONG_TYPE)
235                         .putLong((Long) object)
236                         .array();
237             } else if (object instanceof Float) {
238                 return ByteBuffer.allocate(1 + 4)
239                         .order(ByteOrder.nativeOrder())
240                         .put(FLOAT_TYPE)
241                         .putFloat((Float) object)
242                         .array();
243             } else if (object instanceof String) {
244                 String string = (String) object;
245                 byte[] bytes;
246                 try {
247                     bytes = string.getBytes("UTF-8");
248                 } catch (UnsupportedEncodingException e) {
249                     bytes = new byte[0];
250                 }
251                 return ByteBuffer.allocate(1 + 4 + bytes.length)
252                          .order(ByteOrder.nativeOrder())
253                          .put(STRING_TYPE)
254                          .putInt(bytes.length)
255                          .put(bytes)
256                          .array();
257             } else if (object instanceof Object[]) {
258                 Object[] objects = (Object[]) object;
259                 if (objects.length > 255) {
260                     throw new IllegalArgumentException("Object array too long");
261                 }
262                 byte[][] bytes = new byte[objects.length][];
263                 int totalLength = 0;
264                 for (int i = 0; i < objects.length; i++) {
265                     bytes[i] = encodeObject(objects[i]);
266                     totalLength += bytes[i].length;
267                 }
268                 ByteBuffer buffer = ByteBuffer.allocate(1 + 1 + totalLength)
269                         .order(ByteOrder.nativeOrder())
270                         .put(LIST_TYPE)
271                         .put((byte) objects.length);
272                 for (int i = 0; i < objects.length; i++) {
273                     buffer.put(bytes[i]);
274                 }
275                 return buffer.array();
276             } else {
277                 throw new IllegalArgumentException("Unknown object type " + object);
278             }
279         }
280 
281         /** @hide */
fromBytes(byte[] data)282         public static Event fromBytes(byte[] data) {
283             return new Event(data);
284         }
285 
286         /** @hide */
getBytes()287         public byte[] getBytes() {
288             byte[] bytes = mBuffer.array();
289             return Arrays.copyOf(bytes, bytes.length);
290         }
291 
292         /**
293          * Retreive the last WTF error generated by this object.
294          * @hide
295          */
296         //VisibleForTesting
getLastError()297         public Exception getLastError() {
298             return mLastWtf;
299         }
300 
301         /**
302          * Clear the error state for this object.
303          * @hide
304          */
305         //VisibleForTesting
clearError()306         public void clearError() {
307             mLastWtf = null;
308         }
309 
310         /**
311          * @hide
312          */
313         @Override
equals(@ullable Object o)314         public boolean equals(@Nullable Object o) {
315             // Not using ByteBuffer.equals since it takes buffer position into account and we
316             // always use absolute positions here.
317             if (this == o) return true;
318             if (o == null || getClass() != o.getClass()) return false;
319             Event other = (Event) o;
320             return Arrays.equals(mBuffer.array(), other.mBuffer.array());
321         }
322 
323         /**
324          * @hide
325          */
326         @Override
hashCode()327         public int hashCode() {
328             // Not using ByteBuffer.hashCode since it takes buffer position into account and we
329             // always use absolute positions here.
330             return Arrays.hashCode(mBuffer.array());
331         }
332     }
333 
334     // We assume that the native methods deal with any concurrency issues.
335 
336     /**
337      * Record an event log message.
338      * @param tag The event type tag code
339      * @param value A value to log
340      * @return The number of bytes written
341      */
writeEvent(int tag, int value)342     public static native int writeEvent(int tag, int value);
343 
344     /**
345      * Record an event log message.
346      * @param tag The event type tag code
347      * @param value A value to log
348      * @return The number of bytes written
349      */
writeEvent(int tag, long value)350     public static native int writeEvent(int tag, long value);
351 
352     /**
353      * Record an event log message.
354      * @param tag The event type tag code
355      * @param value A value to log
356      * @return The number of bytes written
357      */
writeEvent(int tag, float value)358     public static native int writeEvent(int tag, float value);
359 
360     /**
361      * Record an event log message.
362      * @param tag The event type tag code
363      * @param str A value to log
364      * @return The number of bytes written
365      */
writeEvent(int tag, String str)366     public static native int writeEvent(int tag, String str);
367 
368     /**
369      * Record an event log message.
370      * @param tag The event type tag code
371      * @param list A list of values to log
372      * @return The number of bytes written
373      */
writeEvent(int tag, Object... list)374     public static native int writeEvent(int tag, Object... list);
375 
376     /**
377      * Read events from the log, filtered by type.
378      * @param tags to search for
379      * @param output container to add events into
380      * @throws IOException if something goes wrong reading events
381      */
readEvents(int[] tags, Collection<Event> output)382     public static native void readEvents(int[] tags, Collection<Event> output)
383             throws IOException;
384 
385     /**
386      * Read events from the log, filtered by type, blocking until logs are about to be overwritten.
387      * @param tags to search for
388      * @param timestamp timestamp allow logs before this time to be overwritten.
389      * @param output container to add events into
390      * @throws IOException if something goes wrong reading events
391      * @hide
392      */
393     @SystemApi
readEventsOnWrapping(int[] tags, long timestamp, Collection<Event> output)394     public static native void readEventsOnWrapping(int[] tags, long timestamp,
395             Collection<Event> output)
396             throws IOException;
397 
398     /**
399      * Get the name associated with an event type tag code.
400      * @param tag code to look up
401      * @return the name of the tag, or null if no tag has that number
402      */
getTagName(int tag)403     public static String getTagName(int tag) {
404         readTagsFile();
405         return sTagNames.get(tag);
406     }
407 
408     /**
409      * Get the event type tag code associated with an event name.
410      * @param name of event to look up
411      * @return the tag code, or -1 if no tag has that name
412      */
getTagCode(String name)413     public static int getTagCode(String name) {
414         readTagsFile();
415         Integer code = sTagCodes.get(name);
416         return code != null ? code : -1;
417     }
418 
419     /**
420      * Read TAGS_FILE, populating sTagCodes and sTagNames, if not already done.
421      */
422     @android.ravenwood.annotation.RavenwoodReplace
readTagsFile()423     private static synchronized void readTagsFile() {
424         if (sTagCodes != null && sTagNames != null) return;
425 
426         sTagCodes = new HashMap<String, Integer>();
427         sTagNames = new HashMap<Integer, String>();
428 
429         Pattern comment = Pattern.compile(COMMENT_PATTERN);
430         Pattern tag = Pattern.compile(TAG_PATTERN);
431         BufferedReader reader = null;
432         String line;
433 
434         try {
435             reader = new BufferedReader(new FileReader(TAGS_FILE), 256);
436             while ((line = reader.readLine()) != null) {
437                 if (comment.matcher(line).matches()) continue;
438 
439                 Matcher m = tag.matcher(line);
440                 if (!m.matches()) {
441                     Log.wtf(TAG, "Bad entry in " + TAGS_FILE + ": " + line);
442                     continue;
443                 }
444 
445                 try {
446                     int num = Integer.parseInt(m.group(1));
447                     String name = m.group(2);
448                     registerTagLocked(num, name);
449                 } catch (NumberFormatException e) {
450                     Log.wtf(TAG, "Error in " + TAGS_FILE + ": " + line, e);
451                 }
452             }
453         } catch (IOException e) {
454             Log.wtf(TAG, "Error reading " + TAGS_FILE, e);
455             // Leave the maps existing but unpopulated
456         } finally {
457             try { if (reader != null) reader.close(); } catch (IOException e) {}
458         }
459     }
460 
registerTagLocked(int num, String name)461     private static void registerTagLocked(int num, String name) {
462         sTagCodes.put(name, num);
463         sTagNames.put(num, name);
464     }
465 
readTagsFile$ravenwood()466     private static synchronized void readTagsFile$ravenwood() {
467         // TODO: restore parsing logic once we carry into runtime
468         sTagCodes = new HashMap<String, Integer>();
469         sTagNames = new HashMap<Integer, String>();
470 
471         // Hard-code a few common tags
472         registerTagLocked(524288, "sysui_action");
473         registerTagLocked(524290, "sysui_count");
474         registerTagLocked(524291, "sysui_histogram");
475     }
476 }
477