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 17 package android.util; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.SystemApi; 22 import android.compat.annotation.UnsupportedAppUsage; 23 import android.os.Build; 24 25 import java.io.BufferedReader; 26 import java.io.FileReader; 27 import java.io.IOException; 28 import java.io.UnsupportedEncodingException; 29 import java.nio.BufferUnderflowException; 30 import java.nio.ByteBuffer; 31 import java.nio.ByteOrder; 32 import java.util.Arrays; 33 import java.util.Collection; 34 import java.util.HashMap; 35 import java.util.regex.Matcher; 36 import java.util.regex.Pattern; 37 38 /** 39 * Access to the system diagnostic event record. System diagnostic events are 40 * used to record certain system-level events (such as garbage collection, 41 * activity manager state, system watchdogs, and other low level activity), 42 * which may be automatically collected and analyzed during system development. 43 * 44 * <p>This is <b>not</b> the main "logcat" debugging log ({@link android.util.Log})! 45 * These diagnostic events are for system integrators, not application authors. 46 * 47 * <p>Events use integer tag codes corresponding to /system/etc/event-log-tags. 48 * They carry a payload of one or more int, long, or String values. The 49 * event-log-tags file defines the payload contents for each type code. 50 */ 51 @android.ravenwood.annotation.RavenwoodKeepWholeClass 52 @android.ravenwood.annotation.RavenwoodNativeSubstitutionClass( 53 "com.android.platform.test.ravenwood.nativesubstitution.EventLog_host") 54 public class EventLog { EventLog()55 /** @hide */ public EventLog() {} 56 57 private static final String TAG = "EventLog"; 58 59 private static final String TAGS_FILE = "/system/etc/event-log-tags"; 60 private static final String COMMENT_PATTERN = "^\\s*(#.*)?$"; 61 private static final String TAG_PATTERN = "^\\s*(\\d+)\\s+(\\w+)\\s*(\\(.*\\))?\\s*$"; 62 private static HashMap<String, Integer> sTagCodes = null; 63 private static HashMap<Integer, String> sTagNames = null; 64 65 /** A previously logged event read from the logs. Instances are thread safe. */ 66 public static final class Event { 67 private final ByteBuffer mBuffer; 68 private Exception mLastWtf; 69 70 // Layout of event log entry received from Android logger. 71 // see system/logging/liblog/include/log/log_read.h 72 private static final int LENGTH_OFFSET = 0; 73 private static final int HEADER_SIZE_OFFSET = 2; 74 private static final int PROCESS_OFFSET = 4; 75 private static final int THREAD_OFFSET = 8; 76 private static final int SECONDS_OFFSET = 12; 77 private static final int NANOSECONDS_OFFSET = 16; 78 private static final int UID_OFFSET = 24; 79 80 // Layout for event log v1 format, v2 and v3 use HEADER_SIZE_OFFSET 81 private static final int V1_PAYLOAD_START = 20; 82 private static final int TAG_LENGTH = 4; 83 84 // Value types 85 private static final byte INT_TYPE = 0; 86 private static final byte LONG_TYPE = 1; 87 private static final byte STRING_TYPE = 2; 88 private static final byte LIST_TYPE = 3; 89 private static final byte FLOAT_TYPE = 4; 90 91 /** @param data containing event, read from the system */ 92 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) Event(byte[] data)93 /*package*/ Event(byte[] data) { 94 mBuffer = ByteBuffer.wrap(data); 95 mBuffer.order(ByteOrder.nativeOrder()); 96 } 97 98 /** @return the process ID which wrote the log entry */ getProcessId()99 public int getProcessId() { 100 return mBuffer.getInt(PROCESS_OFFSET); 101 } 102 103 /** 104 * @return the UID which wrote the log entry 105 * @hide 106 */ 107 @SystemApi getUid()108 public int getUid() { 109 try { 110 return mBuffer.getInt(UID_OFFSET); 111 } catch (IndexOutOfBoundsException e) { 112 // buffer won't contain the UID if the caller doesn't have permission. 113 return -1; 114 } 115 } 116 117 /** @return the thread ID which wrote the log entry */ getThreadId()118 public int getThreadId() { 119 return mBuffer.getInt(THREAD_OFFSET); 120 } 121 122 /** @return the wall clock time when the entry was written */ getTimeNanos()123 public long getTimeNanos() { 124 return mBuffer.getInt(SECONDS_OFFSET) * 1000000000l 125 + mBuffer.getInt(NANOSECONDS_OFFSET); 126 } 127 128 /** @return the type tag code of the entry */ getTag()129 public int getTag() { 130 return mBuffer.getInt(getHeaderSize()); 131 } 132 getHeaderSize()133 private int getHeaderSize() { 134 int length = mBuffer.getShort(HEADER_SIZE_OFFSET); 135 if (length != 0) { 136 return length; 137 } 138 return V1_PAYLOAD_START; 139 } 140 /** @return one of Integer, Long, Float, String, null, or Object[] of same. */ getData()141 public synchronized Object getData() { 142 try { 143 int offset = getHeaderSize(); 144 mBuffer.limit(offset + mBuffer.getShort(LENGTH_OFFSET)); 145 if ((offset + TAG_LENGTH) >= mBuffer.limit()) { 146 // no payload 147 return null; 148 } 149 mBuffer.position(offset + TAG_LENGTH); // Just after the tag. 150 return decodeObject(); 151 } catch (IllegalArgumentException e) { 152 Log.wtf(TAG, "Illegal entry payload: tag=" + getTag(), e); 153 mLastWtf = e; 154 return null; 155 } catch (BufferUnderflowException e) { 156 Log.wtf(TAG, "Truncated entry payload: tag=" + getTag(), e); 157 mLastWtf = e; 158 return null; 159 } 160 } 161 162 /** 163 * Construct a new EventLog object from the current object, copying all log metadata 164 * but replacing the actual payload with the content provided. 165 * @hide 166 */ withNewData(@ullable Object object)167 public Event withNewData(@Nullable Object object) { 168 byte[] payload = encodeObject(object); 169 if (payload.length > 65535 - TAG_LENGTH) { 170 throw new IllegalArgumentException("Payload too long"); 171 } 172 int headerLength = getHeaderSize(); 173 byte[] newBytes = new byte[headerLength + TAG_LENGTH + payload.length]; 174 // Copy header (including the 4 bytes of tag integer at the beginning of payload) 175 System.arraycopy(mBuffer.array(), 0, newBytes, 0, headerLength + TAG_LENGTH); 176 // Fill in encoded objects 177 System.arraycopy(payload, 0, newBytes, headerLength + TAG_LENGTH, payload.length); 178 Event result = new Event(newBytes); 179 // Patch payload length in header 180 result.mBuffer.putShort(LENGTH_OFFSET, (short) (payload.length + TAG_LENGTH)); 181 return result; 182 } 183 184 /** @return the loggable item at the current position in mBuffer. */ decodeObject()185 private Object decodeObject() { 186 byte type = mBuffer.get(); 187 switch (type) { 188 case INT_TYPE: 189 return mBuffer.getInt(); 190 191 case LONG_TYPE: 192 return mBuffer.getLong(); 193 194 case FLOAT_TYPE: 195 return mBuffer.getFloat(); 196 197 case STRING_TYPE: 198 try { 199 int length = mBuffer.getInt(); 200 int start = mBuffer.position(); 201 mBuffer.position(start + length); 202 return new String(mBuffer.array(), start, length, "UTF-8"); 203 } catch (UnsupportedEncodingException e) { 204 Log.wtf(TAG, "UTF-8 is not supported", e); 205 mLastWtf = e; 206 return null; 207 } 208 209 case LIST_TYPE: 210 int length = mBuffer.get(); 211 if (length < 0) length += 256; // treat as signed byte 212 Object[] array = new Object[length]; 213 for (int i = 0; i < length; ++i) array[i] = decodeObject(); 214 return array; 215 216 default: 217 throw new IllegalArgumentException("Unknown entry type: " + type); 218 } 219 } 220 encodeObject(@ullable Object object)221 private static @NonNull byte[] encodeObject(@Nullable Object object) { 222 if (object == null) { 223 return new byte[0]; 224 } 225 if (object instanceof Integer) { 226 return ByteBuffer.allocate(1 + 4) 227 .order(ByteOrder.nativeOrder()) 228 .put(INT_TYPE) 229 .putInt((Integer) object) 230 .array(); 231 } else if (object instanceof Long) { 232 return ByteBuffer.allocate(1 + 8) 233 .order(ByteOrder.nativeOrder()) 234 .put(LONG_TYPE) 235 .putLong((Long) object) 236 .array(); 237 } else if (object instanceof Float) { 238 return ByteBuffer.allocate(1 + 4) 239 .order(ByteOrder.nativeOrder()) 240 .put(FLOAT_TYPE) 241 .putFloat((Float) object) 242 .array(); 243 } else if (object instanceof String) { 244 String string = (String) object; 245 byte[] bytes; 246 try { 247 bytes = string.getBytes("UTF-8"); 248 } catch (UnsupportedEncodingException e) { 249 bytes = new byte[0]; 250 } 251 return ByteBuffer.allocate(1 + 4 + bytes.length) 252 .order(ByteOrder.nativeOrder()) 253 .put(STRING_TYPE) 254 .putInt(bytes.length) 255 .put(bytes) 256 .array(); 257 } else if (object instanceof Object[]) { 258 Object[] objects = (Object[]) object; 259 if (objects.length > 255) { 260 throw new IllegalArgumentException("Object array too long"); 261 } 262 byte[][] bytes = new byte[objects.length][]; 263 int totalLength = 0; 264 for (int i = 0; i < objects.length; i++) { 265 bytes[i] = encodeObject(objects[i]); 266 totalLength += bytes[i].length; 267 } 268 ByteBuffer buffer = ByteBuffer.allocate(1 + 1 + totalLength) 269 .order(ByteOrder.nativeOrder()) 270 .put(LIST_TYPE) 271 .put((byte) objects.length); 272 for (int i = 0; i < objects.length; i++) { 273 buffer.put(bytes[i]); 274 } 275 return buffer.array(); 276 } else { 277 throw new IllegalArgumentException("Unknown object type " + object); 278 } 279 } 280 281 /** @hide */ fromBytes(byte[] data)282 public static Event fromBytes(byte[] data) { 283 return new Event(data); 284 } 285 286 /** @hide */ getBytes()287 public byte[] getBytes() { 288 byte[] bytes = mBuffer.array(); 289 return Arrays.copyOf(bytes, bytes.length); 290 } 291 292 /** 293 * Retreive the last WTF error generated by this object. 294 * @hide 295 */ 296 //VisibleForTesting getLastError()297 public Exception getLastError() { 298 return mLastWtf; 299 } 300 301 /** 302 * Clear the error state for this object. 303 * @hide 304 */ 305 //VisibleForTesting clearError()306 public void clearError() { 307 mLastWtf = null; 308 } 309 310 /** 311 * @hide 312 */ 313 @Override equals(@ullable Object o)314 public boolean equals(@Nullable Object o) { 315 // Not using ByteBuffer.equals since it takes buffer position into account and we 316 // always use absolute positions here. 317 if (this == o) return true; 318 if (o == null || getClass() != o.getClass()) return false; 319 Event other = (Event) o; 320 return Arrays.equals(mBuffer.array(), other.mBuffer.array()); 321 } 322 323 /** 324 * @hide 325 */ 326 @Override hashCode()327 public int hashCode() { 328 // Not using ByteBuffer.hashCode since it takes buffer position into account and we 329 // always use absolute positions here. 330 return Arrays.hashCode(mBuffer.array()); 331 } 332 } 333 334 // We assume that the native methods deal with any concurrency issues. 335 336 /** 337 * Record an event log message. 338 * @param tag The event type tag code 339 * @param value A value to log 340 * @return The number of bytes written 341 */ writeEvent(int tag, int value)342 public static native int writeEvent(int tag, int value); 343 344 /** 345 * Record an event log message. 346 * @param tag The event type tag code 347 * @param value A value to log 348 * @return The number of bytes written 349 */ writeEvent(int tag, long value)350 public static native int writeEvent(int tag, long value); 351 352 /** 353 * Record an event log message. 354 * @param tag The event type tag code 355 * @param value A value to log 356 * @return The number of bytes written 357 */ writeEvent(int tag, float value)358 public static native int writeEvent(int tag, float value); 359 360 /** 361 * Record an event log message. 362 * @param tag The event type tag code 363 * @param str A value to log 364 * @return The number of bytes written 365 */ writeEvent(int tag, String str)366 public static native int writeEvent(int tag, String str); 367 368 /** 369 * Record an event log message. 370 * @param tag The event type tag code 371 * @param list A list of values to log 372 * @return The number of bytes written 373 */ writeEvent(int tag, Object... list)374 public static native int writeEvent(int tag, Object... list); 375 376 /** 377 * Read events from the log, filtered by type. 378 * @param tags to search for 379 * @param output container to add events into 380 * @throws IOException if something goes wrong reading events 381 */ readEvents(int[] tags, Collection<Event> output)382 public static native void readEvents(int[] tags, Collection<Event> output) 383 throws IOException; 384 385 /** 386 * Read events from the log, filtered by type, blocking until logs are about to be overwritten. 387 * @param tags to search for 388 * @param timestamp timestamp allow logs before this time to be overwritten. 389 * @param output container to add events into 390 * @throws IOException if something goes wrong reading events 391 * @hide 392 */ 393 @SystemApi readEventsOnWrapping(int[] tags, long timestamp, Collection<Event> output)394 public static native void readEventsOnWrapping(int[] tags, long timestamp, 395 Collection<Event> output) 396 throws IOException; 397 398 /** 399 * Get the name associated with an event type tag code. 400 * @param tag code to look up 401 * @return the name of the tag, or null if no tag has that number 402 */ getTagName(int tag)403 public static String getTagName(int tag) { 404 readTagsFile(); 405 return sTagNames.get(tag); 406 } 407 408 /** 409 * Get the event type tag code associated with an event name. 410 * @param name of event to look up 411 * @return the tag code, or -1 if no tag has that name 412 */ getTagCode(String name)413 public static int getTagCode(String name) { 414 readTagsFile(); 415 Integer code = sTagCodes.get(name); 416 return code != null ? code : -1; 417 } 418 419 /** 420 * Read TAGS_FILE, populating sTagCodes and sTagNames, if not already done. 421 */ 422 @android.ravenwood.annotation.RavenwoodReplace readTagsFile()423 private static synchronized void readTagsFile() { 424 if (sTagCodes != null && sTagNames != null) return; 425 426 sTagCodes = new HashMap<String, Integer>(); 427 sTagNames = new HashMap<Integer, String>(); 428 429 Pattern comment = Pattern.compile(COMMENT_PATTERN); 430 Pattern tag = Pattern.compile(TAG_PATTERN); 431 BufferedReader reader = null; 432 String line; 433 434 try { 435 reader = new BufferedReader(new FileReader(TAGS_FILE), 256); 436 while ((line = reader.readLine()) != null) { 437 if (comment.matcher(line).matches()) continue; 438 439 Matcher m = tag.matcher(line); 440 if (!m.matches()) { 441 Log.wtf(TAG, "Bad entry in " + TAGS_FILE + ": " + line); 442 continue; 443 } 444 445 try { 446 int num = Integer.parseInt(m.group(1)); 447 String name = m.group(2); 448 registerTagLocked(num, name); 449 } catch (NumberFormatException e) { 450 Log.wtf(TAG, "Error in " + TAGS_FILE + ": " + line, e); 451 } 452 } 453 } catch (IOException e) { 454 Log.wtf(TAG, "Error reading " + TAGS_FILE, e); 455 // Leave the maps existing but unpopulated 456 } finally { 457 try { if (reader != null) reader.close(); } catch (IOException e) {} 458 } 459 } 460 registerTagLocked(int num, String name)461 private static void registerTagLocked(int num, String name) { 462 sTagCodes.put(name, num); 463 sTagNames.put(num, name); 464 } 465 readTagsFile$ravenwood()466 private static synchronized void readTagsFile$ravenwood() { 467 // TODO: restore parsing logic once we carry into runtime 468 sTagCodes = new HashMap<String, Integer>(); 469 sTagNames = new HashMap<Integer, String>(); 470 471 // Hard-code a few common tags 472 registerTagLocked(524288, "sysui_action"); 473 registerTagLocked(524290, "sysui_count"); 474 registerTagLocked(524291, "sysui_histogram"); 475 } 476 } 477