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
17package android.util;
18
19import java.io.BufferedReader;
20import java.io.FileReader;
21import java.io.IOException;
22import java.io.UnsupportedEncodingException;
23import java.nio.BufferUnderflowException;
24import java.nio.ByteBuffer;
25import java.nio.ByteOrder;
26import java.util.Collection;
27import java.util.HashMap;
28import java.util.regex.Matcher;
29import 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 */
44public class 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 */
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 */
86        public int getProcessId() {
87            return mBuffer.getInt(PROCESS_OFFSET);
88        }
89
90        /** @return the thread ID which wrote the log entry */
91        public int getThreadId() {
92            return mBuffer.getInt(THREAD_OFFSET);
93        }
94
95        /** @return the wall clock time when the entry was written */
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 */
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. */
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. */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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