[SurfaceFlinger] Add refresh rate counts to TimeStats

This count will be exposed to lab tests as a breakdown metric for
uncovering jank.

Bug: 144526027
Test: dumpsys SurfaceFlinger --timestats -dump
Test: libsurfaceflinger_unittest
Change-Id: I921e147e96212ad6a08fa14d6e3d7a98893a8602
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 29fe5d9..bbb07a8 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -990,6 +990,9 @@
         return;
     }
 
+    auto& oldRefreshRate =
+            mRefreshRateConfigs->getRefreshRateFromConfigId(display->getActiveConfig());
+
     std::lock_guard<std::mutex> lock(mActiveConfigLock);
     mRefreshRateConfigs->setCurrentConfigId(mUpcomingActiveConfig.configId);
     mRefreshRateStats->setConfigMode(mUpcomingActiveConfig.configId);
@@ -997,6 +1000,9 @@
 
     auto& refreshRate =
             mRefreshRateConfigs->getRefreshRateFromConfigId(mUpcomingActiveConfig.configId);
+    if (refreshRate.vsyncPeriod != oldRefreshRate.vsyncPeriod) {
+        mTimeStats->incrementRefreshRateSwitches();
+    }
     mPhaseConfiguration->setRefreshRateFps(refreshRate.fps);
     mVSyncModulator->setPhaseOffsets(mPhaseConfiguration->getCurrentOffsets());
     ATRACE_INT("ActiveConfigFPS", refreshRate.fps);
diff --git a/services/surfaceflinger/TimeStats/TimeStats.cpp b/services/surfaceflinger/TimeStats/TimeStats.cpp
index 8038eba..96fd03a 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.cpp
+++ b/services/surfaceflinger/TimeStats/TimeStats.cpp
@@ -282,6 +282,15 @@
     mTimeStats.clientCompositionReusedFrames++;
 }
 
+void TimeStats::incrementRefreshRateSwitches() {
+    if (!mEnabled.load()) return;
+
+    ATRACE_CALL();
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    mTimeStats.refreshRateSwitches++;
+}
+
 void TimeStats::recordDisplayEventConnectionCount(int32_t count) {
     if (!mEnabled.load()) return;
 
@@ -835,6 +844,7 @@
     mTimeStats.missedFrames = 0;
     mTimeStats.clientCompositionFrames = 0;
     mTimeStats.clientCompositionReusedFrames = 0;
+    mTimeStats.refreshRateSwitches = 0;
     mTimeStats.displayEventConnectionsCount = 0;
     mTimeStats.displayOnTime = 0;
     mTimeStats.presentToPresent.hist.clear();
diff --git a/services/surfaceflinger/TimeStats/TimeStats.h b/services/surfaceflinger/TimeStats/TimeStats.h
index ddebeb1..8bbae1f 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.h
+++ b/services/surfaceflinger/TimeStats/TimeStats.h
@@ -52,6 +52,8 @@
     virtual void incrementMissedFrames() = 0;
     virtual void incrementClientCompositionFrames() = 0;
     virtual void incrementClientCompositionReusedFrames() = 0;
+    // Increments the number of times the display refresh rate changed.
+    virtual void incrementRefreshRateSwitches() = 0;
     // Records the most up-to-date count of display event connections.
     // The stored count will be the maximum ever recoded.
     virtual void recordDisplayEventConnectionCount(int32_t count) = 0;
@@ -215,6 +217,7 @@
     void incrementMissedFrames() override;
     void incrementClientCompositionFrames() override;
     void incrementClientCompositionReusedFrames() override;
+    void incrementRefreshRateSwitches() override;
     void recordDisplayEventConnectionCount(int32_t count) override;
 
     void recordFrameDuration(nsecs_t startTime, nsecs_t endTime) override;
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp b/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp
index e2f85cc..5305de9 100644
--- a/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp
+++ b/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp
@@ -105,6 +105,7 @@
     StringAppendF(&result, "missedFrames = %d\n", missedFrames);
     StringAppendF(&result, "clientCompositionFrames = %d\n", clientCompositionFrames);
     StringAppendF(&result, "clientCompositionReusedFrames = %d\n", clientCompositionReusedFrames);
+    StringAppendF(&result, "refreshRateSwitches = %d\n", refreshRateSwitches);
     StringAppendF(&result, "displayOnTime = %" PRId64 " ms\n", displayOnTime);
     StringAppendF(&result, "displayConfigStats is as below:\n");
     for (const auto& [fps, duration] : refreshRateStats) {
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h b/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h
index 5e7c449..afb98e0 100644
--- a/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h
+++ b/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h
@@ -62,6 +62,7 @@
         int32_t missedFrames = 0;
         int32_t clientCompositionFrames = 0;
         int32_t clientCompositionReusedFrames = 0;
+        int32_t refreshRateSwitches = 0;
         int32_t displayEventConnectionsCount = 0;
         int64_t displayOnTime = 0;
         Histogram presentToPresent;
diff --git a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
index a7a4d48..45f7ed3 100644
--- a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
@@ -362,6 +362,21 @@
     EXPECT_THAT(result, HasSubstr(expectedResult));
 }
 
+TEST_F(TimeStatsTest, canIncreaseRefreshRateSwitches) {
+    // this stat is not in the proto so verify by checking the string dump
+    constexpr size_t REFRESH_RATE_SWITCHES = 2;
+
+    EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
+    for (size_t i = 0; i < REFRESH_RATE_SWITCHES; i++) {
+        ASSERT_NO_FATAL_FAILURE(mTimeStats->incrementRefreshRateSwitches());
+    }
+
+    const std::string result(inputCommand(InputCommand::DUMP_ALL, FMT_STRING));
+    const std::string expectedResult =
+            "refreshRateSwitches = " + std::to_string(REFRESH_RATE_SWITCHES);
+    EXPECT_THAT(result, HasSubstr(expectedResult));
+}
+
 TEST_F(TimeStatsTest, canAverageFrameDuration) {
     EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
     mTimeStats->setPowerMode(HWC_POWER_MODE_NORMAL);
@@ -744,6 +759,7 @@
     // These stats are not in the proto so verify by checking the string dump.
     EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
     ASSERT_NO_FATAL_FAILURE(mTimeStats->incrementClientCompositionReusedFrames());
+    ASSERT_NO_FATAL_FAILURE(mTimeStats->incrementRefreshRateSwitches());
     mTimeStats->setPowerMode(HWC_POWER_MODE_NORMAL);
     mTimeStats
             ->recordFrameDuration(std::chrono::duration_cast<std::chrono::nanoseconds>(1ms).count(),
@@ -759,6 +775,7 @@
 
     const std::string result(inputCommand(InputCommand::DUMP_ALL, FMT_STRING));
     EXPECT_THAT(result, HasSubstr("clientCompositionReusedFrames = 0"));
+    EXPECT_THAT(result, HasSubstr("refreshRateSwitches = 0"));
     EXPECT_THAT(result, HasSubstr("averageFrameDuration = 0.000 ms"));
     EXPECT_THAT(result, HasSubstr("averageRenderEngineTiming = 0.000 ms"));
 }
diff --git a/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h b/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h
index c45d584..9ea4dd0 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h
@@ -36,6 +36,7 @@
     MOCK_METHOD0(incrementMissedFrames, void());
     MOCK_METHOD0(incrementClientCompositionFrames, void());
     MOCK_METHOD0(incrementClientCompositionReusedFrames, void());
+    MOCK_METHOD0(incrementRefreshRateSwitches, void());
     MOCK_METHOD1(recordDisplayEventConnectionCount, void(int32_t));
     MOCK_METHOD2(recordFrameDuration, void(nsecs_t, nsecs_t));
     MOCK_METHOD2(recordRenderEngineDuration, void(nsecs_t, nsecs_t));