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