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