450 lines
13 KiB
Java
450 lines
13 KiB
Java
![]() |
/*
|
||
|
* Copyright (C) 2017 The Android Open Source Project
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*/
|
||
|
package android.metrics;
|
||
|
|
||
|
import android.annotation.SystemApi;
|
||
|
import android.content.ComponentName;
|
||
|
import android.util.Log;
|
||
|
import android.util.SparseArray;
|
||
|
|
||
|
import com.android.internal.annotations.VisibleForTesting;
|
||
|
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
|
||
|
|
||
|
import java.util.Arrays;
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Helper class to assemble more complex logs.
|
||
|
*
|
||
|
* @hide
|
||
|
*/
|
||
|
@SystemApi
|
||
|
@android.ravenwood.annotation.RavenwoodKeepWholeClass
|
||
|
public class LogMaker {
|
||
|
private static final String TAG = "LogBuilder";
|
||
|
|
||
|
/**
|
||
|
* Min required eventlog line length.
|
||
|
* See: android/util/cts/EventLogTest.java
|
||
|
* Size limits enforced here are intended only as a precaution;
|
||
|
* your logs may be truncated earlier. Please log responsibly.
|
||
|
*
|
||
|
* @hide
|
||
|
*/
|
||
|
@VisibleForTesting
|
||
|
public static final int MAX_SERIALIZED_SIZE = 4000;
|
||
|
|
||
|
private SparseArray<Object> entries = new SparseArray();
|
||
|
|
||
|
/** @param category for the new LogMaker. */
|
||
|
public LogMaker(int category) {
|
||
|
setCategory(category);
|
||
|
}
|
||
|
|
||
|
/* Deserialize from the eventlog */
|
||
|
public LogMaker(Object[] items) {
|
||
|
if (items != null) {
|
||
|
deserialize(items);
|
||
|
} else {
|
||
|
setCategory(MetricsEvent.VIEW_UNKNOWN);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** @param category to replace the existing setting. */
|
||
|
public LogMaker setCategory(int category) {
|
||
|
entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_CATEGORY, category);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/** Set the category to unknown. */
|
||
|
public LogMaker clearCategory() {
|
||
|
entries.remove(MetricsEvent.RESERVED_FOR_LOGBUILDER_CATEGORY);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/** @param type to replace the existing setting. */
|
||
|
public LogMaker setType(int type) {
|
||
|
entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_TYPE, type);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/** Set the type to unknown. */
|
||
|
public LogMaker clearType() {
|
||
|
entries.remove(MetricsEvent.RESERVED_FOR_LOGBUILDER_TYPE);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/** @param subtype to replace the existing setting. */
|
||
|
public LogMaker setSubtype(int subtype) {
|
||
|
entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_SUBTYPE, subtype);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/** Set the subtype to 0. */
|
||
|
public LogMaker clearSubtype() {
|
||
|
entries.remove(MetricsEvent.RESERVED_FOR_LOGBUILDER_SUBTYPE);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set event latency.
|
||
|
*
|
||
|
* @hide // TODO Expose in the future? Too late for O.
|
||
|
*/
|
||
|
public LogMaker setLatency(long milliseconds) {
|
||
|
entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_LATENCY_MILLIS, milliseconds);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This will be set by the system when the log is persisted.
|
||
|
* Client-supplied values will be ignored.
|
||
|
*
|
||
|
* @param timestamp to replace the existing settings.
|
||
|
* @hide
|
||
|
*/
|
||
|
public LogMaker setTimestamp(long timestamp) {
|
||
|
entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_TIMESTAMP, timestamp);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/** Remove the timestamp property.
|
||
|
* @hide
|
||
|
*/
|
||
|
public LogMaker clearTimestamp() {
|
||
|
entries.remove(MetricsEvent.RESERVED_FOR_LOGBUILDER_TIMESTAMP);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/** @param packageName to replace the existing setting. */
|
||
|
public LogMaker setPackageName(String packageName) {
|
||
|
entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_PACKAGENAME, packageName);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param component to replace the existing setting.
|
||
|
* @hide
|
||
|
*/
|
||
|
public LogMaker setComponentName(ComponentName component) {
|
||
|
entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_PACKAGENAME, component.getPackageName());
|
||
|
entries.put(MetricsEvent.FIELD_CLASS_NAME, component.getClassName());
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/** Remove the package name property. */
|
||
|
public LogMaker clearPackageName() {
|
||
|
entries.remove(MetricsEvent.RESERVED_FOR_LOGBUILDER_PACKAGENAME);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This will be set by the system when the log is persisted.
|
||
|
* Client-supplied values will be ignored.
|
||
|
*
|
||
|
* @param pid to replace the existing setting.
|
||
|
* @hide
|
||
|
*/
|
||
|
public LogMaker setProcessId(int pid) {
|
||
|
entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_PID, pid);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/** Remove the process ID property.
|
||
|
* @hide
|
||
|
*/
|
||
|
public LogMaker clearProcessId() {
|
||
|
entries.remove(MetricsEvent.RESERVED_FOR_LOGBUILDER_PID);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This will be set by the system when the log is persisted.
|
||
|
* Client-supplied values will be ignored.
|
||
|
*
|
||
|
* @param uid to replace the existing setting.
|
||
|
* @hide
|
||
|
*/
|
||
|
public LogMaker setUid(int uid) {
|
||
|
entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_UID, uid);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Remove the UID property.
|
||
|
* @hide
|
||
|
*/
|
||
|
public LogMaker clearUid() {
|
||
|
entries.remove(MetricsEvent.RESERVED_FOR_LOGBUILDER_UID);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The name of the counter or histogram.
|
||
|
* Only useful for counter or histogram category objects.
|
||
|
* @param name to replace the existing setting.
|
||
|
* @hide
|
||
|
*/
|
||
|
public LogMaker setCounterName(String name) {
|
||
|
entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_NAME, name);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The bucket label, expressed as an integer.
|
||
|
* Only useful for histogram category objects.
|
||
|
* @param bucket to replace the existing setting.
|
||
|
* @hide
|
||
|
*/
|
||
|
public LogMaker setCounterBucket(int bucket) {
|
||
|
entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_BUCKET, bucket);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The bucket label, expressed as a long integer.
|
||
|
* Only useful for histogram category objects.
|
||
|
* @param bucket to replace the existing setting.
|
||
|
* @hide
|
||
|
*/
|
||
|
public LogMaker setCounterBucket(long bucket) {
|
||
|
entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_BUCKET, bucket);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The value to increment the counter or bucket by.
|
||
|
* Only useful for counter and histogram category objects.
|
||
|
* @param value to replace the existing setting.
|
||
|
* @hide
|
||
|
*/
|
||
|
public LogMaker setCounterValue(int value) {
|
||
|
entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_VALUE, value);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param tag From your MetricsEvent enum.
|
||
|
* @param value One of Integer, Long, Float, or String; or null to clear the tag.
|
||
|
* @return modified LogMaker
|
||
|
*/
|
||
|
public LogMaker addTaggedData(int tag, Object value) {
|
||
|
if (value == null) {
|
||
|
return clearTaggedData(tag);
|
||
|
}
|
||
|
if (!isValidValue(value)) {
|
||
|
throw new IllegalArgumentException(
|
||
|
"Value must be loggable type - int, long, float, String");
|
||
|
}
|
||
|
if (value.toString().getBytes().length > MAX_SERIALIZED_SIZE) {
|
||
|
Log.i(TAG, "Log value too long, omitted: " + value.toString());
|
||
|
} else {
|
||
|
entries.put(tag, value);
|
||
|
}
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Remove a value from the LogMaker.
|
||
|
*
|
||
|
* @param tag From your MetricsEvent enum.
|
||
|
* @return modified LogMaker
|
||
|
*/
|
||
|
public LogMaker clearTaggedData(int tag) {
|
||
|
entries.delete(tag);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return true if this object may be added to a LogMaker as a value.
|
||
|
*/
|
||
|
public boolean isValidValue(Object value) {
|
||
|
return value instanceof Integer ||
|
||
|
value instanceof String ||
|
||
|
value instanceof Long ||
|
||
|
value instanceof Float;
|
||
|
}
|
||
|
|
||
|
public Object getTaggedData(int tag) {
|
||
|
return entries.get(tag);
|
||
|
}
|
||
|
|
||
|
/** @return the category of the log, or unknown. */
|
||
|
public int getCategory() {
|
||
|
Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_CATEGORY);
|
||
|
if (obj instanceof Integer) {
|
||
|
return (Integer) obj;
|
||
|
} else {
|
||
|
return MetricsEvent.VIEW_UNKNOWN;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** @return the type of the log, or unknwon. */
|
||
|
public int getType() {
|
||
|
Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_TYPE);
|
||
|
if (obj instanceof Integer) {
|
||
|
return (Integer) obj;
|
||
|
} else {
|
||
|
return MetricsEvent.TYPE_UNKNOWN;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** @return the subtype of the log, or 0. */
|
||
|
public int getSubtype() {
|
||
|
Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_SUBTYPE);
|
||
|
if (obj instanceof Integer) {
|
||
|
return (Integer) obj;
|
||
|
} else {
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** @return the timestamp of the log.or 0 */
|
||
|
public long getTimestamp() {
|
||
|
Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_TIMESTAMP);
|
||
|
if (obj instanceof Long) {
|
||
|
return (Long) obj;
|
||
|
} else {
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** @return the package name of the log, or null. */
|
||
|
public String getPackageName() {
|
||
|
Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_PACKAGENAME);
|
||
|
if (obj instanceof String) {
|
||
|
return (String) obj;
|
||
|
} else {
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** @return the process ID of the log, or -1. */
|
||
|
public int getProcessId() {
|
||
|
Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_PID);
|
||
|
if (obj instanceof Integer) {
|
||
|
return (Integer) obj;
|
||
|
} else {
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** @return the UID of the log, or -1. */
|
||
|
public int getUid() {
|
||
|
Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_UID);
|
||
|
if (obj instanceof Integer) {
|
||
|
return (Integer) obj;
|
||
|
} else {
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** @return the name of the counter, or null. */
|
||
|
public String getCounterName() {
|
||
|
Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_NAME);
|
||
|
if (obj instanceof String) {
|
||
|
return (String) obj;
|
||
|
} else {
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** @return the bucket label of the histogram\, or 0. */
|
||
|
public long getCounterBucket() {
|
||
|
Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_BUCKET);
|
||
|
if (obj instanceof Number) {
|
||
|
return ((Number) obj).longValue();
|
||
|
} else {
|
||
|
return 0L;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** @return true if the bucket label was specified as a long integer. */
|
||
|
public boolean isLongCounterBucket() {
|
||
|
Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_BUCKET);
|
||
|
return obj instanceof Long;
|
||
|
}
|
||
|
|
||
|
/** @return the increment value of the counter, or 0. */
|
||
|
public int getCounterValue() {
|
||
|
Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_VALUE);
|
||
|
if (obj instanceof Integer) {
|
||
|
return (Integer) obj;
|
||
|
} else {
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return a representation of the log suitable for EventLog.
|
||
|
*/
|
||
|
public Object[] serialize() {
|
||
|
Object[] out = new Object[entries.size() * 2];
|
||
|
for (int i = 0; i < entries.size(); i++) {
|
||
|
out[i * 2] = entries.keyAt(i);
|
||
|
out[i * 2 + 1] = entries.valueAt(i);
|
||
|
}
|
||
|
int size = Arrays.toString(out).getBytes().length;
|
||
|
if (size > MAX_SERIALIZED_SIZE) {
|
||
|
Log.i(TAG, "Log line too long, did not emit: " + size + " bytes.");
|
||
|
throw new RuntimeException();
|
||
|
}
|
||
|
return out;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Reconstitute an object from the output of {@link #serialize()}.
|
||
|
*/
|
||
|
public void deserialize(Object[] items) {
|
||
|
int i = 0;
|
||
|
while (items != null && i < items.length) {
|
||
|
Object key = items[i++];
|
||
|
Object value = i < items.length ? items[i++] : null;
|
||
|
if (key instanceof Integer) {
|
||
|
entries.put((Integer) key, value);
|
||
|
} else {
|
||
|
Log.i(TAG, "Invalid key " + (key == null ? "null" : key.toString()));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param that the object to compare to.
|
||
|
* @return true if values in that equal values in this, for tags that exist in this.
|
||
|
*/
|
||
|
public boolean isSubsetOf(LogMaker that) {
|
||
|
if (that == null) {
|
||
|
return false;
|
||
|
}
|
||
|
for (int i = 0; i < entries.size(); i++) {
|
||
|
int key = this.entries.keyAt(i);
|
||
|
Object thisValue = this.entries.valueAt(i);
|
||
|
Object thatValue = that.entries.get(key);
|
||
|
if ((thisValue == null && thatValue != null) || !thisValue.equals(thatValue))
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return entries containing key value pairs.
|
||
|
* @hide
|
||
|
*/
|
||
|
public SparseArray<Object> getEntries() {
|
||
|
return entries;
|
||
|
}
|
||
|
}
|