1 /* 2 * Copyright (C) 2018 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 package com.android.cts.devicepolicy.metrics; 17 18 import android.cts.statsdatom.lib.ReportUtils; 19 20 import com.android.internal.os.StatsdConfigProto.AtomMatcher; 21 import com.android.internal.os.StatsdConfigProto.EventMetric; 22 import com.android.internal.os.StatsdConfigProto.FieldValueMatcher; 23 import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher; 24 import com.android.internal.os.StatsdConfigProto.StatsdConfig; 25 import com.android.os.AtomsProto.Atom; 26 import com.android.os.StatsLog.ConfigMetricsReportList; 27 import com.android.os.StatsLog.EventMetricData; 28 import com.android.tradefed.device.CollectingByteOutputReceiver; 29 import com.android.tradefed.device.DeviceNotAvailableException; 30 import com.android.tradefed.device.ITestDevice; 31 import com.android.tradefed.log.LogUtil.CLog; 32 33 import com.google.common.io.Files; 34 import com.google.protobuf.InvalidProtocolBufferException; 35 import com.google.protobuf.MessageLite; 36 import com.google.protobuf.Parser; 37 38 import java.io.File; 39 import java.util.ArrayList; 40 import java.util.List; 41 import java.util.function.Predicate; 42 43 /** 44 * Tests Statsd atoms. 45 * <p/> 46 * Uploads statsd event configs, retrieves logs from host side and validates them 47 * against specified criteria. 48 */ 49 class AtomMetricTester { 50 private static final String UPDATE_CONFIG_CMD = "cat %s | cmd stats config update %d"; 51 private static final String DUMP_REPORT_CMD = 52 "cmd stats dump-report %d --include_current_bucket --proto"; 53 private static final String REMOVE_CONFIG_CMD = "cmd stats config remove %d"; 54 /** ID of the config, which evaluates to -1572883457. */ 55 private static final long CONFIG_ID = "cts_config".hashCode(); 56 57 private final ITestDevice mDevice; 58 AtomMetricTester(ITestDevice device)59 AtomMetricTester(ITestDevice device) { 60 mDevice = device; 61 } 62 cleanLogs()63 void cleanLogs() throws Exception { 64 removeConfig(CONFIG_ID); 65 getReportList(); // Clears data. 66 } 67 createConfigBuilder()68 private static StatsdConfig.Builder createConfigBuilder() { 69 return StatsdConfig.newBuilder().setId(CONFIG_ID) 70 .addAllowedLogSource("AID_SYSTEM"); 71 } 72 createAndUploadConfig(int atomTag)73 void createAndUploadConfig(int atomTag) throws Exception { 74 StatsdConfig.Builder conf = createConfigBuilder(); 75 addAtomEvent(conf, atomTag); 76 uploadConfig(conf); 77 } 78 uploadConfig(StatsdConfig.Builder config)79 private void uploadConfig(StatsdConfig.Builder config) throws Exception { 80 uploadConfig(config.build()); 81 } 82 uploadConfig(StatsdConfig config)83 private void uploadConfig(StatsdConfig config) throws Exception { 84 CLog.d("Uploading the following config:\n" + config.toString()); 85 File configFile = File.createTempFile("statsdconfig", ".config"); 86 configFile.deleteOnExit(); 87 Files.write(config.toByteArray(), configFile); 88 String remotePath = "/data/local/tmp/" + configFile.getName(); 89 mDevice.pushFile(configFile, remotePath); 90 mDevice.executeShellCommand(String.format(UPDATE_CONFIG_CMD, remotePath, CONFIG_ID)); 91 mDevice.executeShellCommand("rm " + remotePath); 92 } 93 removeConfig(long configId)94 private void removeConfig(long configId) throws Exception { 95 mDevice.executeShellCommand(String.format(REMOVE_CONFIG_CMD, configId)); 96 } 97 98 /** 99 * Gets the statsd report and sorts it. 100 * Note that this also deletes that report from statsd. 101 */ getEventMetricDataList()102 List<EventMetricData> getEventMetricDataList() throws Exception { 103 ConfigMetricsReportList reportList = getReportList(); 104 return ReportUtils.getEventMetricDataList(reportList); 105 } 106 107 /** Gets the statsd report. Note that this also deletes that report from statsd. */ getReportList()108 private ConfigMetricsReportList getReportList() throws Exception { 109 try { 110 return getDump(ConfigMetricsReportList.parser(), 111 String.format(DUMP_REPORT_CMD, CONFIG_ID)); 112 } catch (com.google.protobuf.InvalidProtocolBufferException e) { 113 CLog.e("Failed to fetch and parse the statsd output report. " 114 + "Perhaps there is not a valid statsd config for the requested " 115 + "uid=" + getHostUid() + ", id=" + CONFIG_ID + "."); 116 throw (e); 117 } 118 } 119 120 /** Creates a FieldValueMatcher.Builder corresponding to the given field. */ createFvm(int field)121 private static FieldValueMatcher.Builder createFvm(int field) { 122 return FieldValueMatcher.newBuilder().setField(field); 123 } 124 addAtomEvent(StatsdConfig.Builder conf, int atomTag)125 private void addAtomEvent(StatsdConfig.Builder conf, int atomTag) throws Exception { 126 addAtomEvent(conf, atomTag, new ArrayList<FieldValueMatcher.Builder>()); 127 } 128 129 /** 130 * Adds an event to the config for an atom that matches the given keys. 131 * 132 * @param conf configuration 133 * @param atomTag atom tag (from atoms.proto) 134 * @param fvms list of FieldValueMatcher.Builders to attach to the atom. May be null. 135 */ addAtomEvent(StatsdConfig.Builder conf, int atomTag, List<FieldValueMatcher.Builder> fvms)136 private void addAtomEvent(StatsdConfig.Builder conf, int atomTag, 137 List<FieldValueMatcher.Builder> fvms) throws Exception { 138 139 final String atomName = "Atom" + System.nanoTime(); 140 final String eventName = "Event" + System.nanoTime(); 141 142 SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(atomTag); 143 if (fvms != null) { 144 for (FieldValueMatcher.Builder fvm : fvms) { 145 sam.addFieldValueMatcher(fvm); 146 } 147 } 148 conf.addAtomMatcher(AtomMatcher.newBuilder() 149 .setId(atomName.hashCode()) 150 .setSimpleAtomMatcher(sam)); 151 conf.addEventMetric(EventMetric.newBuilder() 152 .setId(eventName.hashCode()) 153 .setWhat(atomName.hashCode())); 154 } 155 156 /** 157 * Removes all elements from data prior to the first occurrence of an element for which 158 * the <code>atomMatcher</code> predicate returns <code>true</code>. 159 * After this method is called, the first element of data (if non-empty) is guaranteed to be 160 * an element in state. 161 * 162 * @param atomMatcher predicate that takes an Atom and returns <code>true</code> if it 163 * fits criteria. 164 */ dropWhileNot(List<EventMetricData> metricData, Predicate<Atom> atomMatcher)165 static void dropWhileNot(List<EventMetricData> metricData, Predicate<Atom> atomMatcher) { 166 int firstStateIdx; 167 for (firstStateIdx = 0; firstStateIdx < metricData.size(); firstStateIdx++) { 168 final Atom atom = metricData.get(firstStateIdx).getAtom(); 169 if (atomMatcher.test(atom)) { 170 break; 171 } 172 } 173 if (firstStateIdx == 0) { 174 // First first element already is in state, so there's nothing to do. 175 return; 176 } 177 metricData.subList(0, firstStateIdx).clear(); 178 } 179 180 /** Returns the UID of the host, which should always either be SHELL (2000) or ROOT (0). */ getHostUid()181 private int getHostUid() throws DeviceNotAvailableException { 182 String strUid = ""; 183 try { 184 strUid = mDevice.executeShellCommand("id -u"); 185 return Integer.parseInt(strUid.trim()); 186 } catch (NumberFormatException e) { 187 CLog.e("Failed to get host's uid via shell command. Found " + strUid); 188 // Fall back to alternative method... 189 if (mDevice.isAdbRoot()) { 190 return 0; // ROOT 191 } else { 192 return 2000; // SHELL 193 } 194 } 195 } 196 197 /** 198 * Execute a shell command on device and get the results of 199 * that as a proto of the given type. 200 * 201 * @param parser A protobuf parser object. e.g. MyProto.parser() 202 * @param command The adb shell command to run. e.g. "dumpsys fingerprint --proto" 203 * 204 * @throws DeviceNotAvailableException If there was a problem communicating with 205 * the test device. 206 * @throws InvalidProtocolBufferException If there was an error parsing 207 * the proto. Note that a 0 length buffer is not necessarily an error. 208 */ getDump(Parser<T> parser, String command)209 private <T extends MessageLite> T getDump(Parser<T> parser, String command) 210 throws DeviceNotAvailableException, InvalidProtocolBufferException { 211 final CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver(); 212 mDevice.executeShellCommand(command, receiver); 213 return parser.parseFrom(receiver.getOutput()); 214 } 215 } 216