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