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