• 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.textclassifier.common.statsd;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import android.app.Instrumentation;
22 import android.app.UiAutomation;
23 import android.os.ParcelFileDescriptor;
24 import android.util.Log;
25 import androidx.test.platform.app.InstrumentationRegistry;
26 import com.android.internal.os.StatsdConfigProto.AtomMatcher;
27 import com.android.internal.os.StatsdConfigProto.EventMetric;
28 import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
29 import com.android.internal.os.StatsdConfigProto.StatsdConfig;
30 import com.android.os.AtomsProto.Atom;
31 import com.android.os.StatsLog.AggregatedAtomInfo;
32 import com.android.os.StatsLog.ConfigMetricsReport;
33 import com.android.os.StatsLog.ConfigMetricsReportList;
34 import com.android.os.StatsLog.EventMetricData;
35 import com.android.os.StatsLog.StatsLogReport;
36 import com.google.common.collect.ImmutableList;
37 import com.google.common.io.ByteStreams;
38 import java.io.ByteArrayInputStream;
39 import java.io.FileInputStream;
40 import java.io.FileOutputStream;
41 import java.lang.reflect.Method;
42 import java.util.Comparator;
43 import java.util.List;
44 import java.util.stream.Collectors;
45 import javax.annotation.Nullable;
46 
47 /** Util functions to make statsd testing easier by using adb shell cmd stats commands. */
48 public class StatsdTestUtils {
49   private static final String TAG = "StatsdTestUtils";
50 
StatsdTestUtils()51   private StatsdTestUtils() {}
52 
53   /** Push a config which specifies what loggings we are interested in. */
pushConfig(StatsdConfig config)54   public static void pushConfig(StatsdConfig config) throws Exception {
55     String command = String.format("cmd stats config update %s", config.getId());
56     Log.v(TAG, "pushConfig: " + config);
57     String output = new String(runShellCommand(command, config.toByteArray()));
58     assertThat(output).isEmpty();
59   }
60 
61   /** Adds a atom matcher to capture logs with given atom tag. */
addAtomMatcher(StatsdConfig.Builder builder, int atomTag)62   public static void addAtomMatcher(StatsdConfig.Builder builder, int atomTag) {
63     final String atomName = "Atom" + atomTag;
64     final String eventName = "Event" + atomTag;
65     SimpleAtomMatcher simpleAtomMatcher = SimpleAtomMatcher.newBuilder().setAtomId(atomTag).build();
66     builder.addAtomMatcher(
67         AtomMatcher.newBuilder()
68             .setId(atomName.hashCode())
69             .setSimpleAtomMatcher(simpleAtomMatcher));
70     builder.addEventMetric(
71         EventMetric.newBuilder().setId(eventName.hashCode()).setWhat(atomName.hashCode()));
72   }
73 
74   /**
75    * Extracts logged atoms from the report, sorted by logging time, and deletes the saved report.
76    */
getLoggedAtoms(long configId, long timeoutMillis)77   public static ImmutableList<Atom> getLoggedAtoms(long configId, long timeoutMillis)
78       throws Exception {
79     // There is no callback to notify us the log is collected. So we do a wait here.
80     Thread.sleep(timeoutMillis);
81 
82     ConfigMetricsReportList reportList = getAndRemoveReportList(configId);
83     assertThat(reportList.getReportsCount()).isEqualTo(1);
84     ConfigMetricsReport report = reportList.getReports(0);
85     List<StatsLogReport> metricsList = report.getMetricsList();
86     return ImmutableList.copyOf(
87         metricsList.stream()
88             .flatMap(statsLogReport -> statsLogReport.getEventMetrics().getDataList().stream())
89             .flatMap(
90                 eventMetricData -> backfillAggregatedAtomsinEventMetric(eventMetricData).stream())
91             .sorted(Comparator.comparing(EventMetricData::getElapsedTimestampNanos))
92             .map(EventMetricData::getAtom)
93             .collect(Collectors.toList()));
94   }
95 
96   /** Removes the pushed config file and existing reports. */
cleanup(long configId)97   public static void cleanup(long configId) throws Exception {
98     runShellCommand(String.format("cmd stats config remove %d", configId), /* input= */ null);
99     // Remove existing reports.
100     getAndRemoveReportList(configId);
101   }
102 
103   /**
104    * Runs an adb shell command with the provided input and returns the command line output.
105    *
106    * @param cmd the shell command
107    * @param input the content that will be piped to the command stdin.
108    * @return the command output
109    */
runShellCommand(String cmd, @Nullable byte[] input)110   private static byte[] runShellCommand(String cmd, @Nullable byte[] input) throws Exception {
111     Log.v(TAG, "run shell command: " + cmd);
112     Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
113     UiAutomation uiAutomation = instrumentation.getUiAutomation();
114     Method method =
115         uiAutomation.getClass().getDeclaredMethod("executeShellCommandRw", String.class);
116     ParcelFileDescriptor[] pipes = (ParcelFileDescriptor[]) method.invoke(uiAutomation, cmd);
117     // Write to the input pipe.
118     try (FileOutputStream fos = new ParcelFileDescriptor.AutoCloseOutputStream(pipes[1])) {
119       if (input != null) {
120         fos.write(input);
121       }
122     }
123     // Read from the output pipe.
124     try (FileInputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(pipes[0])) {
125       return ByteStreams.toByteArray(inputStream);
126     }
127   }
128 
129   /** Gets the statsd report. Note that this also deletes that report from statsd. */
getAndRemoveReportList(long configId)130   private static ConfigMetricsReportList getAndRemoveReportList(long configId) throws Exception {
131     byte[] output =
132         runShellCommand(
133             String.format("cmd stats dump-report %d --include_current_bucket --proto", configId),
134             /*input=*/ null);
135     return ConfigMetricsReportList.parser().parseFrom(new ByteArrayInputStream(output));
136   }
137 
backfillAggregatedAtomsinEventMetric( EventMetricData metricData)138   private static ImmutableList<EventMetricData> backfillAggregatedAtomsinEventMetric(
139       EventMetricData metricData) {
140     if (metricData.hasAtom()) {
141       return ImmutableList.of(metricData);
142     }
143     ImmutableList.Builder<EventMetricData> data = ImmutableList.builder();
144     AggregatedAtomInfo atomInfo = metricData.getAggregatedAtomInfo();
145     for (long timestamp : atomInfo.getElapsedTimestampNanosList()) {
146       EventMetricData.Builder newMetricData = EventMetricData.newBuilder();
147       newMetricData.setAtom(atomInfo.getAtom());
148       newMetricData.setElapsedTimestampNanos(timestamp);
149       data.add(newMetricData.build());
150     }
151     return data.build();
152   }
153 }
154