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