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, otherwise 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 statsConfigBuilder.addWhitelistedAtomIds(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER); 109 110 for (Integer atomId : atomIdList) { 111 int atomUniqueId = getUniqueId(); 112 // Build Gauge metric config. 113 GaugeMetric.Builder gaugeMetric = GaugeMetric.newBuilder() 114 .setId(getUniqueId()) 115 .setWhat(atomUniqueId) 116 .setGaugeFieldsFilter(FieldFilter.newBuilder().setIncludeAll(true).build()) 117 .setMaxNumGaugeAtomsPerBucket(MAX_ATOMS) 118 .setSamplingType(GaugeMetric.SamplingType.FIRST_N_SAMPLES) 119 .setTriggerEvent(appBreadCrumbUniqueId) 120 .setBucket(TimeUnit.CTS); 121 122 // add the gauge config. 123 statsConfigBuilder.addAtomMatcher(getSimpleAtomMatcher(atomUniqueId, atomId)) 124 .addGaugeMetric(gaugeMetric.build()); 125 } 126 127 try { 128 adoptShellIdentity(); 129 getStatsManager().addConfig(configId, 130 statsConfigBuilder.build().toByteArray()); 131 StatsLog.logEvent(0); 132 // Dump the counters before the test started. 133 SystemClock.sleep(METRIC_DELAY_MS); 134 dropShellIdentity(); 135 } catch (Exception e) { 136 Log.e(LOG_TAG, "Not able to setup the gauge config.", e); 137 return false; 138 } 139 140 Log.i(LOG_TAG, "Successfully added config with config-id:" + configId); 141 setConfigId(configId); 142 return true; 143 } 144 145 /** 146 * Create simple atom matcher with the given id and the field id. 147 * 148 * @param id 149 * @param fieldId 150 * @return 151 */ getSimpleAtomMatcher(int id, int fieldId)152 private AtomMatcher.Builder getSimpleAtomMatcher(int id, int fieldId) { 153 return AtomMatcher.newBuilder() 154 .setId(id) 155 .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder() 156 .setAtomId(fieldId)); 157 } 158 159 /** 160 * List of authorized source that can write the information into statsd. 161 * 162 * @param configId unique id of the configuration tracked by StatsManager. 163 * @return 164 */ getSimpleSources(long configId)165 private static StatsdConfig.Builder getSimpleSources(long configId) { 166 return StatsdConfig.newBuilder() 167 .setId(configId) 168 .addAllowedLogSource("AID_ROOT") 169 .addAllowedLogSource("AID_SYSTEM") 170 .addAllowedLogSource("AID_RADIO") 171 .addAllowedLogSource("AID_BLUETOOTH") 172 .addAllowedLogSource("AID_GRAPHICS") 173 .addAllowedLogSource("AID_STATSD") 174 .addAllowedLogSource("AID_INCIENTD") 175 .addDefaultPullPackages("AID_SYSTEM") 176 .addDefaultPullPackages("AID_RADIO") 177 .addDefaultPullPackages("AID_STATSD") 178 .addDefaultPullPackages("AID_GPU_SERVICE"); 179 } 180 181 /** 182 * Returns the list of EventMetricData tracked under the config. 183 */ getEventMetrics()184 public List<EventMetricData> getEventMetrics() { 185 ConfigMetricsReportList reportList = null; 186 List<EventMetricData> eventData = new ArrayList<>(); 187 try { 188 if (getConfigId() != -1) { 189 adoptShellIdentity(); 190 reportList = ConfigMetricsReportList.parser() 191 .parseFrom(getStatsManager().getReports(getConfigId())); 192 dropShellIdentity(); 193 } 194 } catch (InvalidProtocolBufferException | StatsUnavailableException se) { 195 Log.e(LOG_TAG, "Retreiving event metrics failed.", se); 196 return eventData; 197 } 198 199 if (reportList != null) { 200 ConfigMetricsReport configReport = reportList.getReports(0); 201 for (StatsLogReport metric : configReport.getMetricsList()) { 202 eventData.addAll(metric.getEventMetrics().getDataList()); 203 } 204 } 205 Log.i(LOG_TAG, "Number of events: " + eventData.size()); 206 return eventData; 207 } 208 209 /** 210 * Returns the list of GaugeMetric data tracked under the config. 211 */ getGaugeMetrics()212 public List<GaugeMetricData> getGaugeMetrics() { 213 ConfigMetricsReportList reportList = null; 214 List<GaugeMetricData> gaugeData = new ArrayList<>(); 215 try { 216 if (getConfigId() != -1) { 217 adoptShellIdentity(); 218 StatsLog.logEvent(0); 219 // Dump the the counters after the test completed. 220 SystemClock.sleep(METRIC_DELAY_MS); 221 reportList = ConfigMetricsReportList.parser() 222 .parseFrom(getStatsManager().getReports(getConfigId())); 223 dropShellIdentity(); 224 } 225 } catch (InvalidProtocolBufferException | StatsUnavailableException se) { 226 Log.e(LOG_TAG, "Retreiving gauge metrics failed.", se); 227 return gaugeData; 228 } 229 230 if (reportList != null) { 231 ConfigMetricsReport configReport = reportList.getReports(0); 232 for (StatsLogReport metric : configReport.getMetricsList()) { 233 gaugeData.addAll(metric.getGaugeMetrics().getDataList()); 234 } 235 } 236 Log.i(LOG_TAG, "Number of Gauge data: " + gaugeData.size()); 237 return gaugeData; 238 } 239 240 /** 241 * Remove the existing config tracked in the statsd. 242 * 243 * @return true if the config is removed successfully otherwise false. 244 */ removeStatsConfig()245 public boolean removeStatsConfig() { 246 Log.i(LOG_TAG, "Removing statsd config-id: " + getConfigId()); 247 try { 248 adoptShellIdentity(); 249 getStatsManager().removeConfig(getConfigId()); 250 dropShellIdentity(); 251 Log.i(LOG_TAG, "Successfully removed config-id: " + getConfigId()); 252 return true; 253 } catch (StatsUnavailableException e) { 254 Log.e(LOG_TAG, String.format("Not able to remove the config-id: %d due to %s ", 255 getConfigId(), e.getMessage())); 256 return false; 257 } 258 } 259 260 /** 261 * StatsManager used to configure, collect and remove the statsd config. 262 * 263 * @return StatsManager 264 */ getStatsManager()265 private StatsManager getStatsManager() { 266 if (mStatsManager == null) { 267 mStatsManager = (StatsManager) InstrumentationRegistry.getTargetContext(). 268 getSystemService(Context.STATS_MANAGER); 269 } 270 return mStatsManager; 271 } 272 273 /** 274 * Returns the package name for the UID if it is available. Otherwise return null. 275 * 276 * @param uid 277 * @return 278 */ getPackageName(int uid)279 public String getPackageName(int uid) { 280 String pkgName = InstrumentationRegistry.getTargetContext().getPackageManager() 281 .getNameForUid(uid); 282 // Remove the UID appended at the end of the package name. 283 if (pkgName != null) { 284 String pkgNameSplit[] = pkgName.split(String.format("\\:%d", uid)); 285 return pkgNameSplit[0]; 286 } 287 return pkgName; 288 } 289 290 /** 291 * Set the config id tracked in the statsd. 292 */ setConfigId(long configId)293 private void setConfigId(long configId) { 294 mConfigId = configId; 295 } 296 297 /** 298 * Return the config id tracked in the statsd. 299 */ getConfigId()300 private long getConfigId() { 301 return mConfigId; 302 } 303 304 /** 305 * Returns the unique id 306 */ getUniqueId()307 private int getUniqueId() { 308 return UUID.randomUUID().hashCode(); 309 } 310 311 /** 312 * Adopts shell permission identity needed to access StatsManager service 313 */ adoptShellIdentity()314 public static void adoptShellIdentity() { 315 InstrumentationRegistry.getInstrumentation().getUiAutomation() 316 .adoptShellPermissionIdentity(); 317 } 318 319 /** 320 * Drop shell permission identity 321 */ dropShellIdentity()322 public static void dropShellIdentity() { 323 InstrumentationRegistry.getInstrumentation().getUiAutomation() 324 .dropShellPermissionIdentity(); 325 } 326 327 } 328