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