Merge "SF: Adding Idle Timer, to detect when devices are idle."
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 22e4d1e..0106b25 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -139,6 +139,7 @@
"Scheduler/DispSyncSource.cpp",
"Scheduler/EventControlThread.cpp",
"Scheduler/EventThread.cpp",
+ "Scheduler/IdleTimer.cpp",
"Scheduler/LayerHistory.cpp",
"Scheduler/MessageQueue.cpp",
"Scheduler/Scheduler.cpp",
diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index 49e7ef6..1f08f4e 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -48,10 +48,14 @@
namespace impl {
EventThread::EventThread(std::unique_ptr<VSyncSource> src,
- ResyncWithRateLimitCallback resyncWithRateLimitCallback,
- InterceptVSyncsCallback interceptVSyncsCallback, const char* threadName)
+ const ResyncWithRateLimitCallback& resyncWithRateLimitCallback,
+ const InterceptVSyncsCallback& interceptVSyncsCallback,
+ const ResetIdleTimerCallback& resetIdleTimerCallback,
+ const char* threadName)
: EventThread(nullptr, std::move(src), resyncWithRateLimitCallback, interceptVSyncsCallback,
- threadName) {}
+ threadName) {
+ mResetIdleTimer = resetIdleTimerCallback;
+}
EventThread::EventThread(VSyncSource* src, ResyncWithRateLimitCallback resyncWithRateLimitCallback,
InterceptVSyncsCallback interceptVSyncsCallback, const char* threadName)
@@ -150,6 +154,9 @@
void EventThread::requestNextVsync(const sp<EventThread::Connection>& connection) {
std::lock_guard<std::mutex> lock(mMutex);
+ if (mResetIdleTimer) {
+ mResetIdleTimer();
+ }
if (mResyncWithRateLimitCallback) {
mResyncWithRateLimitCallback();
diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h
index 15b5bba..0773c05 100644
--- a/services/surfaceflinger/Scheduler/EventThread.h
+++ b/services/surfaceflinger/Scheduler/EventThread.h
@@ -107,13 +107,15 @@
public:
using ResyncWithRateLimitCallback = std::function<void()>;
using InterceptVSyncsCallback = std::function<void(nsecs_t)>;
+ using ResetIdleTimerCallback = std::function<void()>;
// TODO(b/113612090): Once the Scheduler is complete this constructor will become obsolete.
EventThread(VSyncSource* src, ResyncWithRateLimitCallback resyncWithRateLimitCallback,
InterceptVSyncsCallback interceptVSyncsCallback, const char* threadName);
EventThread(std::unique_ptr<VSyncSource> src,
- ResyncWithRateLimitCallback resyncWithRateLimitCallback,
- InterceptVSyncsCallback interceptVSyncsCallback, const char* threadName);
+ const ResyncWithRateLimitCallback& resyncWithRateLimitCallback,
+ const InterceptVSyncsCallback& interceptVSyncsCallback,
+ const ResetIdleTimerCallback& resetIdleTimerCallback, const char* threadName);
~EventThread();
sp<BnDisplayEventConnection> createEventConnection() const override;
@@ -177,6 +179,9 @@
// for debugging
bool mDebugVsyncEnabled GUARDED_BY(mMutex) = false;
+
+ // Callback that resets the idle timer when the next vsync is received.
+ ResetIdleTimerCallback mResetIdleTimer;
};
// ---------------------------------------------------------------------------
diff --git a/services/surfaceflinger/Scheduler/IdleTimer.cpp b/services/surfaceflinger/Scheduler/IdleTimer.cpp
new file mode 100644
index 0000000..5a76dbc
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/IdleTimer.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2018 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 "IdleTimer.h"
+
+#include <chrono>
+#include <thread>
+
+namespace android {
+namespace scheduler {
+
+IdleTimer::IdleTimer(const Interval& interval, const TimeoutCallback& timeoutCallback)
+ : mInterval(interval), mTimeoutCallback(timeoutCallback) {}
+
+IdleTimer::~IdleTimer() {
+ stop();
+}
+
+void IdleTimer::start() {
+ {
+ std::lock_guard<std::mutex> lock(mMutex);
+ mState = TimerState::RESET;
+ }
+ mThread = std::thread(&IdleTimer::loop, this);
+}
+
+void IdleTimer::stop() {
+ {
+ std::lock_guard<std::mutex> lock(mMutex);
+ mState = TimerState::STOPPED;
+ }
+ mCondition.notify_all();
+ if (mThread.joinable()) {
+ mThread.join();
+ }
+}
+
+void IdleTimer::loop() {
+ std::lock_guard<std::mutex> lock(mMutex);
+ while (mState != TimerState::STOPPED) {
+ if (mState == TimerState::IDLE) {
+ mCondition.wait(mMutex);
+ } else if (mState == TimerState::RESET) {
+ mState = TimerState::WAITING;
+ if (mCondition.wait_for(mMutex, mInterval) == std::cv_status::timeout) {
+ if (mTimeoutCallback) {
+ mTimeoutCallback();
+ }
+ }
+ if (mState == TimerState::WAITING) {
+ mState = TimerState::IDLE;
+ }
+ }
+ }
+}
+
+void IdleTimer::reset() {
+ {
+ std::lock_guard<std::mutex> lock(mMutex);
+ mState = TimerState::RESET;
+ }
+ mCondition.notify_all();
+}
+
+} // namespace scheduler
+} // namespace android
diff --git a/services/surfaceflinger/Scheduler/IdleTimer.h b/services/surfaceflinger/Scheduler/IdleTimer.h
new file mode 100644
index 0000000..aee3fa3
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/IdleTimer.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2018 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 <chrono>
+#include <condition_variable>
+#include <thread>
+
+#include <android-base/thread_annotations.h>
+
+namespace android {
+namespace scheduler {
+
+/*
+ * Class that sets off a timer for a given interval, and fires a callback when the
+ * interval expires.
+ */
+class IdleTimer {
+public:
+ using Interval = std::chrono::milliseconds;
+ using TimeoutCallback = std::function<void()>;
+
+ IdleTimer(const Interval& interval, const TimeoutCallback& timeoutCallback);
+ ~IdleTimer();
+
+ void start();
+ void stop();
+ void reset();
+
+private:
+ // Enum to track in what state is the timer.
+ enum class TimerState { STOPPED = 0, RESET = 1, WAITING = 2, IDLE = 3 };
+
+ // Function that loops until the condition for stopping is met.
+ void loop();
+
+ // Thread waiting for timer to expire.
+ std::thread mThread;
+
+ // Condition used to notify mThread.
+ std::condition_variable_any mCondition;
+
+ // Lock used for synchronizing the waiting thread with the application thread.
+ std::mutex mMutex;
+
+ TimerState mState GUARDED_BY(mMutex) = TimerState::RESET;
+
+ // Interval after which timer expires.
+ const Interval mInterval;
+
+ // Callback that happens when timer expires.
+ const TimeoutCallback mTimeoutCallback;
+};
+
+} // namespace scheduler
+} // namespace android
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 5b8cc10..fad56e6 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -29,6 +29,7 @@
#include <android/hardware/configstore/1.2/ISurfaceFlingerConfigs.h>
#include <configstore/Utils.h>
+#include <cutils/properties.h>
#include <gui/ISurfaceComposer.h>
#include <ui/DisplayStatInfo.h>
#include <utils/Timers.h>
@@ -38,6 +39,7 @@
#include "DispSyncSource.h"
#include "EventControlThread.h"
#include "EventThread.h"
+#include "IdleTimer.h"
#include "InjectVSyncSource.h"
#include "SchedulerUtils.h"
@@ -68,6 +70,17 @@
primaryDispSync->init(mHasSyncFramework, mDispSyncPresentTimeOffset);
mPrimaryDispSync = std::move(primaryDispSync);
mEventControlThread = std::make_unique<impl::EventControlThread>(function);
+
+ char value[PROPERTY_VALUE_MAX];
+ property_get("debug.sf.set_idle_timer_ms", value, "0");
+ mSetIdleTimerMs = atoi(value);
+
+ if (mSetIdleTimerMs > 0) {
+ mIdleTimer =
+ std::make_unique<scheduler::IdleTimer>(std::chrono::milliseconds(mSetIdleTimerMs),
+ [this] { expiredTimerCallback(); });
+ mIdleTimer->start();
+ }
}
Scheduler::~Scheduler() = default;
@@ -98,7 +111,8 @@
std::make_unique<DispSyncSource>(dispSync, phaseOffsetNs, true, sourceName.c_str());
const std::string threadName = connectionName + "Thread";
return std::make_unique<impl::EventThread>(std::move(eventThreadSource), resyncCallback,
- interceptCallback, threadName.c_str());
+ interceptCallback, [this] { resetIdleTimer(); },
+ threadName.c_str());
}
sp<IDisplayEventConnection> Scheduler::createDisplayEventConnection(
@@ -309,4 +323,18 @@
}
}
+void Scheduler::resetIdleTimer() {
+ if (mIdleTimer) {
+ mIdleTimer->reset();
+ ATRACE_INT("ExpiredIdleTimer", 0);
+ }
+}
+
+void Scheduler::expiredTimerCallback() {
+ // TODO(b/113612090): Each time a timer expired, we should record the information into
+ // a circular buffer. Once this has happened a given amount (TBD) of times, we can comfortably
+ // say that the device is sitting in idle.
+ ATRACE_INT("ExpiredIdleTimer", 1);
+}
+
} // namespace android
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index ea90824..8d4514b 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -25,6 +25,7 @@
#include "DispSync.h"
#include "EventControlThread.h"
#include "EventThread.h"
+#include "IdleTimer.h"
#include "InjectVSyncSource.h"
#include "LayerHistory.h"
#include "SchedulerUtils.h"
@@ -126,6 +127,10 @@
// Collects the average difference between timestamps for each frame regardless
// of which layer the timestamp came from.
void determineTimestampAverage(bool isAutoTimestamp, const nsecs_t framePresentTime);
+ // Function that resets the idle timer.
+ void resetIdleTimer();
+ // Function that is called when the timer expires.
+ void expiredTimerCallback();
// TODO(b/113612090): Instead of letting BufferQueueLayer to access mDispSync directly, it
// should make request to Scheduler to compute next refresh.
@@ -163,6 +168,11 @@
size_t mCounter = 0;
LayerHistory mLayerHistory;
+
+ // Timer that records time between requests for next vsync. If the time is higher than a given
+ // interval, a callback is fired. Set this variable to >0 to use this feature.
+ int64_t mSetIdleTimerMs = 0;
+ std::unique_ptr<scheduler::IdleTimer> mIdleTimer;
};
} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 1a13f77..2f35ae5 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -30,6 +30,7 @@
"DisplayTransactionTest.cpp",
"EventControlThreadTest.cpp",
"EventThreadTest.cpp",
+ "IdleTimerTest.cpp",
"LayerHistoryTest.cpp",
"SchedulerTest.cpp",
"SchedulerUtilsTest.cpp",
diff --git a/services/surfaceflinger/tests/unittests/IdleTimerTest.cpp b/services/surfaceflinger/tests/unittests/IdleTimerTest.cpp
new file mode 100644
index 0000000..9fe9a18
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/IdleTimerTest.cpp
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "SchedulerUnittests"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <utils/Log.h>
+
+#include "AsyncCallRecorder.h"
+#include "Scheduler/IdleTimer.h"
+
+using namespace std::chrono_literals;
+
+namespace android {
+namespace scheduler {
+
+class IdleTimerTest : public testing::Test {
+protected:
+ IdleTimerTest() = default;
+ ~IdleTimerTest() override = default;
+
+ AsyncCallRecorder<void (*)()> mExpiredTimerCallback;
+
+ std::unique_ptr<IdleTimer> mIdleTimer;
+};
+
+namespace {
+TEST_F(IdleTimerTest, createAndDestroyTest) {
+ mIdleTimer = std::make_unique<scheduler::IdleTimer>(30ms, [] {});
+}
+
+TEST_F(IdleTimerTest, startStopTest) {
+ mIdleTimer = std::make_unique<scheduler::IdleTimer>(30ms, mExpiredTimerCallback.getInvocable());
+ mIdleTimer->start();
+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
+ // The timer expires after 30 ms, so the call to the callback should not happen.
+ EXPECT_FALSE(mExpiredTimerCallback.waitForCall().has_value());
+ mIdleTimer->stop();
+}
+
+TEST_F(IdleTimerTest, resetTest) {
+ mIdleTimer = std::make_unique<scheduler::IdleTimer>(20ms, mExpiredTimerCallback.getInvocable());
+ mIdleTimer->start();
+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
+ // The timer expires after 30 ms, so the call to the callback should not happen.
+ EXPECT_FALSE(mExpiredTimerCallback.waitForCall(1us).has_value());
+ mIdleTimer->reset();
+ // The timer was reset, so the call to the callback should not happen.
+ std::this_thread::sleep_for(std::chrono::milliseconds(15));
+ EXPECT_FALSE(mExpiredTimerCallback.waitForCall(1us).has_value());
+ mIdleTimer->stop();
+}
+
+TEST_F(IdleTimerTest, startNotCalledTest) {
+ mIdleTimer = std::make_unique<scheduler::IdleTimer>(3ms, mExpiredTimerCallback.getInvocable());
+ std::this_thread::sleep_for(6ms);
+ // The start hasn't happened, so the callback does not happen.
+ EXPECT_FALSE(mExpiredTimerCallback.waitForCall(1us).has_value());
+ mIdleTimer->stop();
+}
+
+TEST_F(IdleTimerTest, idleTimerIdlesTest) {
+ mIdleTimer = std::make_unique<scheduler::IdleTimer>(3ms, mExpiredTimerCallback.getInvocable());
+ mIdleTimer->start();
+ std::this_thread::sleep_for(6ms);
+ // The timer expires after 3 ms, so the call to the callback happens.
+ EXPECT_TRUE(mExpiredTimerCallback.waitForCall(1us).has_value());
+ std::this_thread::sleep_for(6ms);
+ // Timer can be idle.
+ EXPECT_FALSE(mExpiredTimerCallback.waitForCall(1us).has_value());
+ // Timer can be reset.
+ mIdleTimer->reset();
+ std::this_thread::sleep_for(6ms);
+ // Timer fires again.
+ EXPECT_TRUE(mExpiredTimerCallback.waitForCall(1us).has_value());
+ mIdleTimer->stop();
+}
+
+TEST_F(IdleTimerTest, timeoutCallbackExecutionTest) {
+ mIdleTimer = std::make_unique<scheduler::IdleTimer>(3ms, mExpiredTimerCallback.getInvocable());
+
+ mIdleTimer->start();
+ std::this_thread::sleep_for(6ms);
+ // The timer expires after 3 ms, so the call to the callback should happen.
+ EXPECT_TRUE(mExpiredTimerCallback.waitForCall(1us).has_value());
+ mIdleTimer->stop();
+}
+
+TEST_F(IdleTimerTest, noCallbacksAfterStopAndResetTest) {
+ mIdleTimer = std::make_unique<scheduler::IdleTimer>(3ms, mExpiredTimerCallback.getInvocable());
+ mIdleTimer->start();
+ std::this_thread::sleep_for(6ms);
+ EXPECT_TRUE(mExpiredTimerCallback.waitForCall().has_value());
+ mIdleTimer->stop();
+ mIdleTimer->reset();
+ std::this_thread::sleep_for(6ms);
+ EXPECT_FALSE(mExpiredTimerCallback.waitForCall().has_value());
+}
+
+TEST_F(IdleTimerTest, noCallbacksAfterStopTest) {
+ mIdleTimer = std::make_unique<scheduler::IdleTimer>(3ms, mExpiredTimerCallback.getInvocable());
+ mIdleTimer->start();
+ std::this_thread::sleep_for(1ms);
+ mIdleTimer->stop();
+ std::this_thread::sleep_for(3ms);
+ EXPECT_FALSE(mExpiredTimerCallback.waitForCall(1us).has_value());
+}
+
+} // namespace
+} // namespace scheduler
+} // namespace android
\ No newline at end of file