blob: 4797e2af4dd1f30b58cb451f000dffee0db10bcb [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.base.jank_tracker;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
import org.chromium.base.FakeTimeTestRule;
import org.chromium.base.TimeUtils;
import org.chromium.base.test.BaseRobolectricTestRunner;
/** Tests for FrameMetricsStore. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class FrameMetricsStoreTest {
@Rule public FakeTimeTestRule mFakeTimeTestRule = new FakeTimeTestRule();
@Test
public void addFrameMeasurementTest() {
FrameMetricsStore store = new FrameMetricsStore();
store.initialize();
store.startTrackingScenario(JankScenario.NEW_TAB_PAGE);
long frame_start_vsync_ts = 0;
store.addFrameMeasurement(10_000_000L, 0, frame_start_vsync_ts);
store.addFrameMeasurement(12_000_000L, 0, frame_start_vsync_ts);
store.addFrameMeasurement(20_000_000L, 1, frame_start_vsync_ts);
store.addFrameMeasurement(8_000_000L, 2, frame_start_vsync_ts);
JankMetrics metrics = store.stopTrackingScenario(JankScenario.NEW_TAB_PAGE);
assertArrayEquals(
new long[] {10_000_000L, 12_000_000L, 20_000_000L, 8_000_000L},
metrics.durationsNs);
assertArrayEquals(new int[] {0, 0, 1, 2}, metrics.missedVsyncs);
metrics = store.stopTrackingScenario(JankScenario.NEW_TAB_PAGE);
assertEquals(0, metrics.durationsNs.length);
}
@Test
public void takeMetrics_getMetricsWithoutAnyFrames() {
FrameMetricsStore store = new FrameMetricsStore();
store.initialize();
store.startTrackingScenario(JankScenario.NEW_TAB_PAGE);
JankMetrics metrics = store.stopTrackingScenario(JankScenario.NEW_TAB_PAGE);
assertEquals(0, metrics.durationsNs.length);
}
@Test
public void concurrentScenarios() {
// We want to test 2 things.
// 1) that concurrent scenarios get the correct frames
// 2) that the deletion logic runs correctly. Note however that deletion logic is not
// actually public behaviour but we just want this test to explicitly exercise it to
// uncover potential bugs.
FrameMetricsStore store = new FrameMetricsStore();
store.initialize();
store.startTrackingScenario(JankScenario.NEW_TAB_PAGE);
long frame_start_vsync_ts = 1_000_000L;
store.addFrameMeasurement(10_000_000L, 0, frame_start_vsync_ts);
store.addFrameMeasurement(12_000_000L, 0, frame_start_vsync_ts + 1);
store.startTrackingScenario(JankScenario.FEED_SCROLLING);
store.addFrameMeasurement(20_000_000L, 2, frame_start_vsync_ts + 2);
store.addFrameMeasurement(8_000_000L, 1, frame_start_vsync_ts + 3);
// Stop NEW_TAB_PAGE and now the first two frames will be deleted from the
// FrameMetricsStore().
JankMetrics metrics = store.stopTrackingScenario(JankScenario.NEW_TAB_PAGE);
assertArrayEquals(
new long[] {10_000_000L, 12_000_000L, 20_000_000L, 8_000_000L},
metrics.durationsNs);
assertArrayEquals(new int[] {0, 0, 2, 1}, metrics.missedVsyncs);
metrics = store.stopTrackingScenario(JankScenario.NEW_TAB_PAGE);
assertEquals(0, metrics.durationsNs.length);
// Only after that will we stop FEED_SCROLLING and we should only see the last two frames.
metrics = store.stopTrackingScenario(JankScenario.FEED_SCROLLING);
assertArrayEquals(new long[] {20_000_000L, 8_000_000L}, metrics.durationsNs);
assertArrayEquals(new int[] {2, 1}, metrics.missedVsyncs);
}
@Test
public void restartScenario() {
// This test is setup to mainly test removeUnusedFrames() method codepaths.
FrameMetricsStore store = new FrameMetricsStore();
store.initialize();
store.startTrackingScenario(JankScenario.WEBVIEW_SCROLLING);
long frame_start_vsync_ts = 1_000_000L;
store.addFrameMeasurement(10_000_000L, 0, frame_start_vsync_ts);
store.addFrameMeasurement(12_000_000L, 1, frame_start_vsync_ts + 1);
JankMetrics metrics = store.stopTrackingScenario(JankScenario.WEBVIEW_SCROLLING);
assertArrayEquals(new long[] {10_000_000L, 12_000_000L}, metrics.durationsNs);
assertArrayEquals(new int[] {0, 1}, metrics.missedVsyncs);
store.startTrackingScenario(JankScenario.WEBVIEW_SCROLLING);
store.addFrameMeasurement(10_000_100L, 2, frame_start_vsync_ts + 2);
store.addFrameMeasurement(11_000_100L, 0, frame_start_vsync_ts + 3);
store.startTrackingScenario(JankScenario.NEW_TAB_PAGE);
store.addFrameMeasurement(12_000_100L, 0, frame_start_vsync_ts + 4);
metrics = store.stopTrackingScenario(JankScenario.WEBVIEW_SCROLLING);
assertArrayEquals(new long[] {10_000_100L, 11_000_100L, 12_000_100L}, metrics.durationsNs);
assertArrayEquals(new int[] {2, 0, 0}, metrics.missedVsyncs);
metrics = store.stopTrackingScenario(JankScenario.NEW_TAB_PAGE);
assertArrayEquals(new long[] {12_000_100L}, metrics.durationsNs);
assertArrayEquals(new int[] {0}, metrics.missedVsyncs);
}
@Test
public void startPendingScenarioBeforeScenarioEnd() {
FrameMetricsStore store = new FrameMetricsStore();
store.initialize();
store.startTrackingScenario(JankScenario.WEBVIEW_SCROLLING);
long now = TimeUtils.uptimeMillis();
long[] frame_timestamps_ns =
new long[] {
now * TimeUtils.NANOSECONDS_PER_MILLISECOND, // Frame1 start time
(now + 1) * TimeUtils.NANOSECONDS_PER_MILLISECOND, // Frame2 start time
(now + 2) * TimeUtils.NANOSECONDS_PER_MILLISECOND, // scenario end time
(now + 3) * TimeUtils.NANOSECONDS_PER_MILLISECOND, // new scenario start time
(now + 4) * TimeUtils.NANOSECONDS_PER_MILLISECOND, // Frame3 start time
(now + 5) * TimeUtils.NANOSECONDS_PER_MILLISECOND, // Frame4 start time
};
store.addFrameMeasurement(10_000_000L, 0, frame_timestamps_ns[0]);
store.addFrameMeasurement(12_000_000L, 3, frame_timestamps_ns[1]);
// This start scenario shouldn't be blanket ignored instead this should
// trigger start of the scenario after stop scenario call.
mFakeTimeTestRule.advanceMillis(
(frame_timestamps_ns[3] / TimeUtils.NANOSECONDS_PER_MILLISECOND) - now);
store.startTrackingScenario(JankScenario.WEBVIEW_SCROLLING);
// The end time of scenario is before the start time of last start calls,
// this can occur in the field as well since we delay stopTrackingScenario
// to receive metrics for all the frames in given time range.
JankMetrics metrics =
store.stopTrackingScenario(JankScenario.WEBVIEW_SCROLLING, frame_timestamps_ns[2]);
assertArrayEquals(new long[] {10_000_000L, 12_000_000L}, metrics.durationsNs);
assertArrayEquals(new int[] {0, 3}, metrics.missedVsyncs);
store.addFrameMeasurement(10_000_100L, 2, frame_timestamps_ns[4]);
store.addFrameMeasurement(11_000_100L, 0, frame_timestamps_ns[5]);
metrics = store.stopTrackingScenario(JankScenario.WEBVIEW_SCROLLING);
assertArrayEquals(new long[] {10_000_100L, 11_000_100L}, metrics.durationsNs);
assertArrayEquals(new int[] {2, 0}, metrics.missedVsyncs);
}
@Test
public void multipleStartScenarioBeforeScenarioEnd() {
/*
* Testing a scenario where might have a complete scroll within the delay period of
* processing a finishTrackingScenario request. We typically wait for ~100ms to receive
* frame metrics corresponding to last few frames in scenario. This test simulates
* scenario depicted below. '|______|' is the actual scroll start and end, while
* '-----------|' is the delay in processing finishTrackingScenario call i.e. when we
* call stopTrackingScenario.
*
* Scroll1: |________________________|-----------|
* Scroll2: |____|-----------|
* Scroll3: |_____________|-----------|
*
* As per current architecture expectation is we will loose metrics for scroll2 and scroll3,
* but this should be fine since the scenario is pretty unrealistic so shouldn't occur often
* in field data.
*/
FrameMetricsStore store = new FrameMetricsStore();
store.initialize();
store.startTrackingScenario(JankScenario.WEBVIEW_SCROLLING);
long now = TimeUtils.uptimeMillis();
long[] frame_timestamps_ns =
new long[] {
now * TimeUtils.NANOSECONDS_PER_MILLISECOND, // Frame1 start time
(now + 1) * TimeUtils.NANOSECONDS_PER_MILLISECOND, // Frame2 start time
(now + 2) * TimeUtils.NANOSECONDS_PER_MILLISECOND, // Scroll1 end time
(now + 3) * TimeUtils.NANOSECONDS_PER_MILLISECOND, // Scroll2 start time
(now + 4) * TimeUtils.NANOSECONDS_PER_MILLISECOND, // Frame3 start time
(now + 5) * TimeUtils.NANOSECONDS_PER_MILLISECOND, // Frame4 start time
(now + 6) * TimeUtils.NANOSECONDS_PER_MILLISECOND, // Scroll2 end time
(now + 7) * TimeUtils.NANOSECONDS_PER_MILLISECOND, // Scroll3 start time
(now + 8) * TimeUtils.NANOSECONDS_PER_MILLISECOND, // Frame5 start time
(now + 9) * TimeUtils.NANOSECONDS_PER_MILLISECOND, // Frame6 start time
(now + 10) * TimeUtils.NANOSECONDS_PER_MILLISECOND, // Scroll3 end time
};
store.addFrameMeasurement(10_000_000L, 0, frame_timestamps_ns[0]); // Frame1
store.addFrameMeasurement(12_000_000L, 1, frame_timestamps_ns[1]); // Frame2
mFakeTimeTestRule.advanceMillis(
(frame_timestamps_ns[3] / TimeUtils.NANOSECONDS_PER_MILLISECOND) - now);
store.startTrackingScenario(JankScenario.WEBVIEW_SCROLLING); // Scroll2 start
store.addFrameMeasurement(10_000_100L, 2, frame_timestamps_ns[4]); // Frame3
store.addFrameMeasurement(11_000_100L, 0, frame_timestamps_ns[5]); // Frame4
mFakeTimeTestRule.advanceMillis(
(frame_timestamps_ns[7] / TimeUtils.NANOSECONDS_PER_MILLISECOND)
- (frame_timestamps_ns[3] / TimeUtils.NANOSECONDS_PER_MILLISECOND));
store.startTrackingScenario(JankScenario.WEBVIEW_SCROLLING); // Scroll3 start
store.addFrameMeasurement(10_000_100L, 1, frame_timestamps_ns[8]); // Frame5
store.addFrameMeasurement(11_000_100L, 0, frame_timestamps_ns[9]); // Frame6
// Scroll1 end.
JankMetrics metrics =
store.stopTrackingScenario(JankScenario.WEBVIEW_SCROLLING, frame_timestamps_ns[2]);
assertArrayEquals(new long[] {10_000_000L, 12_000_000L}, metrics.durationsNs);
assertArrayEquals(new int[] {0, 1}, metrics.missedVsyncs);
// Scroll2 end.
metrics =
store.stopTrackingScenario(JankScenario.WEBVIEW_SCROLLING, frame_timestamps_ns[6]);
assertArrayEquals(new long[] {}, metrics.durationsNs);
assertArrayEquals(new int[] {}, metrics.missedVsyncs);
// Scroll3 end.
metrics =
store.stopTrackingScenario(JankScenario.WEBVIEW_SCROLLING, frame_timestamps_ns[10]);
assertArrayEquals(new long[] {}, metrics.durationsNs);
assertArrayEquals(new int[] {}, metrics.missedVsyncs);
}
}