blob: 8e0e084cd217d16446f426575cc1f4d1181143db [file] [log] [blame]
/*
* Copyright 2024 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 "JankTracker.h"
#include <android/gui/IJankListener.h>
#include "BackgroundExecutor.h"
namespace android {
namespace {
constexpr size_t kJankDataBatchSize = 50;
} // anonymous namespace
std::atomic<size_t> JankTracker::sListenerCount(0);
std::atomic<bool> JankTracker::sCollectAllJankDataForTesting(false);
JankTracker::~JankTracker() {}
void JankTracker::addJankListener(int32_t layerId, sp<IBinder> listener) {
// Increment right away, so that if an onJankData call comes in before the background thread has
// added this listener, it will not drop the data.
sListenerCount++;
BackgroundExecutor::getLowPriorityInstance().sendCallbacks(
{[layerId, listener = std::move(listener)]() {
JankTracker& tracker = getInstance();
const std::lock_guard<std::mutex> _l(tracker.mLock);
tracker.addJankListenerLocked(layerId, listener);
}});
}
void JankTracker::flushJankData(int32_t layerId) {
BackgroundExecutor::getLowPriorityInstance().sendCallbacks(
{[layerId]() { getInstance().doFlushJankData(layerId); }});
}
void JankTracker::removeJankListener(int32_t layerId, sp<IBinder> listener, int64_t afterVsync) {
BackgroundExecutor::getLowPriorityInstance().sendCallbacks(
{[layerId, listener = std::move(listener), afterVsync]() {
JankTracker& tracker = getInstance();
const std::lock_guard<std::mutex> _l(tracker.mLock);
tracker.markJankListenerForRemovalLocked(layerId, listener, afterVsync);
}});
}
void JankTracker::onJankData(int32_t layerId, gui::JankData data) {
if (sListenerCount == 0) {
return;
}
BackgroundExecutor::getLowPriorityInstance().sendCallbacks(
{[layerId, data = std::move(data)]() {
JankTracker& tracker = getInstance();
tracker.mLock.lock();
bool hasListeners = tracker.mJankListeners.count(layerId) > 0;
tracker.mLock.unlock();
if (!hasListeners && !sCollectAllJankDataForTesting) {
return;
}
tracker.mJankDataLock.lock();
tracker.mJankData.emplace(layerId, data);
size_t count = tracker.mJankData.count(layerId);
tracker.mJankDataLock.unlock();
if (count >= kJankDataBatchSize && !sCollectAllJankDataForTesting) {
tracker.doFlushJankData(layerId);
}
}});
}
void JankTracker::addJankListenerLocked(int32_t layerId, sp<IBinder> listener) {
for (auto it = mJankListeners.find(layerId); it != mJankListeners.end(); it++) {
if (it->second.mListener == listener) {
// Undo the duplicate increment in addJankListener.
sListenerCount--;
return;
}
}
mJankListeners.emplace(layerId, std::move(listener));
}
void JankTracker::doFlushJankData(int32_t layerId) {
std::vector<gui::JankData> jankData;
int64_t maxVsync = transferAvailableJankData(layerId, jankData);
std::vector<sp<IBinder>> toSend;
mLock.lock();
for (auto it = mJankListeners.find(layerId); it != mJankListeners.end();) {
if (!jankData.empty()) {
toSend.emplace_back(it->second.mListener);
}
int64_t removeAfter = it->second.mRemoveAfter;
if (removeAfter != -1 && removeAfter <= maxVsync) {
it = mJankListeners.erase(it);
sListenerCount--;
} else {
it++;
}
}
mLock.unlock();
for (const auto& listener : toSend) {
binder::Status status = interface_cast<gui::IJankListener>(listener)->onJankData(jankData);
if (status.exceptionCode() == binder::Status::EX_NULL_POINTER) {
// Remove any listeners, where the App side has gone away, without
// deregistering.
dropJankListener(layerId, listener);
}
}
}
void JankTracker::markJankListenerForRemovalLocked(int32_t layerId, sp<IBinder> listener,
int64_t afterVysnc) {
for (auto it = mJankListeners.find(layerId); it != mJankListeners.end(); it++) {
if (it->second.mListener == listener) {
it->second.mRemoveAfter = std::max(static_cast<int64_t>(0), afterVysnc);
return;
}
}
}
int64_t JankTracker::transferAvailableJankData(int32_t layerId,
std::vector<gui::JankData>& outJankData) {
const std::lock_guard<std::mutex> _l(mJankDataLock);
int64_t maxVsync = 0;
auto range = mJankData.equal_range(layerId);
for (auto it = range.first; it != range.second;) {
maxVsync = std::max(it->second.frameVsyncId, maxVsync);
outJankData.emplace_back(std::move(it->second));
it = mJankData.erase(it);
}
return maxVsync;
}
void JankTracker::dropJankListener(int32_t layerId, sp<IBinder> listener) {
const std::lock_guard<std::mutex> _l(mLock);
for (auto it = mJankListeners.find(layerId); it != mJankListeners.end(); it++) {
if (it->second.mListener == listener) {
mJankListeners.erase(it);
sListenerCount--;
return;
}
}
}
void JankTracker::clearAndStartCollectingAllJankDataForTesting() {
BackgroundExecutor::getLowPriorityInstance().flushQueue();
// Clear all past tracked jank data.
JankTracker& tracker = getInstance();
const std::lock_guard<std::mutex> _l(tracker.mJankDataLock);
tracker.mJankData.clear();
// Pretend there's at least one listener.
sListenerCount++;
sCollectAllJankDataForTesting = true;
}
std::vector<gui::JankData> JankTracker::getCollectedJankDataForTesting(int32_t layerId) {
JankTracker& tracker = getInstance();
const std::lock_guard<std::mutex> _l(tracker.mJankDataLock);
auto range = tracker.mJankData.equal_range(layerId);
std::vector<gui::JankData> result;
std::transform(range.first, range.second, std::back_inserter(result),
[](std::pair<int32_t, gui::JankData> layerIdToJankData) {
return layerIdToJankData.second;
});
return result;
}
void JankTracker::clearAndStopCollectingAllJankDataForTesting() {
// Undo startCollectingAllJankDataForTesting.
sListenerCount--;
sCollectAllJankDataForTesting = false;
// Clear all tracked jank data.
JankTracker& tracker = getInstance();
const std::lock_guard<std::mutex> _l(tracker.mJankDataLock);
tracker.mJankData.clear();
}
} // namespace android