diff --git a/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java b/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java index 881c92d308..ee8ab6824f 100644 --- a/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java +++ b/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java @@ -177,6 +177,7 @@ public class AutomaticOnOffKeepaliveTracker { private static final int MAX_EVENTS_LOGS = 40; private final LocalLog mEventLog = new LocalLog(MAX_EVENTS_LOGS); + private final KeepaliveStatsTracker mKeepaliveStatsTracker = new KeepaliveStatsTracker(); /** * Information about a managed keepalive. * @@ -421,6 +422,7 @@ public class AutomaticOnOffKeepaliveTracker { public void handleStartKeepalive(Message message) { final AutomaticOnOffKeepalive autoKi = (AutomaticOnOffKeepalive) message.obj; mEventLog.log("Start keepalive " + autoKi.mCallback + " on " + autoKi.getNetwork()); + mKeepaliveStatsTracker.onStartKeepalive(); mKeepaliveTracker.handleStartKeepalive(autoKi.mKi); // Add automatic on/off request into list to track its life cycle. @@ -438,12 +440,14 @@ public class AutomaticOnOffKeepaliveTracker { } private void handleResumeKeepalive(@NonNull final KeepaliveTracker.KeepaliveInfo ki) { + mKeepaliveStatsTracker.onResumeKeepalive(); mKeepaliveTracker.handleStartKeepalive(ki); mEventLog.log("Resumed successfully keepalive " + ki.mCallback + " on " + ki.mNai); } private void handlePauseKeepalive(@NonNull final KeepaliveTracker.KeepaliveInfo ki) { mEventLog.log("Suspend keepalive " + ki.mCallback + " on " + ki.mNai); + mKeepaliveStatsTracker.onPauseKeepalive(); // TODO : mKT.handleStopKeepalive should take a KeepaliveInfo instead mKeepaliveTracker.handleStopKeepalive(ki.getNai(), ki.getSlot(), SUCCESS_PAUSED); } @@ -467,6 +471,7 @@ public class AutomaticOnOffKeepaliveTracker { private void cleanupAutoOnOffKeepalive(@NonNull final AutomaticOnOffKeepalive autoKi) { ensureRunningOnHandlerThread(); + mKeepaliveStatsTracker.onStopKeepalive(autoKi.mAutomaticOnOffState != STATE_SUSPENDED); autoKi.close(); if (null != autoKi.mAlarmListener) mAlarmManager.cancel(autoKi.mAlarmListener); diff --git a/service/src/com/android/server/connectivity/KeepaliveStatsTracker.java b/service/src/com/android/server/connectivity/KeepaliveStatsTracker.java new file mode 100644 index 0000000000..290d201863 --- /dev/null +++ b/service/src/com/android/server/connectivity/KeepaliveStatsTracker.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2023 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 com.android.server.connectivity; + +import android.os.SystemClock; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.metrics.DailykeepaliveInfoReported; +import com.android.metrics.DurationForNumOfKeepalive; +import com.android.metrics.DurationPerNumOfKeepalive; + +import java.util.ArrayList; +import java.util.List; + +// TODO(b/273451360): Also track KeepaliveLifetimeForCarrier and DailykeepaliveInfoReported +/** + * Tracks carrier and duration metrics of automatic on/off keepalives. + * + *

This class follows AutomaticOnOffKeepaliveTracker closely and its on*Keepalive methods needs + * to be called in a timely manner to keep the metrics accurate. It is also not thread-safe and all + * public methods must be called by the same thread, namely the ConnectivityService handler thread. + */ +public class KeepaliveStatsTracker { + private static final String TAG = KeepaliveStatsTracker.class.getSimpleName(); + + private final Dependencies mDependencies; + // List of duration stats metric where the index is the number of concurrent keepalives. + // Each DurationForNumOfKeepalive message stores a registered duration and an active duration. + // Registered duration is the total time spent with mNumRegisteredKeepalive == index. + // Active duration is the total time spent with mNumActiveKeepalive == index. + private final List mDurationPerNumOfKeepalive = + new ArrayList<>(); + + private int mNumRegisteredKeepalive = 0; + private int mNumActiveKeepalive = 0; + + // A timestamp of the most recent time the duration metrics was updated. + private long mTimestampSinceLastUpdateDurations; + + /** Dependency class */ + @VisibleForTesting + public static class Dependencies { + // Returns a timestamp with the time base of SystemClock.uptimeMillis to keep durations + // relative to start time and avoid timezone change. + public long getUptimeMillis() { + return SystemClock.uptimeMillis(); + } + } + + public KeepaliveStatsTracker() { + this(new Dependencies()); + } + + @VisibleForTesting + public KeepaliveStatsTracker(Dependencies dependencies) { + mDependencies = dependencies; + mTimestampSinceLastUpdateDurations = mDependencies.getUptimeMillis(); + } + + /** Ensures the list of duration metrics is large enough for number of registered keepalives. */ + private void ensureDurationPerNumOfKeepaliveSize() { + if (mNumActiveKeepalive < 0 || mNumRegisteredKeepalive < 0) { + throw new IllegalStateException( + "Number of active or registered keepalives is negative"); + } + if (mNumActiveKeepalive > mNumRegisteredKeepalive) { + throw new IllegalStateException( + "Number of active keepalives greater than registered keepalives"); + } + + while (mDurationPerNumOfKeepalive.size() <= mNumRegisteredKeepalive) { + final DurationForNumOfKeepalive.Builder durationForNumOfKeepalive = + DurationForNumOfKeepalive.newBuilder(); + durationForNumOfKeepalive.setNumOfKeepalive(mDurationPerNumOfKeepalive.size()); + durationForNumOfKeepalive.setKeepaliveRegisteredDurationsMsec(0); + durationForNumOfKeepalive.setKeepaliveActiveDurationsMsec(0); + + mDurationPerNumOfKeepalive.add(durationForNumOfKeepalive); + } + } + + /** + * Updates the durations metrics to the given time. This should always be called before making a + * change to mNumRegisteredKeepalive or mNumActiveKeepalive to keep the duration metrics + * correct. + * + * @param timeNow a timestamp obtained using Dependencies.getUptimeMillis + */ + private void updateDurationsPerNumOfKeepalive(long timeNow) { + if (mDurationPerNumOfKeepalive.size() < mNumRegisteredKeepalive) { + Log.e(TAG, "Unexpected jump in number of registered keepalive"); + } + ensureDurationPerNumOfKeepaliveSize(); + + final int durationIncrease = (int) (timeNow - mTimestampSinceLastUpdateDurations); + final DurationForNumOfKeepalive.Builder durationForNumOfRegisteredKeepalive = + mDurationPerNumOfKeepalive.get(mNumRegisteredKeepalive); + + durationForNumOfRegisteredKeepalive.setKeepaliveRegisteredDurationsMsec( + durationForNumOfRegisteredKeepalive.getKeepaliveRegisteredDurationsMsec() + + durationIncrease); + + final DurationForNumOfKeepalive.Builder durationForNumOfActiveKeepalive = + mDurationPerNumOfKeepalive.get(mNumActiveKeepalive); + + durationForNumOfActiveKeepalive.setKeepaliveActiveDurationsMsec( + durationForNumOfActiveKeepalive.getKeepaliveActiveDurationsMsec() + + durationIncrease); + + mTimestampSinceLastUpdateDurations = timeNow; + } + + /** Inform the KeepaliveStatsTracker a keepalive has just started and is active. */ + public void onStartKeepalive() { + final long timeNow = mDependencies.getUptimeMillis(); + updateDurationsPerNumOfKeepalive(timeNow); + + mNumRegisteredKeepalive++; + mNumActiveKeepalive++; + } + + /** Inform the KeepaliveStatsTracker a keepalive has just been paused. */ + public void onPauseKeepalive() { + final long timeNow = mDependencies.getUptimeMillis(); + updateDurationsPerNumOfKeepalive(timeNow); + + mNumActiveKeepalive--; + } + + /** Inform the KeepaliveStatsTracker a keepalive has just been resumed. */ + public void onResumeKeepalive() { + final long timeNow = mDependencies.getUptimeMillis(); + updateDurationsPerNumOfKeepalive(timeNow); + + mNumActiveKeepalive++; + } + + /** Inform the KeepaliveStatsTracker a keepalive has just been stopped. */ + public void onStopKeepalive(boolean wasActive) { + final long timeNow = mDependencies.getUptimeMillis(); + updateDurationsPerNumOfKeepalive(timeNow); + + mNumRegisteredKeepalive--; + if (wasActive) mNumActiveKeepalive--; + } + + /** + * Builds and returns DailykeepaliveInfoReported proto. + */ + public DailykeepaliveInfoReported buildKeepaliveMetrics() { + final long timeNow = mDependencies.getUptimeMillis(); + updateDurationsPerNumOfKeepalive(timeNow); + + final DurationPerNumOfKeepalive.Builder durationPerNumOfKeepalive = + DurationPerNumOfKeepalive.newBuilder(); + + mDurationPerNumOfKeepalive.forEach( + durationForNumOfKeepalive -> + durationPerNumOfKeepalive.addDurationForNumOfKeepalive( + durationForNumOfKeepalive)); + + final DailykeepaliveInfoReported.Builder dailyKeepaliveInfoReported = + DailykeepaliveInfoReported.newBuilder(); + + // TODO(b/273451360): fill all the other values and write to ConnectivityStatsLog. + dailyKeepaliveInfoReported.setDurationPerNumOfKeepalive(durationPerNumOfKeepalive); + + return dailyKeepaliveInfoReported.build(); + } + + /** Resets the stored metrics but maintains the state of keepalives */ + public void resetMetrics() { + mDurationPerNumOfKeepalive.clear(); + ensureDurationPerNumOfKeepaliveSize(); + } +} diff --git a/tests/unit/java/com/android/server/connectivity/KeepaliveStatsTrackerTest.java b/tests/unit/java/com/android/server/connectivity/KeepaliveStatsTrackerTest.java new file mode 100644 index 0000000000..d262255d5a --- /dev/null +++ b/tests/unit/java/com/android/server/connectivity/KeepaliveStatsTrackerTest.java @@ -0,0 +1,504 @@ +/* + * Copyright (C) 2022 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 com.android.server.connectivity; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.doReturn; + +import android.os.Build; + +import androidx.test.filters.SmallTest; + +import com.android.metrics.DailykeepaliveInfoReported; +import com.android.metrics.DurationForNumOfKeepalive; +import com.android.metrics.DurationPerNumOfKeepalive; +import com.android.testutils.DevSdkIgnoreRule; +import com.android.testutils.DevSdkIgnoreRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(DevSdkIgnoreRunner.class) +@SmallTest +@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) +public class KeepaliveStatsTrackerTest { + private static final int TEST_UID = 1234; + + private KeepaliveStatsTracker mKeepaliveStatsTracker; + @Mock KeepaliveStatsTracker.Dependencies mDependencies; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + setUptimeMillis(0); + mKeepaliveStatsTracker = new KeepaliveStatsTracker(mDependencies); + } + + private void setUptimeMillis(long time) { + doReturn(time).when(mDependencies).getUptimeMillis(); + } + + /** + * Asserts that a DurationPerNumOfKeepalive contains expected values + * + * @param expectRegisteredDurations integer array where the index is the number of concurrent + * keepalives and the value is the expected duration of time that the tracker is in a state + * with the given number of keepalives registered. + * @param expectActiveDurations integer array where the index is the number of concurrent + * keepalives and the value is the expected duration of time that the tracker is in a state + * with the given number of keepalives active. + * @param resultDurationsPerNumOfKeepalive the DurationPerNumOfKeepalive message to assert. + */ + private void assertDurationMetrics( + int[] expectRegisteredDurations, + int[] expectActiveDurations, + DurationPerNumOfKeepalive resultDurationsPerNumOfKeepalive) { + final int maxNumOfKeepalive = expectRegisteredDurations.length; + assertEquals(maxNumOfKeepalive, expectActiveDurations.length); + assertEquals( + maxNumOfKeepalive, + resultDurationsPerNumOfKeepalive.getDurationForNumOfKeepaliveCount()); + for (int numOfKeepalive = 0; numOfKeepalive < maxNumOfKeepalive; numOfKeepalive++) { + final DurationForNumOfKeepalive resultDurations = + resultDurationsPerNumOfKeepalive.getDurationForNumOfKeepalive(numOfKeepalive); + + assertEquals(numOfKeepalive, resultDurations.getNumOfKeepalive()); + assertEquals( + expectRegisteredDurations[numOfKeepalive], + resultDurations.getKeepaliveRegisteredDurationsMsec()); + assertEquals( + expectActiveDurations[numOfKeepalive], + resultDurations.getKeepaliveActiveDurationsMsec()); + } + } + + private void assertDailyKeepaliveInfoReported( + DailykeepaliveInfoReported dailyKeepaliveInfoReported, + int[] expectRegisteredDurations, + int[] expectActiveDurations) { + // TODO(b/273451360) Assert these values when they are filled. + assertFalse(dailyKeepaliveInfoReported.hasKeepaliveLifetimePerCarrier()); + assertFalse(dailyKeepaliveInfoReported.hasKeepaliveRequests()); + assertFalse(dailyKeepaliveInfoReported.hasAutomaticKeepaliveRequests()); + assertFalse(dailyKeepaliveInfoReported.hasDistinctUserCount()); + assertTrue(dailyKeepaliveInfoReported.getUidList().isEmpty()); + + final DurationPerNumOfKeepalive resultDurations = + dailyKeepaliveInfoReported.getDurationPerNumOfKeepalive(); + assertDurationMetrics(expectRegisteredDurations, expectActiveDurations, resultDurations); + } + + @Test + public void testNoKeepalive() { + final int writeTime = 5000; + + setUptimeMillis(writeTime); + final DailykeepaliveInfoReported dailyKeepaliveInfoReported = + mKeepaliveStatsTracker.buildKeepaliveMetrics(); + + // Expect that the durations are all in numOfKeepalive = 0. + final int[] expectRegisteredDurations = new int[] {writeTime}; + final int[] expectActiveDurations = new int[] {writeTime}; + + assertDailyKeepaliveInfoReported( + dailyKeepaliveInfoReported, + expectRegisteredDurations, + expectActiveDurations); + } + + /* + * Diagram of test (not to scale): + * Key: S - Start/Stop, P - Pause, R - Resume, W - Write + * + * Keepalive S W + * Timeline |------------------------------| + */ + @Test + public void testOneKeepalive_startOnly() { + final int startTime = 1000; + final int writeTime = 5000; + + setUptimeMillis(startTime); + mKeepaliveStatsTracker.onStartKeepalive(); + + setUptimeMillis(writeTime); + final DailykeepaliveInfoReported dailyKeepaliveInfoReported = + mKeepaliveStatsTracker.buildKeepaliveMetrics(); + + // The keepalive is never stopped, expect the duration for numberOfKeepalive of 1 to range + // from startTime to writeTime. + final int[] expectRegisteredDurations = new int[] {startTime, writeTime - startTime}; + final int[] expectActiveDurations = new int[] {startTime, writeTime - startTime}; + assertDailyKeepaliveInfoReported( + dailyKeepaliveInfoReported, + expectRegisteredDurations, + expectActiveDurations); + } + + /* + * Diagram of test (not to scale): + * Key: S - Start/Stop, P - Pause, R - Resume, W - Write + * + * Keepalive S P W + * Timeline |------------------------------| + */ + @Test + public void testOneKeepalive_paused() { + final int startTime = 1000; + final int pauseTime = 2030; + final int writeTime = 5000; + + setUptimeMillis(startTime); + mKeepaliveStatsTracker.onStartKeepalive(); + + setUptimeMillis(pauseTime); + mKeepaliveStatsTracker.onPauseKeepalive(); + + setUptimeMillis(writeTime); + final DailykeepaliveInfoReported dailyKeepaliveInfoReported = + mKeepaliveStatsTracker.buildKeepaliveMetrics(); + + // The keepalive is paused but not stopped, expect the registered duration for + // numberOfKeepalive of 1 to still range from startTime to writeTime while the active + // duration stops at pauseTime. + final int[] expectRegisteredDurations = new int[] {startTime, writeTime - startTime}; + final int[] expectActiveDurations = + new int[] {startTime + (writeTime - pauseTime), pauseTime - startTime}; + assertDailyKeepaliveInfoReported( + dailyKeepaliveInfoReported, + expectRegisteredDurations, + expectActiveDurations); + } + + /* + * Diagram of test (not to scale): + * Key: S - Start/Stop, P - Pause, R - Resume, W - Write + * + * Keepalive S P R W + * Timeline |------------------------------| + */ + @Test + public void testOneKeepalive_resumed() { + final int startTime = 1000; + final int pauseTime = 2030; + final int resumeTime = 3450; + final int writeTime = 5000; + + setUptimeMillis(startTime); + mKeepaliveStatsTracker.onStartKeepalive(); + + setUptimeMillis(pauseTime); + mKeepaliveStatsTracker.onPauseKeepalive(); + + setUptimeMillis(resumeTime); + mKeepaliveStatsTracker.onResumeKeepalive(); + + setUptimeMillis(writeTime); + final DailykeepaliveInfoReported dailyKeepaliveInfoReported = + mKeepaliveStatsTracker.buildKeepaliveMetrics(); + + // The keepalive is paused and resumed but not stopped, expect the registered duration for + // numberOfKeepalive of 1 to still range from startTime to writeTime while the active + // duration stops at pauseTime but resumes at resumeTime and stops at writeTime. + final int[] expectRegisteredDurations = new int[] {startTime, writeTime - startTime}; + final int[] expectActiveDurations = + new int[] { + startTime + (resumeTime - pauseTime), + (pauseTime - startTime) + (writeTime - resumeTime) + }; + assertDailyKeepaliveInfoReported( + dailyKeepaliveInfoReported, + expectRegisteredDurations, + expectActiveDurations); + } + + /* + * Diagram of test (not to scale): + * Key: S - Start/Stop, P - Pause, R - Resume, W - Write + * + * Keepalive S P R S W + * Timeline |------------------------------| + */ + @Test + public void testOneKeepalive_stopped() { + final int startTime = 1000; + final int pauseTime = 2930; + final int resumeTime = 3452; + final int stopTime = 4157; + final int writeTime = 5000; + + setUptimeMillis(startTime); + mKeepaliveStatsTracker.onStartKeepalive(); + + setUptimeMillis(pauseTime); + mKeepaliveStatsTracker.onPauseKeepalive(); + + setUptimeMillis(resumeTime); + mKeepaliveStatsTracker.onResumeKeepalive(); + + setUptimeMillis(stopTime); + mKeepaliveStatsTracker.onStopKeepalive(/* wasActive= */ true); + + setUptimeMillis(writeTime); + final DailykeepaliveInfoReported dailyKeepaliveInfoReported = + mKeepaliveStatsTracker.buildKeepaliveMetrics(); + + // The keepalive is now stopped, expect the registered duration for numberOfKeepalive of 1 + // to now range from startTime to stopTime while the active duration stops at pauseTime but + // resumes at resumeTime and stops again at stopTime. + final int[] expectRegisteredDurations = + new int[] {startTime + (writeTime - stopTime), stopTime - startTime}; + final int[] expectActiveDurations = + new int[] { + startTime + (resumeTime - pauseTime) + (writeTime - stopTime), + (pauseTime - startTime) + (stopTime - resumeTime) + }; + assertDailyKeepaliveInfoReported( + dailyKeepaliveInfoReported, + expectRegisteredDurations, + expectActiveDurations); + } + + /* + * Diagram of test (not to scale): + * Key: S - Start/Stop, P - Pause, R - Resume, W - Write + * + * Keepalive S P S W + * Timeline |------------------------------| + */ + @Test + public void testOneKeepalive_pausedStopped() { + final int startTime = 1000; + final int pauseTime = 2930; + final int stopTime = 4157; + final int writeTime = 5000; + + setUptimeMillis(startTime); + mKeepaliveStatsTracker.onStartKeepalive(); + + setUptimeMillis(pauseTime); + mKeepaliveStatsTracker.onPauseKeepalive(); + + setUptimeMillis(stopTime); + mKeepaliveStatsTracker.onStopKeepalive(/* wasActive= */ false); + + setUptimeMillis(writeTime); + final DailykeepaliveInfoReported dailyKeepaliveInfoReported = + mKeepaliveStatsTracker.buildKeepaliveMetrics(); + + // The keepalive is stopped while paused, expect the registered duration for + // numberOfKeepalive of 1 to range from startTime to stopTime while the active duration + // simply stops at pauseTime. + final int[] expectRegisteredDurations = + new int[] {startTime + (writeTime - stopTime), stopTime - startTime}; + final int[] expectActiveDurations = + new int[] {startTime + (writeTime - pauseTime), (pauseTime - startTime)}; + assertDailyKeepaliveInfoReported( + dailyKeepaliveInfoReported, + expectRegisteredDurations, + expectActiveDurations); + } + + /* + * Diagram of test (not to scale): + * Key: S - Start/Stop, P - Pause, R - Resume, W - Write + * + * Keepalive S P R P R P R S W + * Timeline |------------------------------| + */ + @Test + public void testOneKeepalive_multiplePauses() { + final int startTime = 1000; + // Alternating timestamps of pause and resume + final int[] pauseResumeTimes = new int[] {1200, 1400, 1700, 2000, 2400, 2800}; + final int stopTime = 4000; + final int writeTime = 5000; + + setUptimeMillis(startTime); + mKeepaliveStatsTracker.onStartKeepalive(); + + for (int i = 0; i < pauseResumeTimes.length; i++) { + setUptimeMillis(pauseResumeTimes[i]); + if (i % 2 == 0) { + mKeepaliveStatsTracker.onPauseKeepalive(); + } else { + mKeepaliveStatsTracker.onResumeKeepalive(); + } + } + + setUptimeMillis(stopTime); + mKeepaliveStatsTracker.onStopKeepalive(/* wasActive= */ true); + + setUptimeMillis(writeTime); + final DailykeepaliveInfoReported dailyKeepaliveInfoReported = + mKeepaliveStatsTracker.buildKeepaliveMetrics(); + + final int[] expectRegisteredDurations = + new int[] {startTime + (writeTime - stopTime), stopTime - startTime}; + final int[] expectActiveDurations = + new int[] { + startTime + /* sum of (Resume - Pause) */ (900) + (writeTime - stopTime), + (pauseResumeTimes[0] - startTime) + + /* sum of (Pause - Resume) */ (700) + + (stopTime - pauseResumeTimes[5]) + }; + assertDailyKeepaliveInfoReported( + dailyKeepaliveInfoReported, + expectRegisteredDurations, + expectActiveDurations); + } + + /* + * Diagram of test (not to scale): + * Key: S - Start/Stop, P - Pause, R - Resume, W - Write + * + * Keepalive1 S1 P1 R1 S1 W + * Keepalive2 S2 P2 R2 W + * Timeline |------------------------------| + */ + @Test + public void testTwoKeepalives() { + // The suffix 1/2 indicates which keepalive it is referring to. + final int startTime1 = 1000; + final int pauseTime1 = 1500; + final int startTime2 = 2000; + final int resumeTime1 = 2500; + final int pauseTime2 = 3000; + final int resumeTime2 = 3500; + final int stopTime1 = 4157; + final int writeTime = 5000; + + setUptimeMillis(startTime1); + mKeepaliveStatsTracker.onStartKeepalive(); + + setUptimeMillis(pauseTime1); + mKeepaliveStatsTracker.onPauseKeepalive(); + + setUptimeMillis(startTime2); + mKeepaliveStatsTracker.onStartKeepalive(); + + setUptimeMillis(resumeTime1); + mKeepaliveStatsTracker.onResumeKeepalive(); + + setUptimeMillis(pauseTime2); + mKeepaliveStatsTracker.onPauseKeepalive(); + + setUptimeMillis(resumeTime2); + mKeepaliveStatsTracker.onResumeKeepalive(); + + setUptimeMillis(stopTime1); + mKeepaliveStatsTracker.onStopKeepalive(/* wasActive= */ true); + + setUptimeMillis(writeTime); + final DailykeepaliveInfoReported dailyKeepaliveInfoReported = + mKeepaliveStatsTracker.buildKeepaliveMetrics(); + + // With two keepalives, the number of concurrent keepalives can vary from 0-2 depending on + // both keepalive states. + final int[] expectRegisteredDurations = + new int[] { + startTime1, + // 1 registered keepalive before keepalive2 starts and after keepalive1 stops. + (startTime2 - startTime1) + (writeTime - stopTime1), + // 2 registered keepalives between keepalive2 start and keepalive1 stop. + stopTime1 - startTime2 + }; + + final int[] expectActiveDurations = + new int[] { + // 0 active keepalives when keepalive1 is paused before keepalive2 starts. + startTime1 + (startTime2 - pauseTime1), + // 1 active keepalive before keepalive1 is paused. + (pauseTime1 - startTime1) + // before keepalive1 is resumed and after keepalive2 starts. + + (resumeTime1 - startTime2) + // during keepalive2 is paused since keepalive1 has been resumed. + + (resumeTime2 - pauseTime2) + // after keepalive1 stops since keepalive2 has been resumed. + + (writeTime - stopTime1), + // 2 active keepalives before keepalive2 is paused and before keepalive1 stops. + (pauseTime2 - resumeTime1) + (stopTime1 - resumeTime2) + }; + assertDailyKeepaliveInfoReported( + dailyKeepaliveInfoReported, + expectRegisteredDurations, + expectActiveDurations); + } + + /* + * Diagram of test (not to scale): + * Key: S - Start/Stop, P - Pause, R - Resume, W - Write + * + * Keepalive S W(reset+W) S W + * Timeline |------------------------------| + */ + @Test + public void testResetMetrics() { + final int startTime = 1000; + final int writeTime = 5000; + final int stopTime = 7000; + final int writeTime2 = 10000; + + setUptimeMillis(startTime); + mKeepaliveStatsTracker.onStartKeepalive(); + + setUptimeMillis(writeTime); + final DailykeepaliveInfoReported dailyKeepaliveInfoReported = + mKeepaliveStatsTracker.buildKeepaliveMetrics(); + + // Same expect as testOneKeepalive_startOnly + final int[] expectRegisteredDurations = new int[] {startTime, writeTime - startTime}; + final int[] expectActiveDurations = new int[] {startTime, writeTime - startTime}; + assertDailyKeepaliveInfoReported( + dailyKeepaliveInfoReported, + expectRegisteredDurations, + expectActiveDurations); + + // Reset metrics + mKeepaliveStatsTracker.resetMetrics(); + + final DailykeepaliveInfoReported dailyKeepaliveInfoReported2 = + mKeepaliveStatsTracker.buildKeepaliveMetrics(); + // Expect the stored durations to be 0 but still contain the number of keepalive = 1. + assertDailyKeepaliveInfoReported( + dailyKeepaliveInfoReported2, + /* expectRegisteredDurations= */ new int[] {0, 0}, + /* expectActiveDurations= */ new int[] {0, 0}); + + // Expect that the keepalive is still registered after resetting so it can be stopped. + setUptimeMillis(stopTime); + mKeepaliveStatsTracker.onStopKeepalive(/* wasActive= */ true); + + setUptimeMillis(writeTime2); + final DailykeepaliveInfoReported dailyKeepaliveInfoReported3 = + mKeepaliveStatsTracker.buildKeepaliveMetrics(); + + final int[] expectRegisteredDurations2 = + new int[] {writeTime2 - stopTime, stopTime - writeTime}; + final int[] expectActiveDurations2 = + new int[] {writeTime2 - stopTime, stopTime - writeTime}; + assertDailyKeepaliveInfoReported( + dailyKeepaliveInfoReported3, + expectRegisteredDurations2, + expectActiveDurations2); + } +}