blob: 7cd731cdb898693e68a7d14323c571d0ca01daf8 [file] [log] [blame]
/*
* 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 "ChoreographerFilter.h"
#define LOG_TAG "ChoreographerFilter"
#include <sched.h>
#include <unistd.h>
#include <deque>
#include <string>
#include "Settings.h"
#include "Thread.h"
#include "Log.h"
#include "Trace.h"
using namespace std::chrono_literals;
using time_point = std::chrono::steady_clock::time_point;
namespace {
class Timer {
public:
Timer(std::chrono::nanoseconds refreshPeriod, std::chrono::nanoseconds appToSfDelay)
: mRefreshPeriod(refreshPeriod),
mAppToSfDelay(appToSfDelay) {}
// Returns false if we have detected that we have received the same timestamp multiple times
// so that the caller can wait for fresh timestamps
bool addTimestamp(time_point point) {
// Keep track of the previous timestamp and how many times we've seen it to determine if
// we've stopped receiving Choreographer callbacks, which would indicate that we should
// probably stop until we see them again (e.g., if the app has been moved to the background)
if (point == mLastTimestamp) {
if (mRepeatCount++ > 5) {
return false;
}
} else {
mRepeatCount = 0;
}
mLastTimestamp = point;
point += mAppToSfDelay;
while (mBaseTime + mRefreshPeriod * 1.5 < point) {
mBaseTime += mRefreshPeriod;
}
std::chrono::nanoseconds delta = (point - (mBaseTime + mRefreshPeriod));
if (delta < -mRefreshPeriod / 2 || delta > mRefreshPeriod / 2) {
return true;
}
// TODO: 0.2 weighting factor for exponential smoothing is completely arbitrary
mBaseTime += mRefreshPeriod + delta * 2 / 10;
return true;
}
void sleep(std::chrono::nanoseconds offset) {
if (offset < -(mRefreshPeriod / 2) || offset > mRefreshPeriod / 2) {
offset = 0ms;
}
const auto now = std::chrono::steady_clock::now();
auto targetTime = mBaseTime + mRefreshPeriod + offset;
while (targetTime < now) {
targetTime += mRefreshPeriod;
}
std::this_thread::sleep_until(targetTime);
}
private:
const std::chrono::nanoseconds mRefreshPeriod;
const std::chrono::nanoseconds mAppToSfDelay;
time_point mBaseTime = std::chrono::steady_clock::now();
time_point mLastTimestamp = std::chrono::steady_clock::now();
int32_t mRepeatCount = 0;
};
} // anonymous namespace
namespace swappy {
ChoreographerFilter::ChoreographerFilter(std::chrono::nanoseconds refreshPeriod,
std::chrono::nanoseconds appToSfDelay,
Worker doWork)
: mRefreshPeriod(refreshPeriod),
mAppToSfDelay(appToSfDelay),
mDoWork(doWork) {
Settings::getInstance()->addListener([this]() { onSettingsChanged(); });
std::lock_guard<std::mutex> lock(mThreadPoolMutex);
mUseAffinity = Settings::getInstance()->getUseAffinity();
launchThreadsLocked();
}
ChoreographerFilter::~ChoreographerFilter() {
std::lock_guard<std::mutex> lock(mThreadPoolMutex);
terminateThreadsLocked();
}
void ChoreographerFilter::onChoreographer() {
std::unique_lock<std::mutex> lock(mMutex);
mLastTimestamp = std::chrono::steady_clock::now();
++mSequenceNumber;
mCondition.notify_all();
}
void ChoreographerFilter::launchThreadsLocked() {
{
std::lock_guard<std::mutex> lock(mMutex);
mIsRunning = true;
}
const int32_t numThreads = getNumCpus() > 2 ? 2 : 1;
for (int32_t thread = 0; thread < numThreads; ++thread) {
mThreadPool.push_back(std::thread([this, thread]() { threadMain(mUseAffinity, thread); }));
}
}
void ChoreographerFilter::terminateThreadsLocked() {
{
std::lock_guard<std::mutex> lock(mMutex);
mIsRunning = false;
mCondition.notify_all();
}
for (auto &thread : mThreadPool) {
thread.join();
}
mThreadPool.clear();
}
void ChoreographerFilter::onSettingsChanged() {
const bool useAffinity = Settings::getInstance()->getUseAffinity();
std::lock_guard<std::mutex> lock(mThreadPoolMutex);
if (useAffinity == mUseAffinity) {
return;
}
terminateThreadsLocked();
mUseAffinity = useAffinity;
launchThreadsLocked();
}
void ChoreographerFilter::threadMain(bool useAffinity, int32_t thread) {
Timer timer(mRefreshPeriod, mAppToSfDelay);
{
int cpu = getNumCpus() - 1 - thread;
if (cpu >= 0) {
setAffinity(cpu);
}
}
std::string threadName = "Filter";
threadName += std::to_string(thread);
pthread_setname_np(pthread_self(), threadName.c_str());
std::unique_lock<std::mutex> lock(mMutex);
while (mIsRunning) {
auto timestamp = mLastTimestamp;
auto workDuration = mWorkDuration;
lock.unlock();
// If we have received the same timestamp multiple times, it probably means that the app
// has stopped sending them to us, which could indicate that it's no longer running. If we
// detect that, we stop until we see a fresh timestamp to avoid spinning forever in the
// background.
if (!timer.addTimestamp(timestamp)) {
lock.lock();
mCondition.wait(lock, [=]() { return mLastTimestamp != timestamp; });
timestamp = mLastTimestamp;
lock.unlock();
timer.addTimestamp(timestamp);
}
timer.sleep(-workDuration);
{
std::unique_lock<std::mutex> workLock(mWorkMutex);
const auto now = std::chrono::steady_clock::now();
if (now - mLastWorkRun > mRefreshPeriod / 2) {
// Assume we got here first and there's work to do
gamesdk::ScopedTrace trace("doWork");
mWorkDuration = mDoWork();
mLastWorkRun = now;
}
}
lock.lock();
}
}
} // namespace swappy