| /* |
| * Copyright (C) 2020 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.cts.statsdatom.lib; |
| |
| import static com.google.common.truth.Truth.assertWithMessage; |
| |
| import com.android.os.AtomsProto; |
| import com.android.os.AtomsProto.AppBreadcrumbReported; |
| import com.android.os.StatsLog; |
| import com.android.tradefed.device.DeviceNotAvailableException; |
| import com.android.tradefed.device.ITestDevice; |
| import com.android.tradefed.log.LogUtil; |
| import com.android.utils.SparseIntArray; |
| |
| import com.google.common.collect.Range; |
| |
| import java.util.List; |
| import java.util.Set; |
| import java.util.function.Function; |
| |
| /** |
| * Contains miscellaneous helper functions that are used in statsd atom tests |
| */ |
| public final class AtomTestUtils { |
| |
| public static final int WAIT_TIME_SHORT = 500; |
| public static final int WAIT_TIME_LONG = 1000; |
| |
| public static final long NS_PER_SEC = (long) 1E+9; |
| |
| /** |
| * Sends an AppBreadcrumbReported atom to statsd. For GaugeMetrics that are added using |
| * ConfigUtils, pulls are triggered when statsd receives an AppBreadcrumbReported atom, so |
| * calling this function is necessary for gauge data to be acquired. |
| * |
| * @param device test device can be retrieved using getDevice() |
| */ |
| public static void sendAppBreadcrumbReportedAtom(ITestDevice device) |
| throws DeviceNotAvailableException { |
| String cmd = String.format("cmd stats log-app-breadcrumb %d %d", /*label=*/1, |
| AppBreadcrumbReported.State.START.ordinal()); |
| device.executeShellCommand(cmd); |
| } |
| |
| /** |
| * Asserts that each set of states in {@code stateSets} occurs in {@code data} without assuming |
| * the order of occurrence. |
| * |
| * @param stateSets A list of set of states, where each set represents an equivalent |
| * state of the device for the purpose of CTS. |
| * @param data list of EventMetricData from statsd, produced by |
| * getReportMetricListData() |
| * @param getStateFromAtom expression that takes in an Atom and returns the state it contains |
| */ |
| public static void assertStatesOccurred(List<Set<Integer>> stateSets, |
| List<StatsLog.EventMetricData> data, |
| Function<AtomsProto.Atom, Integer> getStateFromAtom) { |
| // Sometimes, there are more events than there are states. |
| // Eg: When the screen turns off, it may go into OFF and then DOZE immediately. |
| assertWithMessage("Number of result states").that(data.size()).isAtLeast(stateSets.size()); |
| final SparseIntArray dataStateCount = new SparseIntArray(); |
| for (StatsLog.EventMetricData emd : data) { |
| final int state = getStateFromAtom.apply(emd.getAtom()); |
| dataStateCount.put(state, dataStateCount.get(state, 0) + 1); |
| } |
| for (Set<Integer> states : stateSets) { |
| for (int state : states) { |
| final int count = dataStateCount.get(state); |
| assertWithMessage("Remaining count of result state (%s)", state) |
| .that(count).isGreaterThan(0); |
| dataStateCount.put(state, count - 1); |
| } |
| } |
| } |
| |
| /** |
| * Asserts that each set of states in stateSets occurs at least once in data. |
| * Asserts that the states in data occur in the same order as the sets in stateSets. |
| * |
| * @param stateSets A list of set of states, where each set represents an equivalent |
| * state of the device for the purpose of CTS. |
| * @param data list of EventMetricData from statsd, produced by |
| * getReportMetricListData() |
| * @param wait expected duration (in ms) between state changes; asserts that the |
| * actual wait |
| * time was wait/2 <= actual_wait <= 5*wait. Use 0 to ignore this |
| * assertion. |
| * @param getStateFromAtom expression that takes in an Atom and returns the state it contains |
| */ |
| public static void assertStatesOccurredInOrder(List<Set<Integer>> stateSets, |
| List<StatsLog.EventMetricData> data, |
| int wait, Function<AtomsProto.Atom, Integer> getStateFromAtom) { |
| // Sometimes, there are more events than there are states. |
| // Eg: When the screen turns off, it may go into OFF and then DOZE immediately. |
| assertWithMessage("Number of result states").that(data.size()).isAtLeast(stateSets.size()); |
| int stateSetIndex = 0; // Tracks which state set we expect the data to be in. |
| for (int dataIndex = 0; dataIndex < data.size(); dataIndex++) { |
| AtomsProto.Atom atom = data.get(dataIndex).getAtom(); |
| int state = getStateFromAtom.apply(atom); |
| // If state is in the current state set, we do not assert anything. |
| // If it is not, we expect to have transitioned to the next state set. |
| if (stateSets.get(stateSetIndex).contains(state)) { |
| // No need to assert anything. Just log it. |
| LogUtil.CLog.i("The following atom at dataIndex=" + dataIndex + " is " |
| + "in stateSetIndex " + stateSetIndex + ":\n" |
| + data.get(dataIndex).getAtom().toString()); |
| } else { |
| stateSetIndex += 1; |
| LogUtil.CLog.i("Assert that the following atom at dataIndex=" + dataIndex + " is" |
| + " in stateSetIndex " + stateSetIndex + ":\n" |
| + data.get(dataIndex).getAtom().toString()); |
| assertWithMessage("Missed first state").that(dataIndex).isNotEqualTo(0); |
| assertWithMessage("Too many states").that(stateSetIndex) |
| .isLessThan(stateSets.size()); |
| assertWithMessage(String.format("Is in wrong state (%d)", state)) |
| .that(stateSets.get(stateSetIndex)).contains(state); |
| if (wait > 0) { |
| assertTimeDiffBetween(data.get(dataIndex - 1), data.get(dataIndex), |
| wait / 2, wait * 5); |
| } |
| } |
| } |
| assertWithMessage("Too few states").that(stateSetIndex).isEqualTo(stateSets.size() - 1); |
| } |
| |
| /** |
| * Asserts that the two events are within the specified range of each other. |
| * |
| * @param d0 the event that should occur first |
| * @param d1 the event that should occur second |
| * @param minDiffMs d0 should precede d1 by at least this amount |
| * @param maxDiffMs d0 should precede d1 by at most this amount |
| */ |
| public static void assertTimeDiffBetween( |
| StatsLog.EventMetricData d0, StatsLog.EventMetricData d1, |
| int minDiffMs, int maxDiffMs) { |
| long diffMs = (d1.getElapsedTimestampNanos() - d0.getElapsedTimestampNanos()) / 1_000_000; |
| assertWithMessage("Illegal time difference") |
| .that(diffMs).isIn(Range.closed((long) minDiffMs, (long) maxDiffMs)); |
| } |
| |
| // Checks that a timestamp has been truncated to be a multiple of 5 min |
| public static void assertTimestampIsTruncated(long timestampNs) { |
| long fiveMinutesInNs = NS_PER_SEC * 5 * 60; |
| assertWithMessage("Timestamp is not truncated") |
| .that(timestampNs % fiveMinutesInNs).isEqualTo(0); |
| } |
| |
| /** |
| * Removes all elements from data prior to the first occurrence of an element of state. After |
| * this method is called, the first element of data (if non-empty) is guaranteed to be an |
| * element in state. |
| * |
| * @param getStateFromAtom expression that takes in an Atom and returns the state it contains |
| */ |
| public static void popUntilFind(List<StatsLog.EventMetricData> data, Set<Integer> state, |
| Function<AtomsProto.Atom, Integer> getStateFromAtom) { |
| int firstStateIdx; |
| for (firstStateIdx = 0; firstStateIdx < data.size(); firstStateIdx++) { |
| AtomsProto.Atom atom = data.get(firstStateIdx).getAtom(); |
| if (state.contains(getStateFromAtom.apply(atom))) { |
| break; |
| } |
| } |
| if (firstStateIdx == 0) { |
| // First first element already is in state, so there's nothing to do. |
| return; |
| } |
| data.subList(0, firstStateIdx).clear(); |
| } |
| |
| /** |
| * Removes all elements from data after the last occurrence of an element of state. After this |
| * method is called, the last element of data (if non-empty) is guaranteed to be an element in |
| * state. |
| * |
| * @param getStateFromAtom expression that takes in an Atom and returns the state it contains |
| */ |
| public static void popUntilFindFromEnd(List<StatsLog.EventMetricData> data, Set<Integer> state, |
| Function<AtomsProto.Atom, Integer> getStateFromAtom) { |
| int lastStateIdx; |
| for (lastStateIdx = data.size() - 1; lastStateIdx >= 0; lastStateIdx--) { |
| AtomsProto.Atom atom = data.get(lastStateIdx).getAtom(); |
| if (state.contains(getStateFromAtom.apply(atom))) { |
| break; |
| } |
| } |
| if (lastStateIdx == data.size() - 1) { |
| // Last element already is in state, so there's nothing to do. |
| return; |
| } |
| data.subList(lastStateIdx + 1, data.size()).clear(); |
| } |
| |
| private AtomTestUtils() {} |
| } |