1 /*
2  * Copyright (C) 2020 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 
17 package android.cts.statsdatom.lib;
18 
19 import com.android.os.AtomsProto.AppBreadcrumbReported;
20 import com.android.internal.os.StatsdConfigProto.AtomMatcher;
21 import com.android.internal.os.StatsdConfigProto.EventMetric;
22 import com.android.internal.os.StatsdConfigProto.FieldFilter;
23 import com.android.internal.os.StatsdConfigProto.FieldMatcher;
24 import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
25 import com.android.internal.os.StatsdConfigProto.GaugeMetric;
26 import com.android.internal.os.StatsdConfigProto.MessageMatcher;
27 import com.android.internal.os.StatsdConfigProto.Position;
28 import com.android.internal.os.StatsdConfigProto.Predicate;
29 import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
30 import com.android.internal.os.StatsdConfigProto.SimplePredicate;
31 import com.android.internal.os.StatsdConfigProto.StatsdConfig;
32 import com.android.internal.os.StatsdConfigProto.TimeUnit;
33 import com.android.os.AtomsProto.Atom;
34 import com.android.tradefed.device.ITestDevice;
35 import com.android.tradefed.log.LogUtil.CLog;
36 
37 import com.google.common.io.Files;
38 
39 import java.io.File;
40 import java.util.Arrays;
41 import java.util.List;
42 
43 import javax.annotation.Nullable;
44 
45 public final class ConfigUtils {
46     public static final long CONFIG_ID = "cts_config".hashCode(); // evaluates to -1572883457
47     public static final String CONFIG_ID_STRING = String.valueOf(CONFIG_ID);
48 
49     // Attribution chains are the first field in atoms.
50     private static final int ATTRIBUTION_CHAIN_FIELD_NUMBER = 1;
51     // Uids are the first field in attribution nodes.
52     private static final int ATTRIBUTION_NODE_UID_FIELD_NUMBER = 1;
53     // Uids as standalone fields are the first field in atoms.
54     private static final int UID_FIELD_NUMBER = 1;
55 
56     // adb shell commands
57     private static final String UPDATE_CONFIG_CMD = "cmd stats config update";
58     private static final String REMOVE_CONFIG_CMD = "cmd stats config remove";
59 
60     /**
61      * Create a new config with common fields filled out, such as allowed log sources and
62      * default pull packages.
63      *
64      * @param pkgName test app package from which pushed atoms will be sent
65      */
createConfigBuilder(String pkgName)66     public static StatsdConfig.Builder createConfigBuilder(String pkgName) {
67         return StatsdConfig.newBuilder()
68                 .setId(CONFIG_ID)
69                 .addAllowedLogSource("AID_SYSTEM")
70                 .addAllowedLogSource("AID_BLUETOOTH")
71                 // TODO(b/134091167): Fix bluetooth source name issue in Auto platform.
72                 .addAllowedLogSource("com.android.bluetooth")
73                 .addAllowedLogSource("AID_LMKD")
74                 .addAllowedLogSource("AID_MEDIA")
75                 .addAllowedLogSource("AID_RADIO")
76                 .addAllowedLogSource("AID_ROOT")
77                 .addAllowedLogSource("AID_STATSD")
78                 .addAllowedLogSource("com.android.systemui")
79                 .addAllowedLogSource(pkgName)
80                 .addDefaultPullPackages("AID_RADIO")
81                 .addDefaultPullPackages("AID_SYSTEM")
82                 .addWhitelistedAtomIds(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER);
83     }
84 
85     /**
86      * Adds an event metric for the specified atom. The atom should contain a uid either within
87      * an attribution chain or as a standalone field. Only those atoms which contain the uid of
88      * the test app will be included in statsd's report.
89      *
90      * @param config
91      * @param atomId index of atom within atoms.proto
92      * @param uidInAttributionChain if true, the uid is part of the attribution chain; if false,
93      *    uid is a standalone field
94      * @param pkgName test app package from which atom will be logged
95      */
addEventMetricForUidAtom(StatsdConfig.Builder config, int atomId, boolean uidInAttributionChain, String pkgName)96     public static void addEventMetricForUidAtom(StatsdConfig.Builder config, int atomId,
97             boolean uidInAttributionChain, String pkgName) {
98         FieldValueMatcher.Builder fvm = createUidFvm(uidInAttributionChain, pkgName);
99         addEventMetric(config, atomId, Arrays.asList(fvm));
100     }
101 
102     /**
103      * Adds an event metric for the specified atom. All such atoms received by statsd will be
104      * included in the report. If only atoms meeting certain constraints should be added to the
105      * report, use #addEventMetric(int atomId, List<FieldValueMatcher.Builder> fvms instead.
106      *
107      * @param config
108      * @param atomId index of atom within atoms.proto
109      */
addEventMetric(StatsdConfig.Builder config, int atomId)110     public static void addEventMetric(StatsdConfig.Builder config, int atomId) {
111         addEventMetric(config, atomId, /*fvms=*/null);
112     }
113 
114     /**
115      * Adds an event metric to the config for the specified atom. The atom's fields must meet
116      * the constraints specified in fvms for the atom to be included in statsd's report.
117      *
118      * @param config
119      * @param atomId index of atom within atoms.proto
120      * @param fvms list of constraints that atoms are filtered on
121      */
addEventMetric(StatsdConfig.Builder config, int atomId, @Nullable List<FieldValueMatcher.Builder> fvms)122     public static void addEventMetric(StatsdConfig.Builder config, int atomId,
123             @Nullable List<FieldValueMatcher.Builder> fvms) {
124         final String matcherName = "Atom matcher" + System.nanoTime();
125         final String eventName = "Event " + System.nanoTime();
126 
127         SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(atomId);
128         if (fvms != null) {
129             for (FieldValueMatcher.Builder fvm : fvms) {
130                 sam.addFieldValueMatcher(fvm);
131             }
132         }
133 
134         config.addAtomMatcher(AtomMatcher.newBuilder()
135                 .setId(matcherName.hashCode())
136                 .setSimpleAtomMatcher(sam));
137         config.addEventMetric(EventMetric.newBuilder()
138                 .setId(eventName.hashCode())
139                 .setWhat(matcherName.hashCode()));
140     }
141 
142     /**
143      * Adds a gauge metric for a pulled atom with a uid field to the config. The atom will be
144      * pulled when an AppBreadcrumbReported atom is logged to statsd, and only those pulled atoms
145      * containing the uid of the test app will be included in statsd's report.
146      *
147      * @param config
148      * @param atomId index of atom within atoms.proto
149      * @param uidInAttributionChain if true, the uid is part of the attribution chain; if false, uid
150      *    is a standalone field
151      * @param pkgName test app package from which atom will be logged
152      */
addGaugeMetricForUidAtom(StatsdConfig.Builder config, int atomId, boolean uidInAttributionChain, String pkgName)153     public static void addGaugeMetricForUidAtom(StatsdConfig.Builder config, int atomId,
154             boolean uidInAttributionChain, String pkgName) {
155         addGaugeMetricInternal(config, atomId, /*filterByUid=*/true, uidInAttributionChain, pkgName,
156                 /*dimensionsInWhat=*/null);
157     }
158 
159     /**
160      * Equivalent to addGaugeMetricForUidAtom except that the output in the report is sliced by the
161      * specified dimensions.
162      *
163      * @param dimensionsInWhat dimensions to slice the output by
164      */
addGaugeMetricForUidAtomWithDimensions(StatsdConfig.Builder config, int atomId, boolean uidInAttributionChain, String pkgName, FieldMatcher.Builder dimensionsInWhat)165     public static void addGaugeMetricForUidAtomWithDimensions(StatsdConfig.Builder config,
166             int atomId, boolean uidInAttributionChain, String pkgName,
167             FieldMatcher.Builder dimensionsInWhat) {
168         addGaugeMetricInternal(config, atomId, /*filterByUid=*/true, uidInAttributionChain, pkgName,
169                 dimensionsInWhat);
170     }
171 
172     /**
173      * Adds a gauge metric for a pulled atom to the config. The atom will be pulled when an
174      * AppBreadcrumbReported atom is logged to statsd.
175      *
176      * @param config
177      * @param atomId index of the atom within atoms.proto
178      * @param dimensionsInWhat dimensions to slice the output by
179      */
addGaugeMetric(StatsdConfig.Builder config, int atomId)180     public static void addGaugeMetric(StatsdConfig.Builder config, int atomId) {
181         addGaugeMetricInternal(config, atomId, /*filterByUid=*/false,
182                 /*uidInAttributionChain=*/false, /*pkgName=*/null, /*dimensionsInWhat=*/null);
183     }
184 
185     /**
186      * Equivalent to addGaugeMetric except that output in the report is sliced by the specified
187      * dimensions.
188      *
189      * @param dimensionsInWhat dimensions to slice the output by
190      */
addGaugeMetricWithDimensions(StatsdConfig.Builder config, int atomId, FieldMatcher.Builder dimensionsInWhat)191     public static void addGaugeMetricWithDimensions(StatsdConfig.Builder config, int atomId,
192             FieldMatcher.Builder dimensionsInWhat) {
193         addGaugeMetricInternal(config, atomId, /*filterByUid=*/false,
194                 /*uidInAttributionChain=*/false, /*pkgName=*/null, dimensionsInWhat);
195     }
196 
addGaugeMetricInternal(StatsdConfig.Builder config, int atomId, boolean filterByUid, boolean uidInAttributionChain, @Nullable String pkgName, @Nullable FieldMatcher.Builder dimensionsInWhat)197     private static void addGaugeMetricInternal(StatsdConfig.Builder config, int atomId,
198             boolean filterByUid, boolean uidInAttributionChain, @Nullable String pkgName,
199             @Nullable FieldMatcher.Builder dimensionsInWhat) {
200         final String gaugeName = "Gauge metric " + System.nanoTime();
201         final String whatName = "What atom matcher " + System.nanoTime();
202         final String triggerName = "Trigger atom matcher " + System.nanoTime();
203 
204         // Add atom matcher for "what"
205         SimpleAtomMatcher.Builder whatMatcher = SimpleAtomMatcher.newBuilder().setAtomId(atomId);
206         if (filterByUid && pkgName != null) {
207             whatMatcher.addFieldValueMatcher(createUidFvm(uidInAttributionChain, pkgName));
208         }
209         config.addAtomMatcher(AtomMatcher.newBuilder()
210                 .setId(whatName.hashCode())
211                 .setSimpleAtomMatcher(whatMatcher));
212 
213         // Add atom matcher for trigger event
214         SimpleAtomMatcher.Builder triggerMatcher = SimpleAtomMatcher.newBuilder()
215                 .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER);
216         config.addAtomMatcher(AtomMatcher.newBuilder()
217                 .setId(triggerName.hashCode())
218                 .setSimpleAtomMatcher(triggerMatcher));
219 
220         // Add gauge metric
221         GaugeMetric.Builder gaugeMetric = GaugeMetric.newBuilder()
222                 .setId(gaugeName.hashCode())
223                 .setWhat(whatName.hashCode())
224                 .setTriggerEvent(triggerName.hashCode())
225                 .setGaugeFieldsFilter(FieldFilter.newBuilder().setIncludeAll(true).build())
226                 .setBucket(TimeUnit.CTS)
227                 .setSamplingType(GaugeMetric.SamplingType.FIRST_N_SAMPLES)
228                 .setMaxNumGaugeAtomsPerBucket(10_000);
229         if (dimensionsInWhat != null) {
230             gaugeMetric.setDimensionsInWhat(dimensionsInWhat.build());
231         }
232         config.addGaugeMetric(gaugeMetric.build());
233     }
234 
235     /**
236      * Creates a FieldValueMatcher.Builder object that matches atoms whose uid field is equal to
237      * the uid of pkgName.
238      *
239      * @param uidInAttributionChain if true, the uid is part of the attribution chain; if false, uid
240      * is a standalone field
241      * @param pkgName test app package from which atom will be logged
242      */
createUidFvm(boolean uidInAttributionChain, String pkgName)243     public static FieldValueMatcher.Builder createUidFvm(boolean uidInAttributionChain,
244             String pkgName) {
245         if (uidInAttributionChain) {
246             FieldValueMatcher.Builder nodeFvm = createFvm(ATTRIBUTION_NODE_UID_FIELD_NUMBER)
247                     .setEqString(pkgName);
248             return createFvm(ATTRIBUTION_CHAIN_FIELD_NUMBER)
249                     .setPosition(Position.ANY)
250                     .setMatchesTuple(MessageMatcher.newBuilder().addFieldValueMatcher(nodeFvm));
251         } else {
252             return createFvm(UID_FIELD_NUMBER).setEqString(pkgName);
253         }
254     }
255 
256     /**
257      * Creates a FieldValueMatcher.Builder for a particular field. Note that the value still needs
258      * to be set.
259      *
260      * @param fieldNumber index of field within the atom
261      */
createFvm(int fieldNumber)262     public static FieldValueMatcher.Builder createFvm(int fieldNumber) {
263         return FieldValueMatcher.newBuilder().setField(fieldNumber);
264     }
265 
266     /**
267      * Upload a config to statsd.
268      */
uploadConfig(ITestDevice device, StatsdConfig.Builder configBuilder)269     public static void uploadConfig(ITestDevice device, StatsdConfig.Builder configBuilder)
270             throws Exception {
271         StatsdConfig config = configBuilder.build();
272         CLog.d("Uploading the following config to statsd:\n" + config.toString());
273 
274         File configFile = File.createTempFile("statsdconfig", ".config");
275         configFile.deleteOnExit();
276         Files.write(config.toByteArray(), configFile);
277 
278         // Push config to temporary location
279         String remotePath = "/data/local/tmp/" + configFile.getName();
280         device.pushFile(configFile, remotePath);
281 
282         // Send config to statsd
283         device.executeShellCommand(String.join(" ", "cat", remotePath, "|", UPDATE_CONFIG_CMD,
284                 CONFIG_ID_STRING));
285 
286         // Remove config from temporary location
287         device.executeShellCommand("rm " + remotePath);
288 
289         // Sleep for a bit so that statsd receives config before more work is done within the test.
290         Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
291     }
292 
293     /**
294      * Removes any pre-existing CTS configs from statsd.
295      */
removeConfig(ITestDevice device)296     public static void removeConfig(ITestDevice device) throws Exception {
297         device.executeShellCommand(String.join(" ", REMOVE_CONFIG_CMD, CONFIG_ID_STRING));
298     }
299 
uploadConfigForPushedAtomWithUid(ITestDevice device, String pkgName, int atomId, boolean useUidAttributionChain)300     public static void uploadConfigForPushedAtomWithUid(ITestDevice device, String pkgName,
301             int atomId,
302             boolean useUidAttributionChain) throws Exception {
303         StatsdConfig.Builder config = createConfigBuilder(pkgName);
304         addEventMetricForUidAtom(config, atomId, useUidAttributionChain, pkgName);
305         uploadConfig(device, config);
306     }
307 
uploadConfigForPulledAtomWithUid(ITestDevice device, String pkgName, int atomId, boolean useUidAttributionChain)308     public static void uploadConfigForPulledAtomWithUid(ITestDevice device, String pkgName,
309             int atomId,
310             boolean useUidAttributionChain) throws Exception {
311         StatsdConfig.Builder config = createConfigBuilder(pkgName);
312         addGaugeMetricForUidAtom(config, atomId, useUidAttributionChain, pkgName);
313         uploadConfig(device, config);
314     }
315 
uploadConfigForPushedAtom(ITestDevice device, String pkgName, int atomId)316     public static void uploadConfigForPushedAtom(ITestDevice device, String pkgName, int atomId)
317             throws Exception {
318         StatsdConfig.Builder config = createConfigBuilder(pkgName);
319         addEventMetric(config, atomId);
320         uploadConfig(device, config);
321     }
322 
uploadConfigForPushedAtoms(ITestDevice device, String pkgName, int[] atomIds)323     public static void uploadConfigForPushedAtoms(ITestDevice device, String pkgName, int[] atomIds)
324             throws Exception {
325         StatsdConfig.Builder config = createConfigBuilder(pkgName);
326         for (int atomId : atomIds) {
327             addEventMetric(config, atomId);
328         }
329         uploadConfig(device, config);
330     }
331 
uploadConfigForPulledAtom(ITestDevice device, String pkgName, int atomId)332     public static void uploadConfigForPulledAtom(ITestDevice device, String pkgName, int atomId)
333             throws Exception {
334         StatsdConfig.Builder config = createConfigBuilder(pkgName);
335         addGaugeMetric(config, atomId);
336         uploadConfig(device, config);
337     }
338 
ConfigUtils()339     private ConfigUtils() {
340     }
341 }
342