• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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 android.cts.statsdatom.lib;
18 
19 import static com.google.common.truth.Truth.assertWithMessage;
20 
21 import com.android.os.AtomsProto;
22 import com.android.os.AtomsProto.AppBreadcrumbReported;
23 import com.android.os.StatsLog;
24 import com.android.tradefed.device.DeviceNotAvailableException;
25 import com.android.tradefed.device.ITestDevice;
26 import com.android.tradefed.log.LogUtil;
27 import com.android.utils.SparseIntArray;
28 
29 import com.google.common.collect.Range;
30 
31 import java.util.List;
32 import java.util.Set;
33 import java.util.function.Function;
34 
35 /**
36  * Contains miscellaneous helper functions that are used in statsd atom tests
37  */
38 public final class AtomTestUtils {
39 
40     public static final int WAIT_TIME_SHORT = 500;
41     public static final int WAIT_TIME_LONG = 1000;
42 
43     public static final long NS_PER_SEC = (long) 1E+9;
44 
45     /**
46      * Sends an AppBreadcrumbReported atom to statsd. For GaugeMetrics that are added using
47      * ConfigUtils, pulls are triggered when statsd receives an AppBreadcrumbReported atom, so
48      * calling this function is necessary for gauge data to be acquired.
49      *
50      * @param device test device can be retrieved using getDevice()
51      */
sendAppBreadcrumbReportedAtom(ITestDevice device)52     public static void sendAppBreadcrumbReportedAtom(ITestDevice device)
53             throws DeviceNotAvailableException {
54         String cmd = String.format("cmd stats log-app-breadcrumb %d %d", /*label=*/1,
55                 AppBreadcrumbReported.State.START.ordinal());
56         device.executeShellCommand(cmd);
57     }
58 
59     /**
60      * Asserts that each set of states in {@code stateSets} occurs in {@code data} without assuming
61      * the order of occurrence.
62      *
63      * @param stateSets        A list of set of states, where each set represents an equivalent
64      *                         state of the device for the purpose of CTS.
65      * @param data             list of EventMetricData from statsd, produced by
66      *                         getReportMetricListData()
67      * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
68      */
assertStatesOccurred(List<Set<Integer>> stateSets, List<StatsLog.EventMetricData> data, Function<AtomsProto.Atom, Integer> getStateFromAtom)69     public static void assertStatesOccurred(List<Set<Integer>> stateSets,
70             List<StatsLog.EventMetricData> data,
71             Function<AtomsProto.Atom, Integer> getStateFromAtom) {
72         // Sometimes, there are more events than there are states.
73         // Eg: When the screen turns off, it may go into OFF and then DOZE immediately.
74         assertWithMessage("Number of result states").that(data.size()).isAtLeast(stateSets.size());
75         final SparseIntArray dataStateCount = new SparseIntArray();
76         for (StatsLog.EventMetricData emd : data) {
77             final int state = getStateFromAtom.apply(emd.getAtom());
78             dataStateCount.put(state, dataStateCount.get(state, 0) + 1);
79         }
80         for (Set<Integer> states : stateSets) {
81             for (int state : states) {
82                 final int count = dataStateCount.get(state);
83                 assertWithMessage("Remaining count of result state (%s)", state)
84                         .that(count).isGreaterThan(0);
85                 dataStateCount.put(state, count - 1);
86             }
87         }
88     }
89 
90     /**
91      * Asserts that each set of states in stateSets occurs at least once in data.
92      * Asserts that the states in data occur in the same order as the sets in stateSets.
93      *
94      * @param stateSets        A list of set of states, where each set represents an equivalent
95      *                         state of the device for the purpose of CTS.
96      * @param data             list of EventMetricData from statsd, produced by
97      *                         getReportMetricListData()
98      * @param wait             expected duration (in ms) between state changes; asserts that the
99      *                         actual wait
100      *                         time was wait/2 <= actual_wait <= 5*wait. Use 0 to ignore this
101      *                         assertion.
102      * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
103      */
assertStatesOccurredInOrder(List<Set<Integer>> stateSets, List<StatsLog.EventMetricData> data, int wait, Function<AtomsProto.Atom, Integer> getStateFromAtom)104     public static void assertStatesOccurredInOrder(List<Set<Integer>> stateSets,
105             List<StatsLog.EventMetricData> data,
106             int wait, Function<AtomsProto.Atom, Integer> getStateFromAtom) {
107         // Sometimes, there are more events than there are states.
108         // Eg: When the screen turns off, it may go into OFF and then DOZE immediately.
109         assertWithMessage("Number of result states").that(data.size()).isAtLeast(stateSets.size());
110         int stateSetIndex = 0; // Tracks which state set we expect the data to be in.
111         for (int dataIndex = 0; dataIndex < data.size(); dataIndex++) {
112             AtomsProto.Atom atom = data.get(dataIndex).getAtom();
113             int state = getStateFromAtom.apply(atom);
114             // If state is in the current state set, we do not assert anything.
115             // If it is not, we expect to have transitioned to the next state set.
116             if (stateSets.get(stateSetIndex).contains(state)) {
117                 // No need to assert anything. Just log it.
118                 LogUtil.CLog.i("The following atom at dataIndex=" + dataIndex + " is "
119                         + "in stateSetIndex " + stateSetIndex + ":\n"
120                         + data.get(dataIndex).getAtom().toString());
121             } else {
122                 stateSetIndex += 1;
123                 LogUtil.CLog.i("Assert that the following atom at dataIndex=" + dataIndex + " is"
124                         + " in stateSetIndex " + stateSetIndex + ":\n"
125                         + data.get(dataIndex).getAtom().toString());
126                 assertWithMessage("Missed first state").that(dataIndex).isNotEqualTo(0);
127                 assertWithMessage("Too many states").that(stateSetIndex)
128                         .isLessThan(stateSets.size());
129                 assertWithMessage(String.format("Is in wrong state (%d)", state))
130                         .that(stateSets.get(stateSetIndex)).contains(state);
131                 if (wait > 0) {
132                     assertTimeDiffBetween(data.get(dataIndex - 1), data.get(dataIndex),
133                             wait / 2, wait * 5);
134                 }
135             }
136         }
137         assertWithMessage("Too few states").that(stateSetIndex).isEqualTo(stateSets.size() - 1);
138     }
139 
140     /**
141      * Asserts that the two events are within the specified range of each other.
142      *
143      * @param d0        the event that should occur first
144      * @param d1        the event that should occur second
145      * @param minDiffMs d0 should precede d1 by at least this amount
146      * @param maxDiffMs d0 should precede d1 by at most this amount
147      */
assertTimeDiffBetween( StatsLog.EventMetricData d0, StatsLog.EventMetricData d1, int minDiffMs, int maxDiffMs)148     public static void assertTimeDiffBetween(
149             StatsLog.EventMetricData d0, StatsLog.EventMetricData d1,
150             int minDiffMs, int maxDiffMs) {
151         long diffMs = (d1.getElapsedTimestampNanos() - d0.getElapsedTimestampNanos()) / 1_000_000;
152         assertWithMessage("Illegal time difference")
153                 .that(diffMs).isIn(Range.closed((long) minDiffMs, (long) maxDiffMs));
154     }
155 
156     // Checks that a timestamp has been truncated to be a multiple of 5 min
assertTimestampIsTruncated(long timestampNs)157     public static void assertTimestampIsTruncated(long timestampNs) {
158         long fiveMinutesInNs = NS_PER_SEC * 5 * 60;
159         assertWithMessage("Timestamp is not truncated")
160                 .that(timestampNs % fiveMinutesInNs).isEqualTo(0);
161     }
162 
163     /**
164      * Removes all elements from data prior to the first occurrence of an element of state. After
165      * this method is called, the first element of data (if non-empty) is guaranteed to be an
166      * element in state.
167      *
168      * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
169      */
popUntilFind(List<StatsLog.EventMetricData> data, Set<Integer> state, Function<AtomsProto.Atom, Integer> getStateFromAtom)170     public static void popUntilFind(List<StatsLog.EventMetricData> data, Set<Integer> state,
171             Function<AtomsProto.Atom, Integer> getStateFromAtom) {
172         int firstStateIdx;
173         for (firstStateIdx = 0; firstStateIdx < data.size(); firstStateIdx++) {
174             AtomsProto.Atom atom = data.get(firstStateIdx).getAtom();
175             if (state.contains(getStateFromAtom.apply(atom))) {
176                 break;
177             }
178         }
179         if (firstStateIdx == 0) {
180             // First first element already is in state, so there's nothing to do.
181             return;
182         }
183         data.subList(0, firstStateIdx).clear();
184     }
185 
186     /**
187      * Removes all elements from data after the last occurrence of an element of state. After this
188      * method is called, the last element of data (if non-empty) is guaranteed to be an element in
189      * state.
190      *
191      * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
192      */
popUntilFindFromEnd(List<StatsLog.EventMetricData> data, Set<Integer> state, Function<AtomsProto.Atom, Integer> getStateFromAtom)193     public static void popUntilFindFromEnd(List<StatsLog.EventMetricData> data, Set<Integer> state,
194             Function<AtomsProto.Atom, Integer> getStateFromAtom) {
195         int lastStateIdx;
196         for (lastStateIdx = data.size() - 1; lastStateIdx >= 0; lastStateIdx--) {
197             AtomsProto.Atom atom = data.get(lastStateIdx).getAtom();
198             if (state.contains(getStateFromAtom.apply(atom))) {
199                 break;
200             }
201         }
202         if (lastStateIdx == data.size() - 1) {
203             // Last element already is in state, so there's nothing to do.
204             return;
205         }
206         data.subList(lastStateIdx + 1, data.size()).clear();
207     }
208 
AtomTestUtils()209     private AtomTestUtils() {}
210 }
211