• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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