• 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 
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