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