SF: Extract PresentLatencyTracker

Present fences will eventually be tracked per display to compute per-
display CompositorTiming, but for now extract a class and add tests.

Bug: 185535769
Test: libscheduler_test
Change-Id: Ifa7f27059dc0ca5c8ee4747dd2640fb9638fd2bc
diff --git a/services/surfaceflinger/Scheduler/Android.bp b/services/surfaceflinger/Scheduler/Android.bp
index 5de796d..5bd8a99 100644
--- a/services/surfaceflinger/Scheduler/Android.bp
+++ b/services/surfaceflinger/Scheduler/Android.bp
@@ -18,6 +18,7 @@
         "libbase",
         "libcutils",
         "liblog",
+        "libui",
         "libutils",
     ],
 }
@@ -39,6 +40,7 @@
     name: "libscheduler",
     defaults: ["libscheduler_defaults"],
     srcs: [
+        "src/PresentLatencyTracker.cpp",
         "src/Timer.cpp",
     ],
     local_include_dirs: ["include"],
@@ -50,6 +52,7 @@
     test_suites: ["device-tests"],
     defaults: ["libscheduler_defaults"],
     srcs: [
+        "tests/PresentLatencyTrackerTest.cpp",
         "tests/TimerTest.cpp",
     ],
     static_libs: [
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/PresentLatencyTracker.h b/services/surfaceflinger/Scheduler/include/scheduler/PresentLatencyTracker.h
new file mode 100644
index 0000000..23ae83f
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/include/scheduler/PresentLatencyTracker.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright 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.
+ */
+
+#pragma once
+
+#include <memory>
+#include <queue>
+#include <utility>
+
+#include <scheduler/Time.h>
+
+namespace android {
+
+class FenceTime;
+
+namespace scheduler {
+
+// Computes composite-to-present latency by tracking recently composited frames pending to present.
+class PresentLatencyTracker {
+public:
+    // For tests.
+    static constexpr size_t kMaxPendingFrames = 4;
+
+    // Returns the present latency of the latest frame.
+    Duration trackPendingFrame(TimePoint compositeTime,
+                               std::shared_ptr<FenceTime> presentFenceTime);
+
+private:
+    struct PendingFrame {
+        PendingFrame(TimePoint compositeTime, std::shared_ptr<FenceTime> presentFenceTime)
+              : compositeTime(compositeTime), presentFenceTime(std::move(presentFenceTime)) {}
+
+        const TimePoint compositeTime;
+        const std::shared_ptr<FenceTime> presentFenceTime;
+    };
+
+    std::queue<PendingFrame> mPendingFrames;
+};
+
+} // namespace scheduler
+} // namespace android
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Time.h b/services/surfaceflinger/Scheduler/include/scheduler/Time.h
index 6fa548e..2b3ad2d 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/Time.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/Time.h
@@ -32,13 +32,13 @@
 struct Duration;
 
 struct TimePoint : scheduler::SchedulerClock::time_point {
-    explicit TimePoint(const Duration&);
+    explicit constexpr TimePoint(const Duration&);
 
     // Implicit conversion from std::chrono counterpart.
     constexpr TimePoint(scheduler::SchedulerClock::time_point p)
           : scheduler::SchedulerClock::time_point(p) {}
 
-    static TimePoint fromNs(nsecs_t);
+    static constexpr TimePoint fromNs(nsecs_t);
 
     nsecs_t ns() const;
 };
@@ -47,16 +47,16 @@
     // Implicit conversion from std::chrono counterpart.
     constexpr Duration(TimePoint::duration d) : TimePoint::duration(d) {}
 
-    static Duration fromNs(nsecs_t ns) { return {std::chrono::nanoseconds(ns)}; }
+    static constexpr Duration fromNs(nsecs_t ns) { return {std::chrono::nanoseconds(ns)}; }
 
     nsecs_t ns() const { return std::chrono::nanoseconds(*this).count(); }
 };
 
 using Period = Duration;
 
-inline TimePoint::TimePoint(const Duration& d) : scheduler::SchedulerClock::time_point(d) {}
+constexpr TimePoint::TimePoint(const Duration& d) : scheduler::SchedulerClock::time_point(d) {}
 
-inline TimePoint TimePoint::fromNs(nsecs_t ns) {
+constexpr TimePoint TimePoint::fromNs(nsecs_t ns) {
     return TimePoint(Duration::fromNs(ns));
 }
 
diff --git a/services/surfaceflinger/Scheduler/src/PresentLatencyTracker.cpp b/services/surfaceflinger/Scheduler/src/PresentLatencyTracker.cpp
new file mode 100644
index 0000000..8f3e081
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/src/PresentLatencyTracker.cpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright 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.
+ */
+
+#include <scheduler/PresentLatencyTracker.h>
+
+#include <cutils/compiler.h>
+#include <log/log.h>
+#include <ui/FenceTime.h>
+
+namespace android::scheduler {
+
+Duration PresentLatencyTracker::trackPendingFrame(TimePoint compositeTime,
+                                                  std::shared_ptr<FenceTime> presentFenceTime) {
+    Duration presentLatency = Duration::zero();
+    while (!mPendingFrames.empty()) {
+        const auto& pendingFrame = mPendingFrames.front();
+        const auto presentTime =
+                TimePoint::fromNs(pendingFrame.presentFenceTime->getCachedSignalTime());
+
+        if (presentTime == TimePoint::fromNs(Fence::SIGNAL_TIME_PENDING)) {
+            break;
+        }
+
+        if (presentTime == TimePoint::fromNs(Fence::SIGNAL_TIME_INVALID)) {
+            ALOGE("%s: Invalid present fence", __func__);
+        } else {
+            presentLatency = presentTime - pendingFrame.compositeTime;
+        }
+
+        mPendingFrames.pop();
+    }
+
+    mPendingFrames.emplace(compositeTime, std::move(presentFenceTime));
+
+    if (CC_UNLIKELY(mPendingFrames.size() > kMaxPendingFrames)) {
+        ALOGE("%s: Too many pending frames", __func__);
+        mPendingFrames.pop();
+    }
+
+    return presentLatency;
+}
+
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/tests/PresentLatencyTrackerTest.cpp b/services/surfaceflinger/Scheduler/tests/PresentLatencyTrackerTest.cpp
new file mode 100644
index 0000000..8952ca9
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/tests/PresentLatencyTrackerTest.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <array>
+
+#include <scheduler/PresentLatencyTracker.h>
+#include <ui/FenceTime.h>
+
+namespace android::scheduler {
+namespace {
+
+using FencePair = std::pair<sp<Fence>, std::shared_ptr<FenceTime>>;
+
+FencePair makePendingFence(FenceToFenceTimeMap& fenceMap) {
+    const auto fence = sp<Fence>::make();
+    return {fence, fenceMap.createFenceTimeForTest(fence)};
+}
+
+} // namespace
+
+TEST(PresentLatencyTrackerTest, skipsInvalidFences) {
+    PresentLatencyTracker tracker;
+
+    const TimePoint kCompositeTime = TimePoint::fromNs(999);
+    EXPECT_EQ(tracker.trackPendingFrame(kCompositeTime, FenceTime::NO_FENCE), Duration::zero());
+    EXPECT_EQ(tracker.trackPendingFrame(kCompositeTime, FenceTime::NO_FENCE), Duration::zero());
+    EXPECT_EQ(tracker.trackPendingFrame(kCompositeTime, FenceTime::NO_FENCE), Duration::zero());
+
+    FenceToFenceTimeMap fenceMap;
+    const auto [fence, fenceTime] = makePendingFence(fenceMap);
+    EXPECT_EQ(tracker.trackPendingFrame(kCompositeTime, fenceTime), Duration::zero());
+
+    fenceTime->signalForTest(9999);
+
+    EXPECT_EQ(tracker.trackPendingFrame(kCompositeTime, FenceTime::NO_FENCE),
+              Duration::fromNs(9000));
+}
+
+TEST(PresentLatencyTrackerTest, tracksPendingFrames) {
+    PresentLatencyTracker tracker;
+
+    FenceToFenceTimeMap fenceMap;
+    std::array<FencePair, PresentLatencyTracker::kMaxPendingFrames> fences;
+    std::generate(fences.begin(), fences.end(), [&fenceMap] { return makePendingFence(fenceMap); });
+
+    // The present latency is 0 if all fences are pending.
+    const TimePoint kCompositeTime = TimePoint::fromNs(1234);
+    for (const auto& [fence, fenceTime] : fences) {
+        EXPECT_EQ(tracker.trackPendingFrame(kCompositeTime, fenceTime), Duration::zero());
+    }
+
+    // If multiple frames have been presented...
+    constexpr size_t kPresentCount = fences.size() / 2;
+    for (size_t i = 0; i < kPresentCount; i++) {
+        fences[i].second->signalForTest(kCompositeTime.ns() + static_cast<nsecs_t>(i));
+    }
+
+    const auto fence = makePendingFence(fenceMap);
+
+    // ...then the present latency is measured using the latest frame.
+    constexpr Duration kPresentLatency = Duration::fromNs(static_cast<nsecs_t>(kPresentCount) - 1);
+    EXPECT_EQ(tracker.trackPendingFrame(kCompositeTime, fence.second), kPresentLatency);
+}
+
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index bb58eb4..64fd45e 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -2318,32 +2318,6 @@
     mLayersPendingRefresh.clear();
 }
 
-nsecs_t SurfaceFlinger::trackPresentLatency(nsecs_t compositeTime,
-                                            std::shared_ptr<FenceTime> presentFenceTime) {
-    // Update queue of past composite+present times and determine the
-    // most recently known composite to present latency.
-    getBE().mCompositePresentTimes.push({compositeTime, std::move(presentFenceTime)});
-    nsecs_t compositeToPresentLatency = -1;
-    while (!getBE().mCompositePresentTimes.empty()) {
-        SurfaceFlingerBE::CompositePresentTime& cpt = getBE().mCompositePresentTimes.front();
-        // Cached values should have been updated before calling this method,
-        // which helps avoid duplicate syscalls.
-        nsecs_t displayTime = cpt.display->getCachedSignalTime();
-        if (displayTime == Fence::SIGNAL_TIME_PENDING) {
-            break;
-        }
-        compositeToPresentLatency = displayTime - cpt.composite;
-        getBE().mCompositePresentTimes.pop();
-    }
-
-    // Don't let mCompositePresentTimes grow unbounded, just in case.
-    while (getBE().mCompositePresentTimes.size() > 16) {
-        getBE().mCompositePresentTimes.pop();
-    }
-
-    return compositeToPresentLatency;
-}
-
 bool SurfaceFlinger::isHdrLayer(Layer* layer) const {
     // Treat all layers as non-HDR if:
     // 1. They do not have a valid HDR dataspace. Currently we treat those as PQ or HLG. and
@@ -2431,9 +2405,11 @@
     // We use the CompositionEngine::getLastFrameRefreshTimestamp() which might
     // be sampled a little later than when we started doing work for this frame,
     // but that should be okay since CompositorTiming has snapping logic.
-    const nsecs_t compositeTime = mCompositionEngine->getLastFrameRefreshTimestamp();
-    const nsecs_t presentLatency =
-            trackPresentLatency(compositeTime, mPreviousPresentFences[0].fenceTime);
+    const TimePoint compositeTime =
+            TimePoint::fromNs(mCompositionEngine->getLastFrameRefreshTimestamp());
+    const Duration presentLatency =
+            mPresentLatencyTracker.trackPendingFrame(compositeTime,
+                                                     mPreviousPresentFences[0].fenceTime);
 
     const auto& schedule = mScheduler->getVsyncSchedule();
     const TimePoint vsyncDeadline = schedule.vsyncDeadlineAfter(TimePoint::fromNs(now));
@@ -2441,7 +2417,7 @@
     const nsecs_t vsyncPhase = mVsyncConfiguration->getCurrentConfigs().late.sfOffset;
 
     const CompositorTiming compositorTiming(vsyncDeadline.ns(), vsyncPeriod.ns(), vsyncPhase,
-                                            presentLatency);
+                                            presentLatency.ns());
 
     for (const auto& layer: mLayersWithQueuedFrames) {
         layer->onPostComposition(display, glCompositionDoneFenceTime,
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 71b9d5e..e43735f 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -55,6 +55,7 @@
 #include <compositionengine/FenceResult.h>
 #include <compositionengine/OutputColorSetting.h>
 #include <scheduler/Fps.h>
+#include <scheduler/PresentLatencyTracker.h>
 
 #include "ClientCache.h"
 #include "DisplayDevice.h"
@@ -169,13 +170,6 @@
 using DisplayColorSetting = compositionengine::OutputColorSetting;
 
 struct SurfaceFlingerBE {
-    // Only accessed from the main thread.
-    struct CompositePresentTime {
-        nsecs_t composite = -1;
-        std::shared_ptr<FenceTime> display = FenceTime::NO_FENCE;
-    };
-    std::queue<CompositePresentTime> mCompositePresentTimes;
-
     static const size_t NUM_BUCKETS = 8; // < 1-7, 7+
     nsecs_t mFrameBuckets[NUM_BUCKETS] = {};
     nsecs_t mTotalTime = 0;
@@ -954,11 +948,7 @@
     /*
      * Compositing
      */
-    void postComposition();
-
-    // Returns the composite-to-present latency of the latest presented frame.
-    nsecs_t trackPresentLatency(nsecs_t compositeTime, std::shared_ptr<FenceTime> presentFenceTime);
-
+    void postComposition() REQUIRES(kMainThreadContext);
     void postFrame() REQUIRES(kMainThreadContext);
 
     /*
@@ -1326,6 +1316,7 @@
     sp<VsyncModulator> mVsyncModulator;
 
     std::unique_ptr<scheduler::RefreshRateStats> mRefreshRateStats;
+    scheduler::PresentLatencyTracker mPresentLatencyTracker GUARDED_BY(kMainThreadContext);
 
     struct FenceWithFenceTime {
         sp<Fence> fence = Fence::NO_FENCE;
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
index 40281a7..be022e5 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
@@ -627,11 +627,9 @@
 
         mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate(mFdp.ConsumeIntegral<uid_t>());
 
-        mFlinger->postComposition();
-
-        mFlinger->trackPresentLatency(mFdp.ConsumeIntegral<nsecs_t>(), FenceTime::NO_FENCE);
-
+        FTL_FAKE_GUARD(kMainThreadContext, mFlinger->postComposition());
         FTL_FAKE_GUARD(kMainThreadContext, mFlinger->postFrame());
+
         mFlinger->calculateExpectedPresentTime({});
 
         mFlinger->enableHalVirtualDisplays(mFdp.ConsumeBool());
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
index 1a91a69..95219b5 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
@@ -19,6 +19,8 @@
 #include <fuzzer/FuzzedDataProvider.h>
 #include <processgroup/sched_policy.h>
 
+#include <scheduler/PresentLatencyTracker.h>
+
 #include "Scheduler/DispSyncSource.h"
 #include "Scheduler/OneShotTimer.h"
 #include "Scheduler/VSyncDispatchTimerQueue.h"
@@ -58,6 +60,7 @@
 private:
     void fuzzRefreshRateSelection();
     void fuzzRefreshRateConfigs();
+    void fuzzPresentLatencyTracker();
     void fuzzVSyncModulator();
     void fuzzVSyncPredictor();
     void fuzzVSyncReactor();
@@ -382,9 +385,16 @@
     refreshRateStats.setPowerMode(mFdp.PickValueInArray(kPowerModes));
 }
 
+void SchedulerFuzzer::fuzzPresentLatencyTracker() {
+    scheduler::PresentLatencyTracker tracker;
+    tracker.trackPendingFrame(TimePoint::fromNs(mFdp.ConsumeIntegral<nsecs_t>()),
+                              FenceTime::NO_FENCE);
+}
+
 void SchedulerFuzzer::process() {
     fuzzRefreshRateSelection();
     fuzzRefreshRateConfigs();
+    fuzzPresentLatencyTracker();
     fuzzVSyncModulator();
     fuzzVSyncPredictor();
     fuzzVSyncReactor();