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 */
16package android.metrics;
17
18import android.annotation.SystemApi;
19import android.content.ComponentName;
20import android.util.Log;
21import android.util.SparseArray;
22
23import com.android.internal.annotations.VisibleForTesting;
24import 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
34public 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.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     */
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