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.tradefed.util.statsd; 17 18 import com.android.internal.os.StatsdConfigProto.AtomMatcher; 19 import com.android.internal.os.StatsdConfigProto.EventMetric; 20 import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher; 21 import com.android.internal.os.StatsdConfigProto.StatsdConfig; 22 import com.android.tradefed.device.DeviceNotAvailableException; 23 import com.android.tradefed.device.ITestDevice; 24 import com.android.tradefed.log.LogUtil.CLog; 25 import com.android.tradefed.util.CommandResult; 26 import com.android.tradefed.util.FileUtil; 27 28 import com.google.common.io.Files; 29 30 import java.io.File; 31 import java.io.FileNotFoundException; 32 import java.io.IOException; 33 import java.util.Arrays; 34 import java.util.List; 35 import java.util.UUID; 36 import java.util.stream.Collectors; 37 38 /** 39 * Utility class for creating, interacting with, and pushing statsd configuration files. 40 * 41 * <p>TODO(b/118635164): Merge with device-side configuration utilities. 42 */ 43 public class ConfigUtil { 44 public enum LogSource { 45 AID_BLUETOOTH, 46 AID_GRAPHICS, 47 AID_INCIDENTD, 48 AID_RADIO, 49 AID_ROOT, 50 AID_STATSD, 51 AID_SYSTEM, 52 AID_MEDIA, 53 } 54 55 private static final String REMOVE_CONFIG_CMD = "cmd stats config remove"; 56 private static final String UPDATE_CONFIG_CMD = "cmd stats config update"; 57 58 /** 59 * Pushes an event-based configuration file to collect atoms provided in {@code eventAtomIds}. 60 * 61 * @param device where to push the configuration 62 * @param eventAtomIds a list of event atom IDs to collect 63 * @return ID of the newly pushed configuration file 64 */ pushStatsConfig(ITestDevice device, List<Integer> eventAtomIds)65 public static long pushStatsConfig(ITestDevice device, List<Integer> eventAtomIds) 66 throws IOException, DeviceNotAvailableException { 67 return pushStatsConfig(device, eventAtomIds, commonLogSources()); 68 } 69 70 /** 71 * Pushes an event-based configuration file to collect atoms provided in {@code eventAtomIds} 72 * from {@code logSources} 73 * 74 * @param device where to push the configuration 75 * @param eventAtomIds a list of event atom IDs to collect 76 * @param logSources a list of log sources from where atoms can be collected 77 * @return ID of the newly pushed configuration file 78 */ pushStatsConfig( ITestDevice device, List<Integer> eventAtomIds, List<LogSource> logSources)79 public static long pushStatsConfig( 80 ITestDevice device, List<Integer> eventAtomIds, List<LogSource> logSources) 81 throws IOException, DeviceNotAvailableException { 82 StatsdConfig config = generateStatsdConfig(eventAtomIds, logSources); 83 CLog.d( 84 "Collecting atoms [%s] with the following config: %s", 85 eventAtomIds.stream().map(String::valueOf).collect(Collectors.joining(", ")), 86 config.toString()); 87 File configFile = null; 88 try { 89 configFile = File.createTempFile("statsdconfig", ".config"); 90 Files.write(config.toByteArray(), configFile); 91 String remotePath = String.format("/data/local/tmp/%s", configFile.getName()); 92 if (device.pushFile(configFile, remotePath)) { 93 updateConfig(device, remotePath, config.getId()); 94 } else { 95 throw new RuntimeException("Failed to configuration push file to the device."); 96 } 97 return config.getId(); 98 } finally { 99 FileUtil.deleteFile(configFile); 100 } 101 } 102 103 /** 104 * Pushes a binary statsd configuration file to collect metrics 105 * 106 * @param device Test device where the binary statsd config will be pushed to 107 * @param configFile The statsd config file 108 * @return ID of the newly pushed configuration file 109 */ pushBinaryStatsConfig(ITestDevice device, File configFile)110 public static long pushBinaryStatsConfig(ITestDevice device, File configFile) 111 throws IOException, DeviceNotAvailableException { 112 if (!configFile.exists()) { 113 throw new FileNotFoundException( 114 String.format( 115 "File not found for statsd config: %s", configFile.getAbsolutePath())); 116 } 117 String remotePath = String.format("/data/local/tmp/%s", configFile.getName()); 118 if (device.pushFile(configFile, remotePath)) { 119 // For now use a fixed config Id. We will parse it from config file when necessary. 120 long configId = UUID.randomUUID().hashCode(); 121 updateConfig(device, remotePath, configId); 122 return configId; 123 } else { 124 throw new RuntimeException("Failed to push configuration file to the device."); 125 } 126 } 127 128 /** 129 * Removes a statsd configuration file by it's id, {@code configId}. 130 * 131 * @param device where to delete the configuration 132 * @param configId ID of the configuration to delete 133 */ removeConfig(ITestDevice device, long configId)134 public static void removeConfig(ITestDevice device, long configId) 135 throws DeviceNotAvailableException { 136 device.executeShellCommand(String.join(" ", REMOVE_CONFIG_CMD, String.valueOf(configId))); 137 } 138 139 /** 140 * Push a statsd configuration from a config file on device. 141 * 142 * @param device Test device where the config will be uploaded 143 * @param remotePath The path to the statsd config on the device 144 * @param configId ID of the statsd config 145 */ updateConfig(ITestDevice device, String remotePath, long configId)146 private static void updateConfig(ITestDevice device, String remotePath, long configId) 147 throws DeviceNotAvailableException { 148 CommandResult output = 149 device.executeShellV2Command( 150 String.join( 151 " ", 152 "cat", 153 remotePath, 154 "|", 155 UPDATE_CONFIG_CMD, 156 String.valueOf(configId))); 157 device.deleteFile(remotePath); 158 if (output.getStderr().contains("Error parsing")) { 159 throw new RuntimeException("Failed to parse configuration file on the device."); 160 } else if (!output.getStderr().isEmpty()) { 161 throw new RuntimeException( 162 String.format("Failed to push config with error: %s.", output.getStderr())); 163 } 164 } 165 166 /** 167 * Creates an statsd configuration file that will collect the event atoms provided in {@code 168 * eventAtomIds} from {@code logSources}. 169 * 170 * @param eventAtomIds a list of event atom IDs to collect 171 * @param logSources a list of log sources from where atoms can be collected 172 * @return the {@code StatsdConfig} device configuration 173 */ generateStatsdConfig( List<Integer> eventAtomIds, List<LogSource> logSources)174 private static StatsdConfig generateStatsdConfig( 175 List<Integer> eventAtomIds, List<LogSource> logSources) { 176 long configId = UUID.randomUUID().hashCode(); 177 StatsdConfig.Builder configBuilder = 178 StatsdConfig.newBuilder() 179 .setId(configId) 180 .addAllAllowedLogSource( 181 logSources.stream().map(Enum::name).collect(Collectors.toList())); 182 // Add all event atom matchers. 183 for (Integer id : eventAtomIds) { 184 long atomMatcherId = UUID.randomUUID().hashCode(); 185 long eventMatcherId = UUID.randomUUID().hashCode(); 186 configBuilder = 187 configBuilder 188 .addAtomMatcher( 189 AtomMatcher.newBuilder() 190 .setId(atomMatcherId) 191 .setSimpleAtomMatcher( 192 SimpleAtomMatcher.newBuilder().setAtomId(id))) 193 .addEventMetric( 194 EventMetric.newBuilder() 195 .setId(eventMatcherId) 196 .setWhat(atomMatcherId)); 197 } 198 return configBuilder.build(); 199 } 200 201 /** Returns a list of common trusted log sources. */ commonLogSources()202 private static List<LogSource> commonLogSources() { 203 return Arrays.asList( 204 LogSource.AID_BLUETOOTH, 205 LogSource.AID_GRAPHICS, 206 LogSource.AID_INCIDENTD, 207 LogSource.AID_RADIO, 208 LogSource.AID_ROOT, 209 LogSource.AID_STATSD, 210 LogSource.AID_SYSTEM); 211 } 212 } 213 214