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