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