• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 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 com.android.ddmlib.log;
18 
19 import com.android.ddmlib.IDevice;
20 import com.android.ddmlib.Log;
21 import com.android.ddmlib.MultiLineReceiver;
22 import com.android.ddmlib.log.EventContainer.EventValueType;
23 import com.android.ddmlib.log.EventValueDescription.ValueType;
24 import com.android.ddmlib.log.LogReceiver.LogEntry;
25 import com.android.ddmlib.utils.ArrayHelper;
26 
27 import java.io.BufferedReader;
28 import java.io.File;
29 import java.io.FileOutputStream;
30 import java.io.FileReader;
31 import java.io.IOException;
32 import java.io.UnsupportedEncodingException;
33 import java.util.ArrayList;
34 import java.util.Calendar;
35 import java.util.Map;
36 import java.util.Set;
37 import java.util.TreeMap;
38 import java.util.Map.Entry;
39 import java.util.regex.Matcher;
40 import java.util.regex.Pattern;
41 
42 /**
43  * Parser for the "event" log.
44  */
45 public final class EventLogParser {
46 
47     /** Location of the tag map file on the device */
48     private final static String EVENT_TAG_MAP_FILE = "/system/etc/event-log-tags"; //$NON-NLS-1$
49 
50     /**
51      * Event log entry types.  These must match up with the declarations in
52      * java/android/android/util/EventLog.java.
53      */
54     private final static int EVENT_TYPE_INT      = 0;
55     private final static int EVENT_TYPE_LONG     = 1;
56     private final static int EVENT_TYPE_STRING   = 2;
57     private final static int EVENT_TYPE_LIST     = 3;
58 
59     private final static Pattern PATTERN_SIMPLE_TAG = Pattern.compile(
60     "^(\\d+)\\s+([A-Za-z0-9_]+)\\s*$"); //$NON-NLS-1$
61     private final static Pattern PATTERN_TAG_WITH_DESC = Pattern.compile(
62             "^(\\d+)\\s+([A-Za-z0-9_]+)\\s*(.*)\\s*$"); //$NON-NLS-1$
63     private final static Pattern PATTERN_DESCRIPTION = Pattern.compile(
64             "\\(([A-Za-z0-9_\\s]+)\\|(\\d+)(\\|\\d+){0,1}\\)"); //$NON-NLS-1$
65 
66     private final static Pattern TEXT_LOG_LINE = Pattern.compile(
67             "(\\d\\d)-(\\d\\d)\\s(\\d\\d):(\\d\\d):(\\d\\d).(\\d{3})\\s+I/([a-zA-Z0-9_]+)\\s*\\(\\s*(\\d+)\\):\\s+(.*)"); //$NON-NLS-1$
68 
69     private final TreeMap<Integer, String> mTagMap = new TreeMap<Integer, String>();
70 
71     private final TreeMap<Integer, EventValueDescription[]> mValueDescriptionMap =
72         new TreeMap<Integer, EventValueDescription[]>();
73 
EventLogParser()74     public EventLogParser() {
75     }
76 
77     /**
78      * Inits the parser for a specific Device.
79      * <p/>
80      * This methods reads the event-log-tags located on the device to find out
81      * what tags are being written to the event log and what their format is.
82      * @param device The device.
83      * @return <code>true</code> if success, <code>false</code> if failure or cancellation.
84      */
init(IDevice device)85     public boolean init(IDevice device) {
86         // read the event tag map file on the device.
87         try {
88             device.executeShellCommand("cat " + EVENT_TAG_MAP_FILE, //$NON-NLS-1$
89                     new MultiLineReceiver() {
90                 @Override
91                 public void processNewLines(String[] lines) {
92                     for (String line : lines) {
93                         processTagLine(line);
94                     }
95                 }
96                 public boolean isCancelled() {
97                     return false;
98                 }
99             });
100         } catch (Exception e) {
101             // catch all possible exceptions and return false.
102             return false;
103         }
104 
105         return true;
106     }
107 
108     /**
109      * Inits the parser with the content of a tag file.
110      * @param tagFileContent the lines of a tag file.
111      * @return <code>true</code> if success, <code>false</code> if failure.
112      */
init(String[] tagFileContent)113     public boolean init(String[] tagFileContent) {
114         for (String line : tagFileContent) {
115             processTagLine(line);
116         }
117         return true;
118     }
119 
120     /**
121      * Inits the parser with a specified event-log-tags file.
122      * @param filePath
123      * @return <code>true</code> if success, <code>false</code> if failure.
124      */
init(String filePath)125     public boolean init(String filePath)  {
126         try {
127             BufferedReader reader = new BufferedReader(new FileReader(filePath));
128 
129             String line = null;
130             do {
131                 line = reader.readLine();
132                 if (line != null) {
133                     processTagLine(line);
134                 }
135             } while (line != null);
136 
137             return true;
138         } catch (IOException e) {
139             return false;
140         }
141     }
142 
143     /**
144      * Processes a line from the event-log-tags file.
145      * @param line the line to process
146      */
processTagLine(String line)147     private void processTagLine(String line) {
148         // ignore empty lines and comment lines
149         if (line.length() > 0 && line.charAt(0) != '#') {
150             Matcher m = PATTERN_TAG_WITH_DESC.matcher(line);
151             if (m.matches()) {
152                 try {
153                     int value = Integer.parseInt(m.group(1));
154                     String name = m.group(2);
155                     if (name != null && mTagMap.get(value) == null) {
156                         mTagMap.put(value, name);
157                     }
158 
159                     // special case for the GC tag. We ignore what is in the file,
160                     // and take what the custom GcEventContainer class tells us.
161                     // This is due to the event encoding several values on 2 longs.
162                     // @see GcEventContainer
163                     if (value == GcEventContainer.GC_EVENT_TAG) {
164                         mValueDescriptionMap.put(value,
165                             GcEventContainer.getValueDescriptions());
166                     } else {
167 
168                         String description = m.group(3);
169                         if (description != null && description.length() > 0) {
170                             EventValueDescription[] desc =
171                                 processDescription(description);
172 
173                             if (desc != null) {
174                                 mValueDescriptionMap.put(value, desc);
175                             }
176                         }
177                     }
178                 } catch (NumberFormatException e) {
179                     // failed to convert the number into a string. just ignore it.
180                 }
181             } else {
182                 m = PATTERN_SIMPLE_TAG.matcher(line);
183                 if (m.matches()) {
184                     int value = Integer.parseInt(m.group(1));
185                     String name = m.group(2);
186                     if (name != null && mTagMap.get(value) == null) {
187                         mTagMap.put(value, name);
188                     }
189                 }
190             }
191         }
192     }
193 
processDescription(String description)194     private EventValueDescription[] processDescription(String description) {
195         String[] descriptions = description.split("\\s*,\\s*"); //$NON-NLS-1$
196 
197         ArrayList<EventValueDescription> list = new ArrayList<EventValueDescription>();
198 
199         for (String desc : descriptions) {
200             Matcher m = PATTERN_DESCRIPTION.matcher(desc);
201             if (m.matches()) {
202                 try {
203                     String name = m.group(1);
204 
205                     String typeString = m.group(2);
206                     int typeValue = Integer.parseInt(typeString);
207                     EventValueType eventValueType = EventValueType.getEventValueType(typeValue);
208                     if (eventValueType == null) {
209                         // just ignore this description if the value is not recognized.
210                         // TODO: log the error.
211                     }
212 
213                     typeString = m.group(3);
214                     if (typeString != null && typeString.length() > 0) {
215                         //skip the |
216                         typeString = typeString.substring(1);
217 
218                         typeValue = Integer.parseInt(typeString);
219                         ValueType valueType = ValueType.getValueType(typeValue);
220 
221                         list.add(new EventValueDescription(name, eventValueType, valueType));
222                     } else {
223                         list.add(new EventValueDescription(name, eventValueType));
224                     }
225                 } catch (NumberFormatException nfe) {
226                     // just ignore this description if one number is malformed.
227                     // TODO: log the error.
228                 } catch (InvalidValueTypeException e) {
229                     // just ignore this description if data type and data unit don't match
230                     // TODO: log the error.
231                 }
232             } else {
233                 Log.e("EventLogParser",  //$NON-NLS-1$
234                     String.format("Can't parse %1$s", description));  //$NON-NLS-1$
235             }
236         }
237 
238         if (list.size() == 0) {
239             return null;
240         }
241 
242         return list.toArray(new EventValueDescription[list.size()]);
243 
244     }
245 
parse(LogEntry entry)246     public EventContainer parse(LogEntry entry) {
247         if (entry.len < 4) {
248             return null;
249         }
250 
251         int inOffset = 0;
252 
253         int tagValue = ArrayHelper.swap32bitFromArray(entry.data, inOffset);
254         inOffset += 4;
255 
256         String tag = mTagMap.get(tagValue);
257         if (tag == null) {
258             Log.e("EventLogParser", String.format("unknown tag number: %1$d", tagValue));
259         }
260 
261         ArrayList<Object> list = new ArrayList<Object>();
262         if (parseBinaryEvent(entry.data, inOffset, list) == -1) {
263             return null;
264         }
265 
266         Object data;
267         if (list.size() == 1) {
268             data = list.get(0);
269         } else{
270             data = list.toArray();
271         }
272 
273         EventContainer event = null;
274         if (tagValue == GcEventContainer.GC_EVENT_TAG) {
275             event = new GcEventContainer(entry, tagValue, data);
276         } else {
277             event = new EventContainer(entry, tagValue, data);
278         }
279 
280         return event;
281     }
282 
parse(String textLogLine)283     public EventContainer parse(String textLogLine) {
284         // line will look like
285         // 04-29 23:16:16.691 I/dvm_gc_info(  427): <data>
286         // where <data> is either
287         // [value1,value2...]
288         // or
289         // value
290         if (textLogLine.length() == 0) {
291             return null;
292         }
293 
294         // parse the header first
295         Matcher m = TEXT_LOG_LINE.matcher(textLogLine);
296         if (m.matches()) {
297             try {
298                 int month = Integer.parseInt(m.group(1));
299                 int day = Integer.parseInt(m.group(2));
300                 int hours = Integer.parseInt(m.group(3));
301                 int minutes = Integer.parseInt(m.group(4));
302                 int seconds = Integer.parseInt(m.group(5));
303                 int milliseconds = Integer.parseInt(m.group(6));
304 
305                 // convert into seconds since epoch and nano-seconds.
306                 Calendar cal = Calendar.getInstance();
307                 cal.set(cal.get(Calendar.YEAR), month-1, day, hours, minutes, seconds);
308                 int sec = (int)Math.floor(cal.getTimeInMillis()/1000);
309                 int nsec = milliseconds * 1000000;
310 
311                 String tag = m.group(7);
312 
313                 // get the numerical tag value
314                 int tagValue = -1;
315                 Set<Entry<Integer, String>> tagSet = mTagMap.entrySet();
316                 for (Entry<Integer, String> entry : tagSet) {
317                     if (tag.equals(entry.getValue())) {
318                         tagValue = entry.getKey();
319                         break;
320                     }
321                 }
322 
323                 if (tagValue == -1) {
324                     return null;
325                 }
326 
327                 int pid = Integer.parseInt(m.group(8));
328 
329                 Object data = parseTextData(m.group(9), tagValue);
330                 if (data == null) {
331                     return null;
332                 }
333 
334                 // now we can allocate and return the EventContainer
335                 EventContainer event = null;
336                 if (tagValue == GcEventContainer.GC_EVENT_TAG) {
337                     event = new GcEventContainer(tagValue, pid, -1 /* tid */, sec, nsec, data);
338                 } else {
339                     event = new EventContainer(tagValue, pid, -1 /* tid */, sec, nsec, data);
340                 }
341 
342                 return event;
343             } catch (NumberFormatException e) {
344                 return null;
345             }
346         }
347 
348         return null;
349     }
350 
getTagMap()351     public Map<Integer, String> getTagMap() {
352         return mTagMap;
353     }
354 
getEventInfoMap()355     public Map<Integer, EventValueDescription[]> getEventInfoMap() {
356         return mValueDescriptionMap;
357     }
358 
359     /**
360      * Recursively convert binary log data to printable form.
361      *
362      * This needs to be recursive because you can have lists of lists.
363      *
364      * If we run out of room, we stop processing immediately.  It's important
365      * for us to check for space on every output element to avoid producing
366      * garbled output.
367      *
368      * Returns the amount read on success, -1 on failure.
369      */
parseBinaryEvent(byte[] eventData, int dataOffset, ArrayList<Object> list)370     private static int parseBinaryEvent(byte[] eventData, int dataOffset, ArrayList<Object> list) {
371 
372         if (eventData.length - dataOffset < 1)
373             return -1;
374 
375         int offset = dataOffset;
376 
377         int type = eventData[offset++];
378 
379         //fprintf(stderr, "--- type=%d (rem len=%d)\n", type, eventDataLen);
380 
381         switch (type) {
382         case EVENT_TYPE_INT: { /* 32-bit signed int */
383                 int ival;
384 
385                 if (eventData.length - offset < 4)
386                     return -1;
387                 ival = ArrayHelper.swap32bitFromArray(eventData, offset);
388                 offset += 4;
389 
390                 list.add(new Integer(ival));
391             }
392             break;
393         case EVENT_TYPE_LONG: { /* 64-bit signed long */
394                 long lval;
395 
396                 if (eventData.length - offset < 8)
397                     return -1;
398                 lval = ArrayHelper.swap64bitFromArray(eventData, offset);
399                 offset += 8;
400 
401                 list.add(new Long(lval));
402             }
403             break;
404         case EVENT_TYPE_STRING: { /* UTF-8 chars, not NULL-terminated */
405                 int strLen;
406 
407                 if (eventData.length - offset < 4)
408                     return -1;
409                 strLen = ArrayHelper.swap32bitFromArray(eventData, offset);
410                 offset += 4;
411 
412                 if (eventData.length - offset < strLen)
413                     return -1;
414 
415                 // get the string
416                 try {
417                     String str = new String(eventData, offset, strLen, "UTF-8"); //$NON-NLS-1$
418                     list.add(str);
419                 } catch (UnsupportedEncodingException e) {
420                 }
421                 offset += strLen;
422                 break;
423             }
424         case EVENT_TYPE_LIST: { /* N items, all different types */
425 
426                 if (eventData.length - offset < 1)
427                     return -1;
428 
429                 int count = eventData[offset++];
430 
431                 // make a new temp list
432                 ArrayList<Object> subList = new ArrayList<Object>();
433                 for (int i = 0; i < count; i++) {
434                     int result = parseBinaryEvent(eventData, offset, subList);
435                     if (result == -1) {
436                         return result;
437                     }
438 
439                     offset += result;
440                 }
441 
442                 list.add(subList.toArray());
443             }
444             break;
445         default:
446             Log.e("EventLogParser",  //$NON-NLS-1$
447                     String.format("Unknown binary event type %1$d", type));  //$NON-NLS-1$
448             return -1;
449         }
450 
451         return offset - dataOffset;
452     }
453 
parseTextData(String data, int tagValue)454     private Object parseTextData(String data, int tagValue) {
455         // first, get the description of what we're supposed to parse
456         EventValueDescription[] desc = mValueDescriptionMap.get(tagValue);
457 
458         if (desc == null) {
459             // TODO parse and create string values.
460             return null;
461         }
462 
463         if (desc.length == 1) {
464             return getObjectFromString(data, desc[0].getEventValueType());
465         } else if (data.startsWith("[") && data.endsWith("]")) {
466             data = data.substring(1, data.length() - 1);
467 
468             // get each individual values as String
469             String[] values = data.split(",");
470 
471             if (tagValue == GcEventContainer.GC_EVENT_TAG) {
472                 // special case for the GC event!
473                 Object[] objects = new Object[2];
474 
475                 objects[0] = getObjectFromString(values[0], EventValueType.LONG);
476                 objects[1] = getObjectFromString(values[1], EventValueType.LONG);
477 
478                 return objects;
479             } else {
480                 // must be the same number as the number of descriptors.
481                 if (values.length != desc.length) {
482                     return null;
483                 }
484 
485                 Object[] objects = new Object[values.length];
486 
487                 for (int i = 0 ; i < desc.length ; i++) {
488                     Object obj = getObjectFromString(values[i], desc[i].getEventValueType());
489                     if (obj == null) {
490                         return null;
491                     }
492                     objects[i] = obj;
493                 }
494 
495                 return objects;
496             }
497         }
498 
499         return null;
500     }
501 
502 
getObjectFromString(String value, EventValueType type)503     private Object getObjectFromString(String value, EventValueType type) {
504         try {
505             switch (type) {
506                 case INT:
507                     return Integer.valueOf(value);
508                 case LONG:
509                     return Long.valueOf(value);
510                 case STRING:
511                     return value;
512             }
513         } catch (NumberFormatException e) {
514             // do nothing, we'll return null.
515         }
516 
517         return null;
518     }
519 
520     /**
521      * Recreates the event-log-tags at the specified file path.
522      * @param filePath the file path to write the file.
523      * @throws IOException
524      */
saveTags(String filePath)525     public void saveTags(String filePath) throws IOException {
526         File destFile = new File(filePath);
527         destFile.createNewFile();
528         FileOutputStream fos = null;
529 
530         try {
531 
532             fos = new FileOutputStream(destFile);
533 
534             for (Integer key : mTagMap.keySet()) {
535                 // get the tag name
536                 String tagName = mTagMap.get(key);
537 
538                 // get the value descriptions
539                 EventValueDescription[] descriptors = mValueDescriptionMap.get(key);
540 
541                 String line = null;
542                 if (descriptors != null) {
543                     StringBuilder sb = new StringBuilder();
544                     sb.append(String.format("%1$d %2$s", key, tagName)); //$NON-NLS-1$
545                     boolean first = true;
546                     for (EventValueDescription evd : descriptors) {
547                         if (first) {
548                             sb.append(" ("); //$NON-NLS-1$
549                             first = false;
550                         } else {
551                             sb.append(",("); //$NON-NLS-1$
552                         }
553                         sb.append(evd.getName());
554                         sb.append("|"); //$NON-NLS-1$
555                         sb.append(evd.getEventValueType().getValue());
556                         sb.append("|"); //$NON-NLS-1$
557                         sb.append(evd.getValueType().getValue());
558                         sb.append("|)"); //$NON-NLS-1$
559                     }
560                     sb.append("\n"); //$NON-NLS-1$
561 
562                     line = sb.toString();
563                 } else {
564                     line = String.format("%1$d %2$s\n", key, tagName); //$NON-NLS-1$
565                 }
566 
567                 byte[] buffer = line.getBytes();
568                 fos.write(buffer);
569             }
570         } finally {
571             if (fos != null) {
572                 fos.close();
573             }
574         }
575     }
576 
577 
578 }
579