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.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 checks enforced here are intended only as sanity checks;
     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. */
     53     public LogMaker(int category) {
     54         setCategory(category);
     55     }
     56 
     57     /* Deserialize from the eventlog */
     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. */
     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. */
     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. */
     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. */
     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. */
     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. */
     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      */
    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      */
    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      */
    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. */
    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      */
    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. */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    281     public Object getTaggedData(int tag) {
    282         return entries.get(tag);
    283     }
    284 
    285     /** @return the category of the log, or unknown. */
    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. */
    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. */
    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 */
    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. */
    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. */
    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. */
    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. */
    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. */
    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. */
    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. */
    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      */
    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      */
    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