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 com.google.android.collect.Lists;
20
21import java.io.IOException;
22import java.io.UnsupportedEncodingException;
23import java.nio.ByteBuffer;
24import java.nio.ByteOrder;
25import java.util.ArrayList;
26import java.util.Collection;
27import java.util.List;
28
29/**
30 * {@hide}
31 * Dynamically defined (in terms of event types), space efficient (i.e. "tight") event logging
32 * to help instrument code for large scale stability and performance monitoring.
33 *
34 * Note that this class contains all static methods.  This is done for efficiency reasons.
35 *
36 * Events for the event log are self-describing binary data structures.  They start with a 20 byte
37 * header (generated automatically) which contains all of the following in order:
38 *
39 * <ul>
40 * <li> Payload length: 2 bytes - length of the non-header portion </li>
41 * <li> Padding: 2 bytes - no meaning at this time </li>
42 * <li> Timestamp:
43 *   <ul>
44 *   <li> Seconds: 4 bytes - seconds since Epoch </li>
45 *   <li> Nanoseconds: 4 bytes - plus extra nanoseconds </li>
46 *   </ul></li>
47 * <li> Process ID: 4 bytes - matching {@link android.os.Process#myPid} </li>
48 * <li> Thread ID: 4 bytes - matching {@link android.os.Process#myTid} </li>
49 * </li>
50 * </ul>
51 *
52 * The above is followed by a payload, comprised of the following:
53 * <ul>
54 * <li> Tag: 4 bytes - unique integer used to identify a particular event.  This number is also
55 *                     used as a key to map to a string that can be displayed by log reading tools.
56 * </li>
57 * <li> Type: 1 byte - can be either {@link #INT}, {@link #LONG}, {@link #STRING},
58 *                     or {@link #LIST}. </li>
59 * <li> Event log value: the size and format of which is one of:
60 *   <ul>
61 *   <li> INT: 4 bytes </li>
62 *   <li> LONG: 8 bytes </li>
63 *   <li> STRING:
64 *     <ul>
65 *     <li> Size of STRING: 4 bytes </li>
66 *     <li> The string:  n bytes as specified in the size fields above. </li>
67 *     </ul></li>
68 *   <li> {@link List LIST}:
69 *     <ul>
70 *     <li> Num items: 1 byte </li>
71 *     <li> N value payloads, where N is the number of items specified above. </li>
72 *     </ul></li>
73 *   </ul>
74 * </li>
75 * <li> '\n': 1 byte - an automatically generated newline, used to help detect and recover from log
76 *                     corruption and enable standard unix tools like grep, tail and wc to operate
77 *                     on event logs. </li>
78 * </ul>
79 *
80 * Note that all output is done in the endian-ness of the device (as determined
81 * by {@link ByteOrder#nativeOrder()}).
82 */
83
84public class EventLog {
85
86    // Value types
87    public static final byte INT    = 0;
88    public static final byte LONG   = 1;
89    public static final byte STRING = 2;
90    public static final byte LIST   = 3;
91
92    /**
93     * An immutable tuple used to log a heterogeneous set of loggable items.
94     * The items can be Integer, Long, String, or {@link List}.
95     * The maximum number of items is 127
96     */
97    public static final class List {
98        private Object[] mItems;
99
100        /**
101         * Get a particular tuple item
102         * @param pos The position of the item in the tuple
103         */
104        public final Object getItem(int pos) {
105            return mItems[pos];
106        }
107
108        /**
109         * Get the number of items in the tuple.
110         */
111        public final byte getNumItems() {
112            return (byte) mItems.length;
113        }
114
115        /**
116         * Create a new tuple.
117         * @param items The items to create the tuple with, as varargs.
118         * @throws IllegalArgumentException if the arguments are too few (0),
119         *         too many, or aren't loggable types.
120         */
121        public List(Object... items) throws IllegalArgumentException {
122            if (items.length > Byte.MAX_VALUE) {
123                throw new IllegalArgumentException(
124                        "A List must have fewer than "
125                        + Byte.MAX_VALUE + " items in it.");
126            }
127            for (int i = 0; i < items.length; i++) {
128                final Object item = items[i];
129                if (item == null) {
130                    // Would be nice to be able to write null strings...
131                    items[i] = "";
132                } else if (!(item instanceof List ||
133                      item instanceof String ||
134                      item instanceof Integer ||
135                      item instanceof Long)) {
136                    throw new IllegalArgumentException(
137                            "Attempt to create a List with illegal item type.");
138                }
139            }
140            this.mItems = items;
141        }
142    }
143
144    /**
145     * A previously logged event read from the logs.
146     */
147    public static final class Event {
148        private final ByteBuffer mBuffer;
149
150        // Layout of event log entry received from kernel.
151        private static final int LENGTH_OFFSET = 0;
152        private static final int PROCESS_OFFSET = 4;
153        private static final int THREAD_OFFSET = 8;
154        private static final int SECONDS_OFFSET = 12;
155        private static final int NANOSECONDS_OFFSET = 16;
156
157        private static final int PAYLOAD_START = 20;
158        private static final int TAG_OFFSET = 20;
159        private static final int DATA_START = 24;
160
161        /** @param data containing event, read from the system */
162        public Event(byte[] data) {
163            mBuffer = ByteBuffer.wrap(data);
164            mBuffer.order(ByteOrder.nativeOrder());
165        }
166
167        public int getProcessId() {
168            return mBuffer.getInt(PROCESS_OFFSET);
169        }
170
171        public int getThreadId() {
172            return mBuffer.getInt(THREAD_OFFSET);
173        }
174
175        public long getTimeNanos() {
176            return mBuffer.getInt(SECONDS_OFFSET) * 1000000000l
177                    + mBuffer.getInt(NANOSECONDS_OFFSET);
178        }
179
180        public int getTag() {
181            return mBuffer.getInt(TAG_OFFSET);
182        }
183
184        /** @return one of Integer, Long, String, or List. */
185        public synchronized Object getData() {
186            mBuffer.limit(PAYLOAD_START + mBuffer.getShort(LENGTH_OFFSET));
187            mBuffer.position(DATA_START);  // Just after the tag.
188            return decodeObject();
189        }
190
191        public byte[] getRawData() {
192            return mBuffer.array();
193        }
194
195        /** @return the loggable item at the current position in mBuffer. */
196        private Object decodeObject() {
197            if (mBuffer.remaining() < 1) return null;
198            switch (mBuffer.get()) {
199            case INT:
200                if (mBuffer.remaining() < 4) return null;
201                return (Integer) mBuffer.getInt();
202
203            case LONG:
204                if (mBuffer.remaining() < 8) return null;
205                return (Long) mBuffer.getLong();
206
207            case STRING:
208                try {
209                    if (mBuffer.remaining() < 4) return null;
210                    int length = mBuffer.getInt();
211                    if (length < 0 || mBuffer.remaining() < length) return null;
212                    int start = mBuffer.position();
213                    mBuffer.position(start + length);
214                    return new String(mBuffer.array(), start, length, "UTF-8");
215                } catch (UnsupportedEncodingException e) {
216                    throw new RuntimeException(e);  // UTF-8 is guaranteed.
217                }
218
219            case LIST:
220                if (mBuffer.remaining() < 1) return null;
221                int length = mBuffer.get();
222                if (length < 0) return null;
223                Object[] array = new Object[length];
224                for (int i = 0; i < length; ++i) {
225                    array[i] = decodeObject();
226                    if (array[i] == null) return null;
227                }
228                return new List(array);
229
230            default:
231                return null;
232            }
233        }
234    }
235
236    // We assume that the native methods deal with any concurrency issues.
237
238    /**
239     * Send an event log message.
240     * @param tag An event identifer
241     * @param value A value to log
242     * @return The number of bytes written
243     */
244    public static native int writeEvent(int tag, int value);
245
246    /**
247     * Send an event log message.
248     * @param tag An event identifer
249     * @param value A value to log
250     * @return The number of bytes written
251     */
252    public static native int writeEvent(int tag, long value);
253
254    /**
255     * Send an event log message.
256     * @param tag An event identifer
257     * @param str A value to log
258     * @return The number of bytes written
259     */
260    public static native int writeEvent(int tag, String str);
261
262    /**
263     * Send an event log message.
264     * @param tag An event identifer
265     * @param list A {@link List} to log
266     * @return The number of bytes written
267     */
268    public static native int writeEvent(int tag, List list);
269
270    /**
271     * Send an event log message.
272     * @param tag An event identifer
273     * @param list A list of values to log
274     * @return The number of bytes written
275     */
276    public static int writeEvent(int tag, Object... list) {
277        return writeEvent(tag, new List(list));
278    }
279
280    /**
281     * Read events from the log, filtered by type.
282     * @param tags to search for
283     * @param output container to add events into
284     * @throws IOException if something goes wrong reading events
285     */
286    public static native void readEvents(int[] tags, Collection<Event> output)
287            throws IOException;
288
289    /**
290     * Read events from a file.
291     * @param path to read from
292     * @param output container to add events into
293     * @throws IOException if something goes wrong reading events
294     */
295    public static native void readEvents(String path, Collection<Event> output)
296            throws IOException;
297}
298