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