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 17 package com.android.helpers; 18 19 import android.content.Context; 20 import android.app.StatsManager; 21 import android.app.StatsManager.StatsUnavailableException; 22 import android.os.SystemClock; 23 import android.util.Log; 24 import android.util.StatsLog; 25 import androidx.test.InstrumentationRegistry; 26 27 import com.android.internal.os.StatsdConfigProto.AtomMatcher; 28 import com.android.internal.os.StatsdConfigProto.EventMetric; 29 import com.android.internal.os.StatsdConfigProto.FieldFilter; 30 import com.android.internal.os.StatsdConfigProto.GaugeMetric; 31 import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher; 32 import com.android.internal.os.StatsdConfigProto.StatsdConfig; 33 import com.android.internal.os.StatsdConfigProto.TimeUnit; 34 import com.android.os.AtomsProto.Atom; 35 import com.android.os.StatsLog.ConfigMetricsReport; 36 import com.android.os.StatsLog.ConfigMetricsReportList; 37 import com.android.os.StatsLog.EventMetricData; 38 import com.android.os.StatsLog.GaugeMetricData; 39 import com.android.os.StatsLog.StatsLogReport; 40 41 import com.google.protobuf.InvalidProtocolBufferException; 42 43 import java.util.ArrayList; 44 import java.util.List; 45 import java.util.UUID; 46 47 /** 48 * StatsdHelper consist of basic utilities that will be used to setup statsd 49 * config, parse the collected information and remove the statsd config. 50 */ 51 public class StatsdHelper { 52 private static final String LOG_TAG = StatsdHelper.class.getSimpleName(); 53 private static final long MAX_ATOMS = 2000; 54 private static final long METRIC_DELAY_MS = 3000; 55 private long mConfigId = -1; 56 private StatsManager mStatsManager; 57 58 /** 59 * Add simple event configurations using a list of atom ids. 60 * 61 * @param atomIdList uniquely identifies the information that we need to track by statsManager. 62 * @return true if the configuration is added successfully other wise false. 63 */ addEventConfig(List<Integer> atomIdList)64 public boolean addEventConfig(List<Integer> atomIdList) { 65 long configId = System.currentTimeMillis(); 66 StatsdConfig.Builder statsConfigBuilder = getSimpleSources(configId); 67 68 for (Integer atomId : atomIdList) { 69 int atomUniqueId = getUniqueId(); 70 statsConfigBuilder 71 .addEventMetric( 72 EventMetric.newBuilder() 73 .setId(getUniqueId()) 74 .setWhat(atomUniqueId)) 75 .addAtomMatcher(getSimpleAtomMatcher(atomUniqueId, atomId)); 76 } 77 try { 78 adoptShellIdentity(); 79 getStatsManager().addConfig(configId, statsConfigBuilder.build().toByteArray()); 80 dropShellIdentity(); 81 } catch (Exception e) { 82 Log.e(LOG_TAG, "Not able to setup the event config.", e); 83 return false; 84 } 85 Log.i(LOG_TAG, "Successfully added config with config-id:" + configId); 86 setConfigId(configId); 87 return true; 88 } 89 90 /** 91 * Build gauge metric config based on trigger events (i.e AppBreadCrumbReported). 92 * Whenever the events are triggered via StatsLog.logEvent() collect the gauge metrics. 93 * It doesn't matter what the log event is. It could be 0 or 1. 94 * In order to capture the usage during the test take the difference of gauge metrics 95 * before and after the test. 96 * 97 * @param atomIdList List of atoms to be collected in gauge metrics. 98 * @return if the config is added successfully otherwise false. 99 */ addGaugeConfig(List<Integer> atomIdList)100 public boolean addGaugeConfig(List<Integer> atomIdList) { 101 long configId = System.currentTimeMillis(); 102 StatsdConfig.Builder statsConfigBuilder = getSimpleSources(configId); 103 int appBreadCrumbUniqueId = getUniqueId(); 104 105 // Needed for collecting gauge metric based on trigger events. 106 statsConfigBuilder.addAtomMatcher(getSimpleAtomMatcher(appBreadCrumbUniqueId, 107 Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)); 108 109 for (Integer atomId : atomIdList) { 110 int atomUniqueId = getUniqueId(); 111 // Build Gauge metric config. 112 GaugeMetric.Builder gaugeMetric = GaugeMetric.newBuilder() 113 .setId(getUniqueId()) 114 .setWhat(atomUniqueId) 115 .setGaugeFieldsFilter(FieldFilter.newBuilder().setIncludeAll(true).build()) 116 .setMaxNumGaugeAtomsPerBucket(MAX_ATOMS) 117 .setSamplingType(GaugeMetric.SamplingType.FIRST_N_SAMPLES) 118 .setTriggerEvent(appBreadCrumbUniqueId) 119 .setBucket(TimeUnit.CTS); 120 121 // add the gauge config. 122 statsConfigBuilder.addAtomMatcher(getSimpleAtomMatcher(atomUniqueId, atomId)) 123 .addGaugeMetric(gaugeMetric.build()); 124 } 125 126 try { 127 adoptShellIdentity(); 128 getStatsManager().addConfig(configId, 129 statsConfigBuilder.build().toByteArray()); 130 StatsLog.logEvent(0); 131 // Dump the counters before the test started. 132 SystemClock.sleep(METRIC_DELAY_MS); 133 dropShellIdentity(); 134 } catch (Exception e) { 135 Log.e(LOG_TAG, "Not able to setup the gauge config.", e); 136 return false; 137 } 138 139 Log.i(LOG_TAG, "Successfully added config with config-id:" + configId); 140 setConfigId(configId); 141 return true; 142 } 143 144 /** 145 * Create simple atom matcher with the given id and the field id. 146 * 147 * @param id 148 * @param fieldId 149 * @return 150 */ getSimpleAtomMatcher(int id, int fieldId)151 private AtomMatcher.Builder getSimpleAtomMatcher(int id, int fieldId) { 152 return AtomMatcher.newBuilder() 153 .setId(id) 154 .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder() 155 .setAtomId(fieldId)); 156 } 157 158 /** 159 * List of authorized source that can write the information into statsd. 160 * 161 * @param configId unique id of the configuration tracked by StatsManager. 162 * @return 163 */ getSimpleSources(long configId)164 private static StatsdConfig.Builder getSimpleSources(long configId) { 165 return StatsdConfig.newBuilder().setId(configId) 166 .addAllowedLogSource("AID_ROOT") 167 .addAllowedLogSource("AID_SYSTEM") 168 .addAllowedLogSource("AID_RADIO") 169 .addAllowedLogSource("AID_BLUETOOTH") 170 .addAllowedLogSource("AID_GRAPHICS") 171 .addAllowedLogSource("AID_STATSD") 172 .addAllowedLogSource("AID_INCIENTD"); 173 } 174 175 /** 176 * Returns the list of EventMetricData tracked under the config. 177 */ getEventMetrics()178 public List<EventMetricData> getEventMetrics() { 179 ConfigMetricsReportList reportList = null; 180 List<EventMetricData> eventData = new ArrayList<>(); 181 try { 182 if (getConfigId() != -1) { 183 adoptShellIdentity(); 184 reportList = ConfigMetricsReportList.parser() 185 .parseFrom(getStatsManager().getReports(getConfigId())); 186 dropShellIdentity(); 187 } 188 } catch (InvalidProtocolBufferException | StatsUnavailableException se) { 189 Log.e(LOG_TAG, "Retreiving event metrics failed.", se); 190 return eventData; 191 } 192 193 if (reportList != null) { 194 ConfigMetricsReport configReport = reportList.getReports(0); 195 for (StatsLogReport metric : configReport.getMetricsList()) { 196 eventData.addAll(metric.getEventMetrics().getDataList()); 197 } 198 } 199 Log.i(LOG_TAG, "Number of events: " + eventData.size()); 200 return eventData; 201 } 202 203 /** 204 * Returns the list of GaugeMetric data tracked under the config. 205 */ getGaugeMetrics()206 public List<GaugeMetricData> getGaugeMetrics() { 207 ConfigMetricsReportList reportList = null; 208 List<GaugeMetricData> gaugeData = new ArrayList<>(); 209 try { 210 if (getConfigId() != -1) { 211 adoptShellIdentity(); 212 StatsLog.logEvent(0); 213 // Dump the the counters after the test completed. 214 SystemClock.sleep(METRIC_DELAY_MS); 215 reportList = ConfigMetricsReportList.parser() 216 .parseFrom(getStatsManager().getReports(getConfigId())); 217 dropShellIdentity(); 218 } 219 } catch (InvalidProtocolBufferException | StatsUnavailableException se) { 220 Log.e(LOG_TAG, "Retreiving gauge metrics failed.", se); 221 return gaugeData; 222 } 223 224 if (reportList != null) { 225 ConfigMetricsReport configReport = reportList.getReports(0); 226 for (StatsLogReport metric : configReport.getMetricsList()) { 227 gaugeData.addAll(metric.getGaugeMetrics().getDataList()); 228 } 229 } 230 Log.i(LOG_TAG, "Number of Gauge data: " + gaugeData.size()); 231 return gaugeData; 232 } 233 234 /** 235 * Remove the existing config tracked in the statsd. 236 * 237 * @return true if the config is removed successfully otherwise false. 238 */ removeStatsConfig()239 public boolean removeStatsConfig() { 240 Log.i(LOG_TAG, "Removing statsd config-id: " + getConfigId()); 241 try { 242 adoptShellIdentity(); 243 getStatsManager().removeConfig(getConfigId()); 244 dropShellIdentity(); 245 Log.i(LOG_TAG, "Successfully removed config-id: " + getConfigId()); 246 return true; 247 } catch (StatsUnavailableException e) { 248 Log.e(LOG_TAG, String.format("Not able to remove the config-id: %d due to %s ", 249 getConfigId(), e.getMessage())); 250 return false; 251 } 252 } 253 254 /** 255 * StatsManager used to configure, collect and remove the statsd config. 256 * 257 * @return StatsManager 258 */ getStatsManager()259 private StatsManager getStatsManager() { 260 if (mStatsManager == null) { 261 mStatsManager = (StatsManager) InstrumentationRegistry.getTargetContext(). 262 getSystemService(Context.STATS_MANAGER); 263 } 264 return mStatsManager; 265 } 266 267 /** 268 * Returns the package name for the UID if it is available. Otherwise return null. 269 * 270 * @param uid 271 * @return 272 */ getPackageName(int uid)273 public String getPackageName(int uid) { 274 String pkgName = InstrumentationRegistry.getTargetContext().getPackageManager() 275 .getNameForUid(uid); 276 // Remove the UID appended at the end of the package name. 277 if (pkgName != null) { 278 String pkgNameSplit[] = pkgName.split(String.format("\\:%d", uid)); 279 return pkgNameSplit[0]; 280 } 281 return pkgName; 282 } 283 284 /** 285 * Set the config id tracked in the statsd. 286 */ setConfigId(long configId)287 private void setConfigId(long configId) { 288 mConfigId = configId; 289 } 290 291 /** 292 * Return the config id tracked in the statsd. 293 */ getConfigId()294 private long getConfigId() { 295 return mConfigId; 296 } 297 298 /** 299 * Returns the unique id 300 */ getUniqueId()301 private int getUniqueId() { 302 return UUID.randomUUID().hashCode(); 303 } 304 305 /** 306 * Adopts shell permission identity needed to access StatsManager service 307 */ adoptShellIdentity()308 public static void adoptShellIdentity() { 309 InstrumentationRegistry.getInstrumentation().getUiAutomation() 310 .adoptShellPermissionIdentity(); 311 } 312 313 /** 314 * Drop shell permission identity 315 */ dropShellIdentity()316 public static void dropShellIdentity() { 317 InstrumentationRegistry.getInstrumentation().getUiAutomation() 318 .dropShellPermissionIdentity(); 319 } 320 321 } 322