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