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