summaryrefslogtreecommitdiff
path: root/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/ConfigUtils.java
blob: be665ba40132888c6a4957d57e784d8c6100747f (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
/*
 * Copyright (C) 2020 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.cts.statsdatom.lib;

import com.android.os.AtomsProto.AppBreadcrumbReported;
import com.android.internal.os.StatsdConfigProto.AtomMatcher;
import com.android.internal.os.StatsdConfigProto.EventMetric;
import com.android.internal.os.StatsdConfigProto.FieldFilter;
import com.android.internal.os.StatsdConfigProto.FieldMatcher;
import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
import com.android.internal.os.StatsdConfigProto.GaugeMetric;
import com.android.internal.os.StatsdConfigProto.MessageMatcher;
import com.android.internal.os.StatsdConfigProto.Position;
import com.android.internal.os.StatsdConfigProto.Predicate;
import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
import com.android.internal.os.StatsdConfigProto.SimplePredicate;
import com.android.internal.os.StatsdConfigProto.StatsdConfig;
import com.android.internal.os.StatsdConfigProto.TimeUnit;
import com.android.os.AtomsProto.Atom;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.log.LogUtil.CLog;

import com.google.common.io.Files;

import java.io.File;
import java.util.Arrays;
import java.util.List;

import javax.annotation.Nullable;

public final class ConfigUtils {
    public static final long CONFIG_ID = "cts_config".hashCode(); // evaluates to -1572883457
    public static final String CONFIG_ID_STRING = String.valueOf(CONFIG_ID);

    // Attribution chains are the first field in atoms.
    private static final int ATTRIBUTION_CHAIN_FIELD_NUMBER = 1;
    // Uids are the first field in attribution nodes.
    private static final int ATTRIBUTION_NODE_UID_FIELD_NUMBER = 1;
    // Uids as standalone fields are the first field in atoms.
    private static final int UID_FIELD_NUMBER = 1;

    // adb shell commands
    private static final String UPDATE_CONFIG_CMD = "cmd stats config update";
    private static final String REMOVE_CONFIG_CMD = "cmd stats config remove";

    /**
     * Create a new config with common fields filled out, such as allowed log sources and
     * default pull packages.
     *
     * @param pkgName test app package from which pushed atoms will be sent
     */
    public static StatsdConfig.Builder createConfigBuilder(String pkgName) {
        return StatsdConfig.newBuilder()
                .setId(CONFIG_ID)
                .addAllowedLogSource("AID_SYSTEM")
                .addAllowedLogSource("AID_BLUETOOTH")
                .addAllowedLogSource("com.android.bluetooth")
                // TODO(b/236681553): Remove this.
                .addAllowedLogSource("com.android.bluetooth.services")
                // TODO(b/236681553): Remove this.
                .addAllowedLogSource("com.google.android.bluetooth.services")
                .addAllowedLogSource("AID_LMKD")
                .addAllowedLogSource("AID_MEDIA")
                .addAllowedLogSource("AID_RADIO")
                .addAllowedLogSource("AID_ROOT")
                .addAllowedLogSource("AID_STATSD")
                .addAllowedLogSource("com.android.systemui")
                .addAllowedLogSource(pkgName)
                .addDefaultPullPackages("AID_RADIO")
                .addDefaultPullPackages("AID_SYSTEM")
                .addWhitelistedAtomIds(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER);
    }

    /**
     * Adds an event metric for the specified atom. The atom should contain a uid either within
     * an attribution chain or as a standalone field. Only those atoms which contain the uid of
     * the test app will be included in statsd's report.
     *
     * @param config
     * @param atomId index of atom within atoms.proto
     * @param uidInAttributionChain if true, the uid is part of the attribution chain; if false,
     *    uid is a standalone field
     * @param pkgName test app package from which atom will be logged
     */
    public static void addEventMetricForUidAtom(StatsdConfig.Builder config, int atomId,
            boolean uidInAttributionChain, String pkgName) {
        FieldValueMatcher.Builder fvm = createUidFvm(uidInAttributionChain, pkgName);
        addEventMetric(config, atomId, Arrays.asList(fvm));
    }

    /**
     * Adds an event metric for the specified atom. All such atoms received by statsd will be
     * included in the report. If only atoms meeting certain constraints should be added to the
     * report, use #addEventMetric(int atomId, List<FieldValueMatcher.Builder> fvms instead.
     *
     * @param config
     * @param atomId index of atom within atoms.proto
     */
    public static void addEventMetric(StatsdConfig.Builder config, int atomId) {
        addEventMetric(config, atomId, /*fvms=*/null);
    }

    /**
     * Adds an event metric to the config for the specified atom. The atom's fields must meet
     * the constraints specified in fvms for the atom to be included in statsd's report.
     *
     * @param config
     * @param atomId index of atom within atoms.proto
     * @param fvms list of constraints that atoms are filtered on
     */
    public static void addEventMetric(StatsdConfig.Builder config, int atomId,
            @Nullable List<FieldValueMatcher.Builder> fvms) {
        final String matcherName = "Atom matcher" + System.nanoTime();
        final String eventName = "Event " + System.nanoTime();

        SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(atomId);
        if (fvms != null) {
            for (FieldValueMatcher.Builder fvm : fvms) {
                sam.addFieldValueMatcher(fvm);
            }
        }

        config.addAtomMatcher(AtomMatcher.newBuilder()
                .setId(matcherName.hashCode())
                .setSimpleAtomMatcher(sam));
        config.addEventMetric(EventMetric.newBuilder()
                .setId(eventName.hashCode())
                .setWhat(matcherName.hashCode()));
    }

    /**
     * Adds a gauge metric for a pulled atom with a uid field to the config. The atom will be
     * pulled when an AppBreadcrumbReported atom is logged to statsd, and only those pulled atoms
     * containing the uid of the test app will be included in statsd's report.
     *
     * @param config
     * @param atomId index of atom within atoms.proto
     * @param uidInAttributionChain if true, the uid is part of the attribution chain; if false, uid
     *    is a standalone field
     * @param pkgName test app package from which atom will be logged
     */
    public static void addGaugeMetricForUidAtom(StatsdConfig.Builder config, int atomId,
            boolean uidInAttributionChain, String pkgName) {
        addGaugeMetricInternal(config, atomId, /*filterByUid=*/true, uidInAttributionChain, pkgName,
                /*dimensionsInWhat=*/null);
    }

    /**
     * Equivalent to addGaugeMetricForUidAtom except that the output in the report is sliced by the
     * specified dimensions.
     *
     * @param dimensionsInWhat dimensions to slice the output by
     */
    public static void addGaugeMetricForUidAtomWithDimensions(StatsdConfig.Builder config,
            int atomId, boolean uidInAttributionChain, String pkgName,
            FieldMatcher.Builder dimensionsInWhat) {
        addGaugeMetricInternal(config, atomId, /*filterByUid=*/true, uidInAttributionChain, pkgName,
                dimensionsInWhat);
    }

    /**
     * Adds a gauge metric for a pulled atom to the config. The atom will be pulled when an
     * AppBreadcrumbReported atom is logged to statsd.
     *
     * @param config
     * @param atomId index of the atom within atoms.proto
     * @param dimensionsInWhat dimensions to slice the output by
     */
    public static void addGaugeMetric(StatsdConfig.Builder config, int atomId) {
        addGaugeMetricInternal(config, atomId, /*filterByUid=*/false,
                /*uidInAttributionChain=*/false, /*pkgName=*/null, /*dimensionsInWhat=*/null);
    }

    /**
     * Equivalent to addGaugeMetric except that output in the report is sliced by the specified
     * dimensions.
     *
     * @param dimensionsInWhat dimensions to slice the output by
     */
    public static void addGaugeMetricWithDimensions(StatsdConfig.Builder config, int atomId,
            FieldMatcher.Builder dimensionsInWhat) {
        addGaugeMetricInternal(config, atomId, /*filterByUid=*/false,
                /*uidInAttributionChain=*/false, /*pkgName=*/null, dimensionsInWhat);
    }

    private static void addGaugeMetricInternal(StatsdConfig.Builder config, int atomId,
            boolean filterByUid, boolean uidInAttributionChain, @Nullable String pkgName,
            @Nullable FieldMatcher.Builder dimensionsInWhat) {
        final String gaugeName = "Gauge metric " + System.nanoTime();
        final String whatName = "What atom matcher " + System.nanoTime();
        final String triggerName = "Trigger atom matcher " + System.nanoTime();

        // Add atom matcher for "what"
        SimpleAtomMatcher.Builder whatMatcher = SimpleAtomMatcher.newBuilder().setAtomId(atomId);
        if (filterByUid && pkgName != null) {
            whatMatcher.addFieldValueMatcher(createUidFvm(uidInAttributionChain, pkgName));
        }
        config.addAtomMatcher(AtomMatcher.newBuilder()
                .setId(whatName.hashCode())
                .setSimpleAtomMatcher(whatMatcher));

        // Add atom matcher for trigger event
        SimpleAtomMatcher.Builder triggerMatcher = SimpleAtomMatcher.newBuilder()
                .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER);
        config.addAtomMatcher(AtomMatcher.newBuilder()
                .setId(triggerName.hashCode())
                .setSimpleAtomMatcher(triggerMatcher));

        // Add gauge metric
        GaugeMetric.Builder gaugeMetric = GaugeMetric.newBuilder()
                .setId(gaugeName.hashCode())
                .setWhat(whatName.hashCode())
                .setTriggerEvent(triggerName.hashCode())
                .setGaugeFieldsFilter(FieldFilter.newBuilder().setIncludeAll(true).build())
                .setBucket(TimeUnit.CTS)
                .setSamplingType(GaugeMetric.SamplingType.FIRST_N_SAMPLES)
                .setMaxNumGaugeAtomsPerBucket(10_000);
        if (dimensionsInWhat != null) {
            gaugeMetric.setDimensionsInWhat(dimensionsInWhat.build());
        }
        config.addGaugeMetric(gaugeMetric.build());
    }

    /**
     * Creates a FieldValueMatcher.Builder object that matches atoms whose uid field is equal to
     * the uid of pkgName.
     *
     * @param uidInAttributionChain if true, the uid is part of the attribution chain; if false, uid
     * is a standalone field
     * @param pkgName test app package from which atom will be logged
     */
    public static FieldValueMatcher.Builder createUidFvm(boolean uidInAttributionChain,
            String pkgName) {
        if (uidInAttributionChain) {
            FieldValueMatcher.Builder nodeFvm = createFvm(ATTRIBUTION_NODE_UID_FIELD_NUMBER)
                    .setEqString(pkgName);
            return createFvm(ATTRIBUTION_CHAIN_FIELD_NUMBER)
                    .setPosition(Position.ANY)
                    .setMatchesTuple(MessageMatcher.newBuilder().addFieldValueMatcher(nodeFvm));
        } else {
            return createFvm(UID_FIELD_NUMBER).setEqString(pkgName);
        }
    }

    /**
     * Creates a FieldValueMatcher.Builder for a particular field. Note that the value still needs
     * to be set.
     *
     * @param fieldNumber index of field within the atom
     */
    public static FieldValueMatcher.Builder createFvm(int fieldNumber) {
        return FieldValueMatcher.newBuilder().setField(fieldNumber);
    }

    /**
     * Upload a config to statsd.
     */
    public static void uploadConfig(ITestDevice device, StatsdConfig.Builder configBuilder)
            throws Exception {
        StatsdConfig config = configBuilder.build();
        CLog.d("Uploading the following config to statsd:\n" + config.toString());

        File configFile = File.createTempFile("statsdconfig", ".config");
        configFile.deleteOnExit();
        Files.write(config.toByteArray(), configFile);

        // Push config to temporary location
        String remotePath = "/data/local/tmp/" + configFile.getName();
        device.pushFile(configFile, remotePath);

        // Send config to statsd
        device.executeShellCommand(String.join(" ", "cat", remotePath, "|", UPDATE_CONFIG_CMD,
                CONFIG_ID_STRING));

        // Remove config from temporary location
        device.executeShellCommand("rm " + remotePath);

        // Sleep for a bit so that statsd receives config before more work is done within the test.
        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
    }

    /**
     * Removes any pre-existing CTS configs from statsd.
     */
    public static void removeConfig(ITestDevice device) throws Exception {
        device.executeShellCommand(String.join(" ", REMOVE_CONFIG_CMD, CONFIG_ID_STRING));
    }

    public static void uploadConfigForPushedAtomWithUid(ITestDevice device, String pkgName,
            int atomId,
            boolean useUidAttributionChain) throws Exception {
        StatsdConfig.Builder config = createConfigBuilder(pkgName);
        addEventMetricForUidAtom(config, atomId, useUidAttributionChain, pkgName);
        uploadConfig(device, config);
    }

    public static void uploadConfigForPulledAtomWithUid(ITestDevice device, String pkgName,
            int atomId,
            boolean useUidAttributionChain) throws Exception {
        StatsdConfig.Builder config = createConfigBuilder(pkgName);
        addGaugeMetricForUidAtom(config, atomId, useUidAttributionChain, pkgName);
        uploadConfig(device, config);
    }

    public static void uploadConfigForPushedAtom(ITestDevice device, String pkgName, int atomId)
            throws Exception {
        StatsdConfig.Builder config = createConfigBuilder(pkgName);
        addEventMetric(config, atomId);
        uploadConfig(device, config);
    }

    public static void uploadConfigForPushedAtoms(ITestDevice device, String pkgName, int[] atomIds)
            throws Exception {
        StatsdConfig.Builder config = createConfigBuilder(pkgName);
        for (int atomId : atomIds) {
            addEventMetric(config, atomId);
        }
        uploadConfig(device, config);
    }

    public static void uploadConfigForPulledAtom(ITestDevice device, String pkgName, int atomId)
            throws Exception {
        StatsdConfig.Builder config = createConfigBuilder(pkgName);
        addGaugeMetric(config, atomId);
        uploadConfig(device, config);
    }

    private ConfigUtils() {
    }
}