Home | History | Annotate | Download | only in metrics
      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.RESERVED_FOR_LOGBUILDER_LATENCY_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