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 java.io.BufferedReader;
20 import java.io.FileReader;
21 import java.io.IOException;
22 import java.io.UnsupportedEncodingException;
23 import java.nio.BufferUnderflowException;
24 import java.nio.ByteBuffer;
25 import java.nio.ByteOrder;
26 import java.util.Collection;
27 import java.util.HashMap;
28 import java.util.regex.Matcher;
29 import java.util.regex.Pattern;
30 
31 /**
32  * Access to the system diagnostic event record.  System diagnostic events are
33  * used to record certain system-level events (such as garbage collection,
34  * activity manager state, system watchdogs, and other low level activity),
35  * which may be automatically collected and analyzed during system development.
36  *
37  * <p>This is <b>not</b> the main "logcat" debugging log ({@link android.util.Log})!
38  * These diagnostic events are for system integrators, not application authors.
39  *
40  * <p>Events use integer tag codes corresponding to /system/etc/event-log-tags.
41  * They carry a payload of one or more int, long, or String values.  The
42  * event-log-tags file defines the payload contents for each type code.
43  */
44 public class EventLog {
EventLog()45     /** @hide */ public EventLog() {}
46 
47     private static final String TAG = "EventLog";
48 
49     private static final String TAGS_FILE = "/system/etc/event-log-tags";
50     private static final String COMMENT_PATTERN = "^\\s*(#.*)?$";
51     private static final String TAG_PATTERN = "^\\s*(\\d+)\\s+(\\w+)\\s*(\\(.*\\))?\\s*$";
52     private static HashMap<String, Integer> sTagCodes = null;
53     private static HashMap<Integer, String> sTagNames = null;
54 
55     /** A previously logged event read from the logs. */
56     public static final class Event {
57         private final ByteBuffer mBuffer;
58 
59         // Layout of event log entry received from Android logger.
60         //  see system/core/include/log/logger.h
61         private static final int LENGTH_OFFSET = 0;
62         private static final int HEADER_SIZE_OFFSET = 2;
63         private static final int PROCESS_OFFSET = 4;
64         private static final int THREAD_OFFSET = 8;
65         private static final int SECONDS_OFFSET = 12;
66         private static final int NANOSECONDS_OFFSET = 16;
67 
68         // Layout for event log v1 format, v2 and v3 use HEADER_SIZE_OFFSET
69         private static final int V1_PAYLOAD_START = 20;
70         private static final int DATA_OFFSET = 4;
71 
72         // Value types
73         private static final byte INT_TYPE    = 0;
74         private static final byte LONG_TYPE   = 1;
75         private static final byte STRING_TYPE = 2;
76         private static final byte LIST_TYPE   = 3;
77         private static final byte FLOAT_TYPE = 4;
78 
79         /** @param data containing event, read from the system */
Event(byte[] data)80         /*package*/ Event(byte[] data) {
81             mBuffer = ByteBuffer.wrap(data);
82             mBuffer.order(ByteOrder.nativeOrder());
83         }
84 
85         /** @return the process ID which wrote the log entry */
getProcessId()86         public int getProcessId() {
87             return mBuffer.getInt(PROCESS_OFFSET);
88         }
89 
90         /** @return the thread ID which wrote the log entry */
getThreadId()91         public int getThreadId() {
92             return mBuffer.getInt(THREAD_OFFSET);
93         }
94 
95         /** @return the wall clock time when the entry was written */
getTimeNanos()96         public long getTimeNanos() {
97             return mBuffer.getInt(SECONDS_OFFSET) * 1000000000l
98                     + mBuffer.getInt(NANOSECONDS_OFFSET);
99         }
100 
101         /** @return the type tag code of the entry */
getTag()102         public int getTag() {
103             int offset = mBuffer.getShort(HEADER_SIZE_OFFSET);
104             if (offset == 0) {
105                 offset = V1_PAYLOAD_START;
106             }
107             return mBuffer.getInt(offset);
108         }
109 
110         /** @return one of Integer, Long, Float, String, null, or Object[] of same. */
getData()111         public synchronized Object getData() {
112             try {
113                 int offset = mBuffer.getShort(HEADER_SIZE_OFFSET);
114                 if (offset == 0) {
115                     offset = V1_PAYLOAD_START;
116                 }
117                 mBuffer.limit(offset + mBuffer.getShort(LENGTH_OFFSET));
118                 mBuffer.position(offset + DATA_OFFSET); // Just after the tag.
119                 return decodeObject();
120             } catch (IllegalArgumentException e) {
121                 Log.wtf(TAG, "Illegal entry payload: tag=" + getTag(), e);
122                 return null;
123             } catch (BufferUnderflowException e) {
124                 Log.wtf(TAG, "Truncated entry payload: tag=" + getTag(), e);
125                 return null;
126             }
127         }
128 
129         /** @return the loggable item at the current position in mBuffer. */
decodeObject()130         private Object decodeObject() {
131             byte type = mBuffer.get();
132             switch (type) {
133             case INT_TYPE:
134                 return mBuffer.getInt();
135 
136             case LONG_TYPE:
137                 return mBuffer.getLong();
138 
139             case FLOAT_TYPE:
140                 return mBuffer.getFloat();
141 
142             case STRING_TYPE:
143                 try {
144                     int length = mBuffer.getInt();
145                     int start = mBuffer.position();
146                     mBuffer.position(start + length);
147                     return new String(mBuffer.array(), start, length, "UTF-8");
148                 } catch (UnsupportedEncodingException e) {
149                     Log.wtf(TAG, "UTF-8 is not supported", e);
150                     return null;
151                 }
152 
153             case LIST_TYPE:
154                 int length = mBuffer.get();
155                 if (length < 0) length += 256;  // treat as signed byte
156                 Object[] array = new Object[length];
157                 for (int i = 0; i < length; ++i) array[i] = decodeObject();
158                 return array;
159 
160             default:
161                 throw new IllegalArgumentException("Unknown entry type: " + type);
162             }
163         }
164     }
165 
166     // We assume that the native methods deal with any concurrency issues.
167 
168     /**
169      * Record an event log message.
170      * @param tag The event type tag code
171      * @param value A value to log
172      * @return The number of bytes written
173      */
writeEvent(int tag, int value)174     public static native int writeEvent(int tag, int value);
175 
176     /**
177      * Record an event log message.
178      * @param tag The event type tag code
179      * @param value A value to log
180      * @return The number of bytes written
181      */
writeEvent(int tag, long value)182     public static native int writeEvent(int tag, long value);
183 
184     /**
185      * Record an event log message.
186      * @param tag The event type tag code
187      * @param value A value to log
188      * @return The number of bytes written
189      */
writeEvent(int tag, float value)190     public static native int writeEvent(int tag, float value);
191 
192     /**
193      * Record an event log message.
194      * @param tag The event type tag code
195      * @param str A value to log
196      * @return The number of bytes written
197      */
writeEvent(int tag, String str)198     public static native int writeEvent(int tag, String str);
199 
200     /**
201      * Record an event log message.
202      * @param tag The event type tag code
203      * @param list A list of values to log
204      * @return The number of bytes written
205      */
writeEvent(int tag, Object... list)206     public static native int writeEvent(int tag, Object... list);
207 
208     /**
209      * Read events from the log, filtered by type.
210      * @param tags to search for
211      * @param output container to add events into
212      * @throws IOException if something goes wrong reading events
213      */
readEvents(int[] tags, Collection<Event> output)214     public static native void readEvents(int[] tags, Collection<Event> output)
215             throws IOException;
216 
217     /**
218      * Get the name associated with an event type tag code.
219      * @param tag code to look up
220      * @return the name of the tag, or null if no tag has that number
221      */
getTagName(int tag)222     public static String getTagName(int tag) {
223         readTagsFile();
224         return sTagNames.get(tag);
225     }
226 
227     /**
228      * Get the event type tag code associated with an event name.
229      * @param name of event to look up
230      * @return the tag code, or -1 if no tag has that name
231      */
getTagCode(String name)232     public static int getTagCode(String name) {
233         readTagsFile();
234         Integer code = sTagCodes.get(name);
235         return code != null ? code : -1;
236     }
237 
238     /**
239      * Read TAGS_FILE, populating sTagCodes and sTagNames, if not already done.
240      */
readTagsFile()241     private static synchronized void readTagsFile() {
242         if (sTagCodes != null && sTagNames != null) return;
243 
244         sTagCodes = new HashMap<String, Integer>();
245         sTagNames = new HashMap<Integer, String>();
246 
247         Pattern comment = Pattern.compile(COMMENT_PATTERN);
248         Pattern tag = Pattern.compile(TAG_PATTERN);
249         BufferedReader reader = null;
250         String line;
251 
252         try {
253             reader = new BufferedReader(new FileReader(TAGS_FILE), 256);
254             while ((line = reader.readLine()) != null) {
255                 if (comment.matcher(line).matches()) continue;
256 
257                 Matcher m = tag.matcher(line);
258                 if (!m.matches()) {
259                     Log.wtf(TAG, "Bad entry in " + TAGS_FILE + ": " + line);
260                     continue;
261                 }
262 
263                 try {
264                     int num = Integer.parseInt(m.group(1));
265                     String name = m.group(2);
266                     sTagCodes.put(name, num);
267                     sTagNames.put(num, name);
268                 } catch (NumberFormatException e) {
269                     Log.wtf(TAG, "Error in " + TAGS_FILE + ": " + line, e);
270                 }
271             }
272         } catch (IOException e) {
273             Log.wtf(TAG, "Error reading " + TAGS_FILE, e);
274             // Leave the maps existing but unpopulated
275         } finally {
276             try { if (reader != null) reader.close(); } catch (IOException e) {}
277         }
278     }
279 }
280