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