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