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.Arrays;
27import java.util.Collection;
28import java.util.HashMap;
29import java.util.regex.Matcher;
30import 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 */
45public class 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 */
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 */
87        public int getProcessId() {
88            return mBuffer.getInt(PROCESS_OFFSET);
89        }
90
91        /** @return the thread ID which wrote the log entry */
92        public int getThreadId() {
93            return mBuffer.getInt(THREAD_OFFSET);
94        }
95
96        /** @return the wall clock time when the entry was written */
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 */
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. */
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. */
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 */
167        public static Event fromBytes(byte[] data) {
168            return new Event(data);
169        }
170
171        /** @hide */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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