• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "ChoreographerFilter.h"
18 
19 #define LOG_TAG "ChoreographerFilter"
20 
21 #include <sched.h>
22 #include <unistd.h>
23 
24 #include <deque>
25 #include <string>
26 
27 #include "Settings.h"
28 #include "Thread.h"
29 
30 #include "Log.h"
31 #include "Trace.h"
32 
33 using namespace std::chrono_literals;
34 using time_point = std::chrono::steady_clock::time_point;
35 
36 namespace {
37 
38 class Timer {
39   public:
Timer(std::chrono::nanoseconds refreshPeriod,std::chrono::nanoseconds appToSfDelay)40     Timer(std::chrono::nanoseconds refreshPeriod, std::chrono::nanoseconds appToSfDelay)
41         : mRefreshPeriod(refreshPeriod),
42           mAppToSfDelay(appToSfDelay) {}
43 
44     // Returns false if we have detected that we have received the same timestamp multiple times
45     // so that the caller can wait for fresh timestamps
addTimestamp(time_point point)46     bool addTimestamp(time_point point) {
47         // Keep track of the previous timestamp and how many times we've seen it to determine if
48         // we've stopped receiving Choreographer callbacks, which would indicate that we should
49         // probably stop until we see them again (e.g., if the app has been moved to the background)
50         if (point == mLastTimestamp) {
51             if (mRepeatCount++ > 5) {
52                 return false;
53             }
54         } else {
55             mRepeatCount = 0;
56         }
57         mLastTimestamp = point;
58 
59         point += mAppToSfDelay;
60 
61         while (mBaseTime + mRefreshPeriod * 1.5 < point) {
62             mBaseTime += mRefreshPeriod;
63         }
64 
65         std::chrono::nanoseconds delta = (point - (mBaseTime + mRefreshPeriod));
66         if (delta < -mRefreshPeriod / 2 || delta > mRefreshPeriod / 2) {
67             return true;
68         }
69 
70         // TODO: 0.2 weighting factor for exponential smoothing is completely arbitrary
71         mBaseTime += mRefreshPeriod + delta * 2 / 10;
72 
73         return true;
74     }
75 
sleep(std::chrono::nanoseconds offset)76     void sleep(std::chrono::nanoseconds offset) {
77         if (offset < -(mRefreshPeriod / 2) || offset > mRefreshPeriod / 2) {
78             offset = 0ms;
79         }
80 
81         const auto now = std::chrono::steady_clock::now();
82         auto targetTime = mBaseTime + mRefreshPeriod + offset;
83         while (targetTime < now) {
84             targetTime += mRefreshPeriod;
85         }
86 
87         std::this_thread::sleep_until(targetTime);
88     }
89 
90   private:
91     const std::chrono::nanoseconds mRefreshPeriod;
92     const std::chrono::nanoseconds mAppToSfDelay;
93     time_point mBaseTime = std::chrono::steady_clock::now();
94 
95     time_point mLastTimestamp = std::chrono::steady_clock::now();
96     int32_t mRepeatCount = 0;
97 };
98 
99 } // anonymous namespace
100 
101 namespace swappy {
102 
ChoreographerFilter(std::chrono::nanoseconds refreshPeriod,std::chrono::nanoseconds appToSfDelay,Worker doWork)103 ChoreographerFilter::ChoreographerFilter(std::chrono::nanoseconds refreshPeriod,
104                                          std::chrono::nanoseconds appToSfDelay,
105                                          Worker doWork)
106     : mRefreshPeriod(refreshPeriod),
107       mAppToSfDelay(appToSfDelay),
108       mDoWork(doWork) {
109     Settings::getInstance()->addListener([this]() { onSettingsChanged(); });
110 
111     std::lock_guard<std::mutex> lock(mThreadPoolMutex);
112     mUseAffinity = Settings::getInstance()->getUseAffinity();
113     launchThreadsLocked();
114 }
115 
~ChoreographerFilter()116 ChoreographerFilter::~ChoreographerFilter() {
117     std::lock_guard<std::mutex> lock(mThreadPoolMutex);
118     terminateThreadsLocked();
119 }
120 
onChoreographer()121 void ChoreographerFilter::onChoreographer() {
122     std::unique_lock<std::mutex> lock(mMutex);
123     mLastTimestamp = std::chrono::steady_clock::now();
124     ++mSequenceNumber;
125     mCondition.notify_all();
126 }
127 
launchThreadsLocked()128 void ChoreographerFilter::launchThreadsLocked() {
129     {
130         std::lock_guard<std::mutex> lock(mMutex);
131         mIsRunning = true;
132     }
133 
134     const int32_t numThreads = getNumCpus() > 2 ? 2 : 1;
135     for (int32_t thread = 0; thread < numThreads; ++thread) {
136         mThreadPool.push_back(std::thread([this, thread]() { threadMain(mUseAffinity, thread); }));
137     }
138 }
139 
terminateThreadsLocked()140 void ChoreographerFilter::terminateThreadsLocked() {
141     {
142         std::lock_guard<std::mutex> lock(mMutex);
143         mIsRunning = false;
144         mCondition.notify_all();
145     }
146 
147     for (auto &thread : mThreadPool) {
148         thread.join();
149     }
150     mThreadPool.clear();
151 }
152 
onSettingsChanged()153 void ChoreographerFilter::onSettingsChanged() {
154     const bool useAffinity = Settings::getInstance()->getUseAffinity();
155     std::lock_guard<std::mutex> lock(mThreadPoolMutex);
156     if (useAffinity == mUseAffinity) {
157         return;
158     }
159 
160     terminateThreadsLocked();
161     mUseAffinity = useAffinity;
162     launchThreadsLocked();
163 }
164 
threadMain(bool useAffinity,int32_t thread)165 void ChoreographerFilter::threadMain(bool useAffinity, int32_t thread) {
166     Timer timer(mRefreshPeriod, mAppToSfDelay);
167 
168     {
169         int cpu = getNumCpus() - 1 - thread;
170         if (cpu >= 0) {
171             setAffinity(cpu);
172         }
173     }
174 
175     std::string threadName = "Filter";
176     threadName += swappy::to_string(thread);
177     pthread_setname_np(pthread_self(), threadName.c_str());
178 
179     std::unique_lock<std::mutex> lock(mMutex);
180     while (mIsRunning) {
181         auto timestamp = mLastTimestamp;
182         auto workDuration = mWorkDuration;
183         lock.unlock();
184 
185         // If we have received the same timestamp multiple times, it probably means that the app
186         // has stopped sending them to us, which could indicate that it's no longer running. If we
187         // detect that, we stop until we see a fresh timestamp to avoid spinning forever in the
188         // background.
189         if (!timer.addTimestamp(timestamp)) {
190             lock.lock();
191             mCondition.wait(lock, [=]() { return mLastTimestamp != timestamp; });
192             timestamp = mLastTimestamp;
193             lock.unlock();
194             timer.addTimestamp(timestamp);
195         }
196 
197         timer.sleep(-workDuration);
198         {
199             std::unique_lock<std::mutex> workLock(mWorkMutex);
200             const auto now = std::chrono::steady_clock::now();
201             if (now - mLastWorkRun > mRefreshPeriod / 2) {
202                 // Assume we got here first and there's work to do
203                 gamesdk::ScopedTrace trace("doWork");
204                 mWorkDuration = mDoWork();
205                 mLastWorkRun = now;
206             }
207         }
208         lock.lock();
209     }
210 }
211 
212 } // namespace swappy
213