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 limits enforced here are intended only as a precaution;
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. */
LogMaker(int category)53     public LogMaker(int category) {
54         setCategory(category);
55     }
56 
57     /* Deserialize from the eventlog */
LogMaker(Object[] items)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. */
setCategory(int category)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. */
clearCategory()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. */
setType(int type)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. */
clearType()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. */
setSubtype(int subtype)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. */
clearSubtype()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      */
setLatency(long milliseconds)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      */
setTimestamp(long timestamp)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      */
clearTimestamp()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. */
setPackageName(String packageName)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      */
setComponentName(ComponentName component)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. */
clearPackageName()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      */
setProcessId(int pid)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      */
clearProcessId()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      */
setUid(int uid)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      */
clearUid()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      */
setCounterName(String name)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      */
setCounterBucket(int bucket)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      */
setCounterBucket(long bucket)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      */
setCounterValue(int value)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      */
addTaggedData(int tag, Object value)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      */
clearTaggedData(int tag)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      */
isValidValue(Object value)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 
getTaggedData(int tag)281     public Object getTaggedData(int tag) {
282         return entries.get(tag);
283     }
284 
285     /** @return the category of the log, or unknown. */
getCategory()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. */
getType()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. */
getSubtype()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 */
getTimestamp()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. */
getPackageName()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. */
getProcessId()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. */
getUid()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. */
getCounterName()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. */
getCounterBucket()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. */
isLongCounterBucket()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. */
getCounterValue()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      */
serialize()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      */
deserialize(Object[] items)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