1 /* 2 * Copyright (C) 2017 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 package android.metrics; 17 18 import android.annotation.SystemApi; 19 import android.annotation.TestApi; 20 import android.content.ComponentName; 21 import android.util.Log; 22 import android.util.SparseArray; 23 24 import com.android.internal.annotations.VisibleForTesting; 25 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 26 27 28 29 /** 30 * Helper class to assemble more complex logs. 31 * 32 * @hide 33 */ 34 @SystemApi 35 @TestApi 36 public class LogMaker { 37 private static final String TAG = "LogBuilder"; 38 39 /** 40 * Min required eventlog line length. 41 * See: android/util/cts/EventLogTest.java 42 * Size limits enforced here are intended only as a precaution; 43 * your logs may be truncated earlier. Please log responsibly. 44 * 45 * @hide 46 */ 47 @VisibleForTesting 48 public static final int MAX_SERIALIZED_SIZE = 4000; 49 50 private SparseArray<Object> entries = new SparseArray(); 51 52 /** @param category for the new LogMaker. */ LogMaker(int category)53 public LogMaker(int category) { 54 setCategory(category); 55 } 56 57 /* Deserialize from the eventlog */ LogMaker(Object[] items)58 public LogMaker(Object[] items) { 59 if (items != null) { 60 deserialize(items); 61 } else { 62 setCategory(MetricsEvent.VIEW_UNKNOWN); 63 } 64 } 65 66 /** @param category to replace the existing setting. */ setCategory(int category)67 public LogMaker setCategory(int category) { 68 entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_CATEGORY, category); 69 return this; 70 } 71 72 /** Set the category to unknown. */ clearCategory()73 public LogMaker clearCategory() { 74 entries.remove(MetricsEvent.RESERVED_FOR_LOGBUILDER_CATEGORY); 75 return this; 76 } 77 78 /** @param type to replace the existing setting. */ setType(int type)79 public LogMaker setType(int type) { 80 entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_TYPE, type); 81 return this; 82 } 83 84 /** Set the type to unknown. */ clearType()85 public LogMaker clearType() { 86 entries.remove(MetricsEvent.RESERVED_FOR_LOGBUILDER_TYPE); 87 return this; 88 } 89 90 /** @param subtype to replace the existing setting. */ setSubtype(int subtype)91 public LogMaker setSubtype(int subtype) { 92 entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_SUBTYPE, subtype); 93 return this; 94 } 95 96 /** Set the subtype to 0. */ clearSubtype()97 public LogMaker clearSubtype() { 98 entries.remove(MetricsEvent.RESERVED_FOR_LOGBUILDER_SUBTYPE); 99 return this; 100 } 101 102 /** 103 * Set event latency. 104 * 105 * @hide // TODO Expose in the future? Too late for O. 106 */ setLatency(long milliseconds)107 public LogMaker setLatency(long milliseconds) { 108 entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_LATENCY_MILLIS, milliseconds); 109 return this; 110 } 111 112 /** 113 * This will be set by the system when the log is persisted. 114 * Client-supplied values will be ignored. 115 * 116 * @param timestamp to replace the existing settings. 117 * @hide 118 */ setTimestamp(long timestamp)119 public LogMaker setTimestamp(long timestamp) { 120 entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_TIMESTAMP, timestamp); 121 return this; 122 } 123 124 /** Remove the timestamp property. 125 * @hide 126 */ clearTimestamp()127 public LogMaker clearTimestamp() { 128 entries.remove(MetricsEvent.RESERVED_FOR_LOGBUILDER_TIMESTAMP); 129 return this; 130 } 131 132 /** @param packageName to replace the existing setting. */ setPackageName(String packageName)133 public LogMaker setPackageName(String packageName) { 134 entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_PACKAGENAME, packageName); 135 return this; 136 } 137 138 /** 139 * @param component to replace the existing setting. 140 * @hide 141 */ setComponentName(ComponentName component)142 public LogMaker setComponentName(ComponentName component) { 143 entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_PACKAGENAME, component.getPackageName()); 144 entries.put(MetricsEvent.FIELD_CLASS_NAME, component.getClassName()); 145 return this; 146 } 147 148 /** Remove the package name property. */ clearPackageName()149 public LogMaker clearPackageName() { 150 entries.remove(MetricsEvent.RESERVED_FOR_LOGBUILDER_PACKAGENAME); 151 return this; 152 } 153 154 /** 155 * This will be set by the system when the log is persisted. 156 * Client-supplied values will be ignored. 157 * 158 * @param pid to replace the existing setting. 159 * @hide 160 */ setProcessId(int pid)161 public LogMaker setProcessId(int pid) { 162 entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_PID, pid); 163 return this; 164 } 165 166 /** Remove the process ID property. 167 * @hide 168 */ clearProcessId()169 public LogMaker clearProcessId() { 170 entries.remove(MetricsEvent.RESERVED_FOR_LOGBUILDER_PID); 171 return this; 172 } 173 174 /** 175 * This will be set by the system when the log is persisted. 176 * Client-supplied values will be ignored. 177 * 178 * @param uid to replace the existing setting. 179 * @hide 180 */ setUid(int uid)181 public LogMaker setUid(int uid) { 182 entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_UID, uid); 183 return this; 184 } 185 186 /** 187 * Remove the UID property. 188 * @hide 189 */ clearUid()190 public LogMaker clearUid() { 191 entries.remove(MetricsEvent.RESERVED_FOR_LOGBUILDER_UID); 192 return this; 193 } 194 195 /** 196 * The name of the counter or histogram. 197 * Only useful for counter or histogram category objects. 198 * @param name to replace the existing setting. 199 * @hide 200 */ setCounterName(String name)201 public LogMaker setCounterName(String name) { 202 entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_NAME, name); 203 return this; 204 } 205 206 /** 207 * The bucket label, expressed as an integer. 208 * Only useful for histogram category objects. 209 * @param bucket to replace the existing setting. 210 * @hide 211 */ setCounterBucket(int bucket)212 public LogMaker setCounterBucket(int bucket) { 213 entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_BUCKET, bucket); 214 return this; 215 } 216 217 /** 218 * The bucket label, expressed as a long integer. 219 * Only useful for histogram category objects. 220 * @param bucket to replace the existing setting. 221 * @hide 222 */ setCounterBucket(long bucket)223 public LogMaker setCounterBucket(long bucket) { 224 entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_BUCKET, bucket); 225 return this; 226 } 227 228 /** 229 * The value to increment the counter or bucket by. 230 * Only useful for counter and histogram category objects. 231 * @param value to replace the existing setting. 232 * @hide 233 */ setCounterValue(int value)234 public LogMaker setCounterValue(int value) { 235 entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_VALUE, value); 236 return this; 237 } 238 239 /** 240 * @param tag From your MetricsEvent enum. 241 * @param value One of Integer, Long, Float, or String; or null to clear the tag. 242 * @return modified LogMaker 243 */ addTaggedData(int tag, Object value)244 public LogMaker addTaggedData(int tag, Object value) { 245 if (value == null) { 246 return clearTaggedData(tag); 247 } 248 if (!isValidValue(value)) { 249 throw new IllegalArgumentException( 250 "Value must be loggable type - int, long, float, String"); 251 } 252 if (value.toString().getBytes().length > MAX_SERIALIZED_SIZE) { 253 Log.i(TAG, "Log value too long, omitted: " + value.toString()); 254 } else { 255 entries.put(tag, value); 256 } 257 return this; 258 } 259 260 /** 261 * Remove a value from the LogMaker. 262 * 263 * @param tag From your MetricsEvent enum. 264 * @return modified LogMaker 265 */ clearTaggedData(int tag)266 public LogMaker clearTaggedData(int tag) { 267 entries.delete(tag); 268 return this; 269 } 270 271 /** 272 * @return true if this object may be added to a LogMaker as a value. 273 */ isValidValue(Object value)274 public boolean isValidValue(Object value) { 275 return value instanceof Integer || 276 value instanceof String || 277 value instanceof Long || 278 value instanceof Float; 279 } 280 getTaggedData(int tag)281 public Object getTaggedData(int tag) { 282 return entries.get(tag); 283 } 284 285 /** @return the category of the log, or unknown. */ getCategory()286 public int getCategory() { 287 Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_CATEGORY); 288 if (obj instanceof Integer) { 289 return (Integer) obj; 290 } else { 291 return MetricsEvent.VIEW_UNKNOWN; 292 } 293 } 294 295 /** @return the type of the log, or unknwon. */ getType()296 public int getType() { 297 Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_TYPE); 298 if (obj instanceof Integer) { 299 return (Integer) obj; 300 } else { 301 return MetricsEvent.TYPE_UNKNOWN; 302 } 303 } 304 305 /** @return the subtype of the log, or 0. */ getSubtype()306 public int getSubtype() { 307 Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_SUBTYPE); 308 if (obj instanceof Integer) { 309 return (Integer) obj; 310 } else { 311 return 0; 312 } 313 } 314 315 /** @return the timestamp of the log.or 0 */ getTimestamp()316 public long getTimestamp() { 317 Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_TIMESTAMP); 318 if (obj instanceof Long) { 319 return (Long) obj; 320 } else { 321 return 0; 322 } 323 } 324 325 /** @return the package name of the log, or null. */ getPackageName()326 public String getPackageName() { 327 Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_PACKAGENAME); 328 if (obj instanceof String) { 329 return (String) obj; 330 } else { 331 return null; 332 } 333 } 334 335 /** @return the process ID of the log, or -1. */ getProcessId()336 public int getProcessId() { 337 Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_PID); 338 if (obj instanceof Integer) { 339 return (Integer) obj; 340 } else { 341 return -1; 342 } 343 } 344 345 /** @return the UID of the log, or -1. */ getUid()346 public int getUid() { 347 Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_UID); 348 if (obj instanceof Integer) { 349 return (Integer) obj; 350 } else { 351 return -1; 352 } 353 } 354 355 /** @return the name of the counter, or null. */ getCounterName()356 public String getCounterName() { 357 Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_NAME); 358 if (obj instanceof String) { 359 return (String) obj; 360 } else { 361 return null; 362 } 363 } 364 365 /** @return the bucket label of the histogram\, or 0. */ getCounterBucket()366 public long getCounterBucket() { 367 Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_BUCKET); 368 if (obj instanceof Number) { 369 return ((Number) obj).longValue(); 370 } else { 371 return 0L; 372 } 373 } 374 375 /** @return true if the bucket label was specified as a long integer. */ isLongCounterBucket()376 public boolean isLongCounterBucket() { 377 Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_BUCKET); 378 return obj instanceof Long; 379 } 380 381 /** @return the increment value of the counter, or 0. */ getCounterValue()382 public int getCounterValue() { 383 Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_VALUE); 384 if (obj instanceof Integer) { 385 return (Integer) obj; 386 } else { 387 return 0; 388 } 389 } 390 391 /** 392 * @return a representation of the log suitable for EventLog. 393 */ serialize()394 public Object[] serialize() { 395 Object[] out = new Object[entries.size() * 2]; 396 for (int i = 0; i < entries.size(); i++) { 397 out[i * 2] = entries.keyAt(i); 398 out[i * 2 + 1] = entries.valueAt(i); 399 } 400 int size = out.toString().getBytes().length; 401 if (size > MAX_SERIALIZED_SIZE) { 402 Log.i(TAG, "Log line too long, did not emit: " + size + " bytes."); 403 throw new RuntimeException(); 404 } 405 return out; 406 } 407 408 /** 409 * Reconstitute an object from the output of {@link #serialize()}. 410 */ deserialize(Object[] items)411 public void deserialize(Object[] items) { 412 int i = 0; 413 while (items != null && i < items.length) { 414 Object key = items[i++]; 415 Object value = i < items.length ? items[i++] : null; 416 if (key instanceof Integer) { 417 entries.put((Integer) key, value); 418 } else { 419 Log.i(TAG, "Invalid key " + (key == null ? "null" : key.toString())); 420 } 421 } 422 } 423 424 /** 425 * @param that the object to compare to. 426 * @return true if values in that equal values in this, for tags that exist in this. 427 */ 428 public boolean isSubsetOf(LogMaker that) { 429 if (that == null) { 430 return false; 431 } 432 for (int i = 0; i < entries.size(); i++) { 433 int key = this.entries.keyAt(i); 434 Object thisValue = this.entries.valueAt(i); 435 Object thatValue = that.entries.get(key); 436 if ((thisValue == null && thatValue != null) || !thisValue.equals(thatValue)) 437 return false; 438 } 439 return true; 440 } 441 442 /** 443 * @return entries containing key value pairs. 444 * @hide 445 */ 446 public SparseArray<Object> getEntries() { 447 return entries; 448 } 449 } 450