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 private static final String REMOVE_CONFIG_CMD = "cmd stats config remove"; 45 private static final String UPDATE_CONFIG_CMD = "cmd stats config update"; 46 47 /** 48 * Pushes an event-based configuration file to collect atoms provided in {@code eventAtomIds}. 49 * 50 * @param device where to push the configuration 51 * @param eventAtomIds a list of event atom IDs to collect 52 * @return ID of the newly pushed configuration file 53 */ pushStatsConfig(ITestDevice device, List<Integer> eventAtomIds)54 public static long pushStatsConfig(ITestDevice device, List<Integer> eventAtomIds) 55 throws IOException, DeviceNotAvailableException { 56 StatsdConfig config = generateStatsdConfig(eventAtomIds); 57 CLog.d( 58 "Collecting atoms [%s] with the following config: %s", 59 eventAtomIds.stream().map(String::valueOf).collect(Collectors.joining(", ")), 60 config.toString()); 61 File configFile = null; 62 try { 63 configFile = File.createTempFile("statsdconfig", ".config"); 64 Files.write(config.toByteArray(), configFile); 65 String remotePath = String.format("/data/local/tmp/%s", configFile.getName()); 66 if (device.pushFile(configFile, remotePath)) { 67 updateConfig(device, remotePath, config.getId()); 68 } else { 69 throw new RuntimeException("Failed to configuration push file to the device."); 70 } 71 return config.getId(); 72 } finally { 73 FileUtil.deleteFile(configFile); 74 } 75 } 76 77 /** 78 * Pushes a binary statsd configuration file to collect metrics 79 * 80 * @param device Test device where the binary statsd config will be pushed to 81 * @param configFile The statsd config file 82 * @return ID of the newly pushed configuration file 83 */ pushBinaryStatsConfig(ITestDevice device, File configFile)84 public static long pushBinaryStatsConfig(ITestDevice device, File configFile) 85 throws IOException, DeviceNotAvailableException { 86 if (!configFile.exists()) { 87 throw new FileNotFoundException( 88 String.format( 89 "File not found for statsd config: %s", configFile.getAbsolutePath())); 90 } 91 String remotePath = String.format("/data/local/tmp/%s", configFile.getName()); 92 if (device.pushFile(configFile, remotePath)) { 93 // For now use a fixed config Id. We will parse it from config file when necessary. 94 long configId = UUID.randomUUID().hashCode(); 95 updateConfig(device, remotePath, configId); 96 return configId; 97 } else { 98 throw new RuntimeException("Failed to push configuration file to the device."); 99 } 100 } 101 102 /** 103 * Removes a statsd configuration file by it's id, {@code configId}. 104 * 105 * @param device where to delete the configuration 106 * @param configId ID of the configuration to delete 107 */ removeConfig(ITestDevice device, long configId)108 public static void removeConfig(ITestDevice device, long configId) 109 throws DeviceNotAvailableException { 110 device.executeShellCommand(String.join(" ", REMOVE_CONFIG_CMD, String.valueOf(configId))); 111 } 112 113 /** 114 * Push a statsd configuration from a config file on device. 115 * 116 * @param device Test device where the config will be uploaded 117 * @param remotePath The path to the statsd config on the device 118 * @param configId ID of the statsd config 119 */ updateConfig(ITestDevice device, String remotePath, long configId)120 private static void updateConfig(ITestDevice device, String remotePath, long configId) 121 throws DeviceNotAvailableException { 122 CommandResult output = 123 device.executeShellV2Command( 124 String.join( 125 " ", 126 "cat", 127 remotePath, 128 "|", 129 UPDATE_CONFIG_CMD, 130 String.valueOf(configId))); 131 device.deleteFile(remotePath); 132 if (output.getStderr().contains("Error parsing")) { 133 throw new RuntimeException("Failed to parse configuration file on the device."); 134 } else if (!output.getStderr().isEmpty()) { 135 throw new RuntimeException( 136 String.format("Failed to push config with error: %s.", output.getStderr())); 137 } 138 } 139 140 /** 141 * Creates an statsd configuration file that will collect the event atoms provided in {@code 142 * eventAtomIds}. Note this only accepts data from the list of {@code #commonLogSources} now. 143 * 144 * @param eventAtomIds a list of event atom IDs to collect 145 * @return the {@code StatsdConfig} device configuration 146 */ generateStatsdConfig(List<Integer> eventAtomIds)147 private static StatsdConfig generateStatsdConfig(List<Integer> eventAtomIds) { 148 long configId = UUID.randomUUID().hashCode(); 149 StatsdConfig.Builder configBuilder = 150 StatsdConfig.newBuilder() 151 .setId(configId) 152 .addAllAllowedLogSource(commonLogSources()); 153 // Add all event atom matchers. 154 for (Integer id : eventAtomIds) { 155 long atomMatcherId = UUID.randomUUID().hashCode(); 156 long eventMatcherId = UUID.randomUUID().hashCode(); 157 configBuilder = 158 configBuilder 159 .addAtomMatcher( 160 AtomMatcher.newBuilder() 161 .setId(atomMatcherId) 162 .setSimpleAtomMatcher( 163 SimpleAtomMatcher.newBuilder().setAtomId(id))) 164 .addEventMetric( 165 EventMetric.newBuilder() 166 .setId(eventMatcherId) 167 .setWhat(atomMatcherId)); 168 } 169 return configBuilder.build(); 170 } 171 172 /** Returns a list of common trusted log sources. */ commonLogSources()173 private static List<String> commonLogSources() { 174 return Arrays.asList( 175 "AID_BLUETOOTH", 176 "AID_GRAPHICS", 177 "AID_INCIENTD", 178 "AID_RADIO", 179 "AID_ROOT", 180 "AID_STATSD", 181 "AID_SYSTEM"); 182 } 183 } 184