summaryrefslogtreecommitdiff
path: root/android/metrics/LogMaker.java
blob: 2bb43bd36be3bd6caddcc731ccbe8f02a5553e30 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package android.metrics;

import android.annotation.SystemApi;
import android.content.ComponentName;
import android.util.Log;
import android.util.SparseArray;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;



/**
 * Helper class to assemble more complex logs.
 *
 * @hide
 */
@SystemApi
public class LogMaker {
    private static final String TAG = "LogBuilder";

    /**
     * Min required eventlog line length.
     * See: android/util/cts/EventLogTest.java
     * Size checks enforced here are intended only as sanity checks;
     * your logs may be truncated earlier. Please log responsibly.
     *
     * @hide
     */
    @VisibleForTesting
    public static final int MAX_SERIALIZED_SIZE = 4000;

    private SparseArray<Object> entries = new SparseArray();

    /** @param category for the new LogMaker. */
    public LogMaker(int category) {
        setCategory(category);
    }

    /* Deserialize from the eventlog */
    public LogMaker(Object[] items) {
        if (items != null) {
            deserialize(items);
        } else {
            setCategory(MetricsEvent.VIEW_UNKNOWN);
        }
    }

    /** @param category to replace the existing setting. */
    public LogMaker setCategory(int category) {
        entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_CATEGORY, category);
        return this;
    }

    /** Set the category to unknown. */
    public LogMaker clearCategory() {
        entries.remove(MetricsEvent.RESERVED_FOR_LOGBUILDER_CATEGORY);
        return this;
    }

    /** @param type to replace the existing setting. */
    public LogMaker setType(int type) {
        entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_TYPE, type);
        return this;
    }

    /** Set the type to unknown. */
    public LogMaker clearType() {
        entries.remove(MetricsEvent.RESERVED_FOR_LOGBUILDER_TYPE);
        return this;
    }

    /** @param subtype to replace the existing setting. */
    public LogMaker setSubtype(int subtype) {
        entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_SUBTYPE, subtype);
        return this;
    }

    /** Set the subtype to 0. */
    public LogMaker clearSubtype() {
        entries.remove(MetricsEvent.RESERVED_FOR_LOGBUILDER_SUBTYPE);
        return this;
    }

    /**
     * Set event latency.
     *
     * @hide // TODO Expose in the future?  Too late for O.
     */
    public LogMaker setLatency(long milliseconds) {
        entries.put(MetricsEvent.NOTIFICATION_SINCE_CREATE_MILLIS, milliseconds);
        return this;
    }

    /**
     * This will be set by the system when the log is persisted.
     * Client-supplied values will be ignored.
     *
     * @param timestamp to replace the existing settings.
     * @hide
     */
    public LogMaker setTimestamp(long timestamp) {
        entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_TIMESTAMP, timestamp);
        return this;
    }

    /** Remove the timestamp property.
     * @hide
     */
    public LogMaker clearTimestamp() {
        entries.remove(MetricsEvent.RESERVED_FOR_LOGBUILDER_TIMESTAMP);
        return this;
    }

    /** @param packageName to replace the existing setting. */
    public LogMaker setPackageName(String packageName) {
        entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_PACKAGENAME, packageName);
        return this;
    }

    /**
     * @param component to replace the existing setting.
     * @hide
     */
    public LogMaker setComponentName(ComponentName component) {
        entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_PACKAGENAME, component.getPackageName());
        entries.put(MetricsEvent.FIELD_CLASS_NAME, component.getClassName());
        return this;
    }

    /** Remove the package name property. */
    public LogMaker clearPackageName() {
        entries.remove(MetricsEvent.RESERVED_FOR_LOGBUILDER_PACKAGENAME);
        return this;
    }

    /**
     * This will be set by the system when the log is persisted.
     * Client-supplied values will be ignored.
     *
     * @param pid to replace the existing setting.
     * @hide
     */
    public LogMaker setProcessId(int pid) {
        entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_PID, pid);
        return this;
    }

    /** Remove the process ID property.
     * @hide
     */
    public LogMaker clearProcessId() {
        entries.remove(MetricsEvent.RESERVED_FOR_LOGBUILDER_PID);
        return this;
    }

    /**
     * This will be set by the system when the log is persisted.
     * Client-supplied values will be ignored.
     *
     * @param uid to replace the existing setting.
     * @hide
     */
    public LogMaker setUid(int uid) {
        entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_UID, uid);
        return this;
    }

    /**
     * Remove the UID property.
     * @hide
     */
    public LogMaker clearUid() {
        entries.remove(MetricsEvent.RESERVED_FOR_LOGBUILDER_UID);
        return this;
    }

    /**
     * The name of the counter or histogram.
     * Only useful for counter or histogram category objects.
     * @param name to replace the existing setting.
     * @hide
     */
    public LogMaker setCounterName(String name) {
        entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_NAME, name);
        return this;
    }

    /**
     * The bucket label, expressed as an integer.
     * Only useful for histogram category objects.
     * @param bucket to replace the existing setting.
     * @hide
     */
    public LogMaker setCounterBucket(int bucket) {
        entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_BUCKET, bucket);
        return this;
    }

    /**
     * The bucket label, expressed as a long integer.
     * Only useful for histogram category objects.
     * @param bucket to replace the existing setting.
     * @hide
     */
    public LogMaker setCounterBucket(long bucket) {
        entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_BUCKET, bucket);
        return this;
    }

    /**
     * The value to increment the counter or bucket by.
     * Only useful for counter and histogram category objects.
     * @param value to replace the existing setting.
     * @hide
     */
    public LogMaker setCounterValue(int value) {
        entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_VALUE, value);
        return this;
    }

    /**
     * @param tag From your MetricsEvent enum.
     * @param value One of Integer, Long, Float, or String; or null to clear the tag.
     * @return modified LogMaker
     */
    public LogMaker addTaggedData(int tag, Object value) {
        if (value == null) {
            return clearTaggedData(tag);
        }
        if (!isValidValue(value)) {
            throw new IllegalArgumentException(
                    "Value must be loggable type - int, long, float, String");
        }
        if (value.toString().getBytes().length > MAX_SERIALIZED_SIZE) {
            Log.i(TAG, "Log value too long, omitted: " + value.toString());
        } else {
            entries.put(tag, value);
        }
        return this;
    }

    /**
     * Remove a value from the LogMaker.
     *
     * @param tag From your MetricsEvent enum.
     * @return modified LogMaker
     */
    public LogMaker clearTaggedData(int tag) {
        entries.delete(tag);
        return this;
    }

    /**
     * @return true if this object may be added to a LogMaker as a value.
     */
    public boolean isValidValue(Object value) {
        return value instanceof Integer ||
            value instanceof String ||
            value instanceof Long ||
            value instanceof Float;
    }

    public Object getTaggedData(int tag) {
        return entries.get(tag);
    }

    /** @return the category of the log, or unknown. */
    public int getCategory() {
        Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_CATEGORY);
        if (obj instanceof Integer) {
            return (Integer) obj;
        } else {
            return MetricsEvent.VIEW_UNKNOWN;
        }
    }

    /** @return the type of the log, or unknwon. */
    public int getType() {
        Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_TYPE);
        if (obj instanceof Integer) {
            return (Integer) obj;
        } else {
            return MetricsEvent.TYPE_UNKNOWN;
        }
    }

    /** @return the subtype of the log, or 0. */
    public int getSubtype() {
        Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_SUBTYPE);
        if (obj instanceof Integer) {
            return (Integer) obj;
        } else {
            return 0;
        }
    }

    /** @return the timestamp of the log.or 0 */
    public long getTimestamp() {
        Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_TIMESTAMP);
        if (obj instanceof Long) {
            return (Long) obj;
        } else {
            return 0;
        }
    }

    /** @return the package name of the log, or null. */
    public String getPackageName() {
        Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_PACKAGENAME);
        if (obj instanceof String) {
            return (String) obj;
        } else {
            return null;
        }
    }

    /** @return the process ID of the log, or -1. */
    public int getProcessId() {
        Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_PID);
        if (obj instanceof Integer) {
            return (Integer) obj;
        } else {
            return -1;
        }
    }

    /** @return the UID of the log, or -1. */
    public int getUid() {
        Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_UID);
        if (obj instanceof Integer) {
            return (Integer) obj;
        } else {
            return -1;
        }
    }

    /** @return the name of the counter, or null. */
    public String getCounterName() {
        Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_NAME);
        if (obj instanceof String) {
            return (String) obj;
        } else {
            return null;
        }
    }

    /** @return the bucket label of the histogram\, or 0. */
    public long getCounterBucket() {
        Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_BUCKET);
        if (obj instanceof Number) {
            return ((Number) obj).longValue();
        } else {
            return 0L;
        }
    }

    /** @return true if the bucket label was specified as a long integer. */
    public boolean isLongCounterBucket() {
        Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_BUCKET);
        return obj instanceof Long;
    }

    /** @return the increment value of the counter, or 0. */
    public int getCounterValue() {
        Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_VALUE);
        if (obj instanceof Integer) {
            return (Integer) obj;
        } else {
            return 0;
        }
    }

    /**
     * @return a representation of the log suitable for EventLog.
     */
    public Object[] serialize() {
        Object[] out = new Object[entries.size() * 2];
        for (int i = 0; i < entries.size(); i++) {
            out[i * 2] = entries.keyAt(i);
            out[i * 2 + 1] = entries.valueAt(i);
        }
        int size = out.toString().getBytes().length;
        if (size > MAX_SERIALIZED_SIZE) {
            Log.i(TAG, "Log line too long, did not emit: " + size + " bytes.");
            throw new RuntimeException();
        }
        return out;
    }

    /**
     * Reconstitute an object from the output of {@link #serialize()}.
     */
    public void deserialize(Object[] items) {
        int i = 0;
        while (items != null && i < items.length) {
            Object key = items[i++];
            Object value = i < items.length ? items[i++] : null;
            if (key instanceof Integer) {
                entries.put((Integer) key, value);
            } else {
                Log.i(TAG, "Invalid key " + (key == null ? "null" : key.toString()));
            }
        }
    }

    /**
     * @param that the object to compare to.
     * @return true if values in that equal values in this, for tags that exist in this.
     */
    public boolean isSubsetOf(LogMaker that) {
        if (that == null) {
            return false;
        }
        for (int i = 0; i < entries.size(); i++) {
            int key = this.entries.keyAt(i);
            Object thisValue = this.entries.valueAt(i);
            Object thatValue = that.entries.get(key);
            if ((thisValue == null && thatValue != null) || !thisValue.equals(thatValue))
                return false;
        }
        return true;
    }
}