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