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