blob: f386db55a07dc11330a91ea2a9c958c27f54a53e [file] [log] [blame]
/*
* Copyright 2021 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.
*/
#define LOG_TAG "powerhal-libperfmgr"
#define ATRACE_TAG (ATRACE_TAG_POWER | ATRACE_TAG_HAL)
#include "PowerSessionManager.h"
#include <android-base/file.h>
#include <android-base/stringprintf.h>
#include <log/log.h>
#include <perfmgr/HintManager.h>
#include <private/android_filesystem_config.h>
#include <processgroup/processgroup.h>
#include <sys/syscall.h>
#include <utils/Trace.h>
#include "AdpfTypes.h"
#include "AppDescriptorTrace.h"
#include "AppHintDesc.h"
#include "tests/mocks/MockHintManager.h"
namespace aidl {
namespace google {
namespace hardware {
namespace power {
namespace impl {
namespace pixel {
using ::android::perfmgr::HintManager;
namespace {
/* there is no glibc or bionic wrapper */
struct sched_attr {
__u32 size;
__u32 sched_policy;
__u64 sched_flags;
__s32 sched_nice;
__u32 sched_priority;
__u64 sched_runtime;
__u64 sched_deadline;
__u64 sched_period;
__u32 sched_util_min;
__u32 sched_util_max;
};
static int set_uclamp(int tid, UclampRange range) {
// Ensure min and max are bounded by the range limits and each other
range.uclampMin = std::min(std::max(kUclampMin, range.uclampMin), kUclampMax);
range.uclampMax = std::min(std::max(range.uclampMax, range.uclampMin), kUclampMax);
sched_attr attr = {};
attr.size = sizeof(attr);
attr.sched_flags =
(SCHED_FLAG_KEEP_ALL | SCHED_FLAG_UTIL_CLAMP_MIN | SCHED_FLAG_UTIL_CLAMP_MAX);
attr.sched_util_min = range.uclampMin;
attr.sched_util_max = range.uclampMax;
const int ret = syscall(__NR_sched_setattr, tid, attr, 0);
if (ret) {
ALOGW("sched_setattr failed for thread %d, err=%d", tid, errno);
return errno;
}
return 0;
}
} // namespace
template <class HintManagerT>
void PowerSessionManager<HintManagerT>::updateHintMode(const std::string &mode, bool enabled) {
if (enabled && mode.compare(0, 8, "REFRESH_") == 0) {
if (mode.compare("REFRESH_120FPS") == 0) {
mDisplayRefreshRate = 120;
} else if (mode.compare("REFRESH_90FPS") == 0) {
mDisplayRefreshRate = 90;
} else if (mode.compare("REFRESH_60FPS") == 0) {
mDisplayRefreshRate = 60;
}
}
if (HintManager::GetInstance()->GetAdpfProfile()) {
HintManager::GetInstance()->SetAdpfProfile(mode);
}
}
template <class HintManagerT>
void PowerSessionManager<HintManagerT>::updateHintBoost(const std::string &boost,
int32_t durationMs) {
ATRACE_CALL();
ALOGV("PowerSessionManager::updateHintBoost: boost: %s, durationMs: %d", boost.c_str(),
durationMs);
}
template <class HintManagerT>
int PowerSessionManager<HintManagerT>::getDisplayRefreshRate() {
return mDisplayRefreshRate;
}
template <class HintManagerT>
void PowerSessionManager<HintManagerT>::addPowerSession(
const std::string &idString, const std::shared_ptr<AppHintDesc> &sessionDescriptor,
const std::shared_ptr<AppDescriptorTrace> &sessionTrace,
const std::vector<int32_t> &threadIds) {
if (!sessionDescriptor) {
ALOGE("sessionDescriptor is null. PowerSessionManager failed to add power session: %s",
idString.c_str());
return;
}
const auto timeNow = std::chrono::steady_clock::now();
SessionValueEntry sve;
sve.tgid = sessionDescriptor->tgid;
sve.uid = sessionDescriptor->uid;
sve.idString = idString;
sve.isActive = sessionDescriptor->is_active;
sve.isAppSession = sessionDescriptor->uid >= AID_APP_START;
sve.lastUpdatedTime = timeNow;
sve.votes = std::make_shared<Votes>();
sve.sessionTrace = sessionTrace;
sve.votes->add(
static_cast<std::underlying_type_t<AdpfVoteType>>(AdpfVoteType::CPU_VOTE_DEFAULT),
CpuVote(false, timeNow, sessionDescriptor->targetNs, kUclampMin, kUclampMax));
bool addedRes = false;
{
std::lock_guard<std::mutex> lock(mSessionTaskMapMutex);
addedRes = mSessionTaskMap.add(sessionDescriptor->sessionId, sve, {});
}
if (!addedRes) {
ALOGE("sessionTaskMap failed to add power session: %" PRId64, sessionDescriptor->sessionId);
}
setThreadsFromPowerSession(sessionDescriptor->sessionId, threadIds);
}
template <class HintManagerT>
void PowerSessionManager<HintManagerT>::removePowerSession(int64_t sessionId) {
// To remove a session we also need to undo the effects the session
// has on currently enabled votes which means setting vote to inactive
// and then forceing a uclamp update to occur
forceSessionActive(sessionId, false);
std::vector<pid_t> addedThreads;
std::vector<pid_t> removedThreads;
{
// Wait till end to remove session because it needs to be around for apply U clamp
// to work above since applying the uclamp needs a valid session id
std::lock_guard<std::mutex> lock(mSessionTaskMapMutex);
mSessionTaskMap.replace(sessionId, {}, &addedThreads, &removedThreads);
mSessionTaskMap.remove(sessionId);
}
for (auto tid : removedThreads) {
if (!SetTaskProfiles(tid, {"NoResetUclampGrp"})) {
ALOGE("Failed to set NoResetUclampGrp task profile for tid:%d", tid);
}
}
unregisterSession(sessionId);
}
template <class HintManagerT>
void PowerSessionManager<HintManagerT>::setThreadsFromPowerSession(
int64_t sessionId, const std::vector<int32_t> &threadIds) {
std::vector<pid_t> addedThreads;
std::vector<pid_t> removedThreads;
forceSessionActive(sessionId, false);
{
std::lock_guard<std::mutex> lock(mSessionTaskMapMutex);
mSessionTaskMap.replace(sessionId, threadIds, &addedThreads, &removedThreads);
}
for (auto tid : addedThreads) {
if (!SetTaskProfiles(tid, {"ResetUclampGrp"})) {
ALOGE("Failed to set ResetUclampGrp task profile for tid:%d", tid);
}
}
for (auto tid : removedThreads) {
if (!SetTaskProfiles(tid, {"NoResetUclampGrp"})) {
ALOGE("Failed to set NoResetUclampGrp task profile for tid:%d", tid);
}
}
forceSessionActive(sessionId, true);
}
template <class HintManagerT>
std::optional<bool> PowerSessionManager<HintManagerT>::isAnyAppSessionActive() {
bool isAnyAppSessionActive = false;
{
std::lock_guard<std::mutex> lock(mSessionTaskMapMutex);
isAnyAppSessionActive =
mSessionTaskMap.isAnyAppSessionActive(std::chrono::steady_clock::now());
}
return isAnyAppSessionActive;
}
template <class HintManagerT>
void PowerSessionManager<HintManagerT>::updateUniversalBoostMode() {
const auto active = isAnyAppSessionActive();
if (!active.has_value()) {
return;
}
if (active.value()) {
disableSystemTopAppBoost();
} else {
enableSystemTopAppBoost();
}
}
template <class HintManagerT>
void PowerSessionManager<HintManagerT>::dumpToFd(int fd) {
std::ostringstream dump_buf;
dump_buf << "========== Begin PowerSessionManager ADPF list ==========\n";
std::lock_guard<std::mutex> lock(mSessionTaskMapMutex);
mSessionTaskMap.forEachSessionValTasks(
[&](auto /* sessionId */, const auto &sessionVal, const auto &tasks) {
sessionVal.dump(dump_buf);
dump_buf << " Tid:Ref[";
size_t tasksLen = tasks.size();
for (auto taskId : tasks) {
dump_buf << taskId << ":";
const auto &sessionIds = mSessionTaskMap.getSessionIds(taskId);
if (!sessionIds.empty()) {
dump_buf << sessionIds.size();
}
if (tasksLen > 0) {
dump_buf << ", ";
--tasksLen;
}
}
dump_buf << "]\n";
});
dump_buf << "========== End PowerSessionManager ADPF list ==========\n";
if (!::android::base::WriteStringToFd(dump_buf.str(), fd)) {
ALOGE("Failed to dump one of session list to fd:%d", fd);
}
}
template <class HintManagerT>
void PowerSessionManager<HintManagerT>::pause(int64_t sessionId) {
{
std::lock_guard<std::mutex> lock(mSessionTaskMapMutex);
auto sessValPtr = mSessionTaskMap.findSession(sessionId);
if (nullptr == sessValPtr) {
ALOGW("Pause failed, session is null %" PRId64, sessionId);
return;
}
if (!sessValPtr->isActive) {
ALOGW("Sess(%" PRId64 "), cannot pause, already inActive", sessionId);
return;
}
sessValPtr->isActive = false;
}
applyCpuAndGpuVotes(sessionId, std::chrono::steady_clock::now());
updateUniversalBoostMode();
}
template <class HintManagerT>
void PowerSessionManager<HintManagerT>::resume(int64_t sessionId) {
{
std::lock_guard<std::mutex> lock(mSessionTaskMapMutex);
auto sessValPtr = mSessionTaskMap.findSession(sessionId);
if (nullptr == sessValPtr) {
ALOGW("Resume failed, session is null %" PRId64, sessionId);
return;
}
if (sessValPtr->isActive) {
ALOGW("Sess(%" PRId64 "), cannot resume, already active", sessionId);
return;
}
sessValPtr->isActive = true;
}
applyCpuAndGpuVotes(sessionId, std::chrono::steady_clock::now());
updateUniversalBoostMode();
}
template <class HintManagerT>
void PowerSessionManager<HintManagerT>::updateTargetWorkDuration(
int64_t sessionId, AdpfVoteType voteId, std::chrono::nanoseconds durationNs) {
int voteIdInt = static_cast<std::underlying_type_t<AdpfVoteType>>(voteId);
std::lock_guard<std::mutex> lock(mSessionTaskMapMutex);
auto sessValPtr = mSessionTaskMap.findSession(sessionId);
if (nullptr == sessValPtr) {
ALOGE("Failed to updateTargetWorkDuration, session val is null id: %" PRId64, sessionId);
return;
}
sessValPtr->votes->updateDuration(voteIdInt, durationNs);
// Note, for now we are not recalculating and applying uclamp because
// that maintains behavior from before. In the future we may want to
// revisit that decision.
}
template <typename T>
auto shouldScheduleTimeout(Votes const &votes, int vote_id, std::chrono::time_point<T> deadline)
-> bool {
return !votes.voteIsActive(vote_id) || deadline < votes.voteTimeout(vote_id);
}
template <class HintManagerT>
void PowerSessionManager<HintManagerT>::voteSet(int64_t sessionId, AdpfVoteType voteId,
int uclampMin, int uclampMax,
std::chrono::steady_clock::time_point startTime,
std::chrono::nanoseconds durationNs) {
const int voteIdInt = static_cast<std::underlying_type_t<AdpfVoteType>>(voteId);
const auto timeoutDeadline = startTime + durationNs;
bool scheduleTimeout = false;
{
std::lock_guard lock(mSessionTaskMapMutex);
auto session = mSessionTaskMap.findSession(sessionId);
if (!session) {
// Because of the async nature of some events an event for a session
// that has been removed is a possibility
return;
}
scheduleTimeout = shouldScheduleTimeout(*session->votes, voteIdInt, timeoutDeadline),
mSessionTaskMap.addVote(sessionId, voteIdInt, uclampMin, uclampMax, startTime, durationNs);
if (ATRACE_ENABLED()) {
ATRACE_INT(session->sessionTrace->trace_votes[voteIdInt].c_str(), uclampMin);
}
session->lastUpdatedTime = startTime;
applyUclampLocked(sessionId, startTime);
}
if (scheduleTimeout) {
mEventSessionTimeoutWorker.schedule(
{.timeStamp = startTime, .sessionId = sessionId, .voteId = voteIdInt},
timeoutDeadline);
}
}
template <class HintManagerT>
void PowerSessionManager<HintManagerT>::voteSet(int64_t sessionId, AdpfVoteType voteId,
Cycles capacity,
std::chrono::steady_clock::time_point startTime,
std::chrono::nanoseconds durationNs) {
const int voteIdInt = static_cast<std::underlying_type_t<AdpfVoteType>>(voteId);
const auto timeoutDeadline = startTime + durationNs;
bool scheduleTimeout = false;
{
std::lock_guard lock(mSessionTaskMapMutex);
auto session = mSessionTaskMap.findSession(sessionId);
if (!session) {
return;
}
scheduleTimeout = shouldScheduleTimeout(*session->votes, voteIdInt, timeoutDeadline),
mSessionTaskMap.addGpuVote(sessionId, voteIdInt, capacity, startTime, durationNs);
if (ATRACE_ENABLED()) {
ATRACE_INT(session->sessionTrace->trace_votes[voteIdInt].c_str(),
static_cast<int>(capacity));
}
session->lastUpdatedTime = startTime;
applyGpuVotesLocked(sessionId, startTime);
}
if (scheduleTimeout) {
mEventSessionTimeoutWorker.schedule(
{.timeStamp = startTime, .sessionId = sessionId, .voteId = voteIdInt},
timeoutDeadline);
}
}
template <class HintManagerT>
void PowerSessionManager<HintManagerT>::disableBoosts(int64_t sessionId) {
{
std::lock_guard<std::mutex> lock(mSessionTaskMapMutex);
auto sessValPtr = mSessionTaskMap.findSession(sessionId);
if (nullptr == sessValPtr) {
// Because of the async nature of some events an event for a session
// that has been removed is a possibility
return;
}
// sessValPtr->disableBoosts();
for (auto vid : {AdpfVoteType::CPU_LOAD_UP, AdpfVoteType::CPU_LOAD_RESET,
AdpfVoteType::CPU_LOAD_RESUME, AdpfVoteType::VOTE_POWER_EFFICIENCY,
AdpfVoteType::GPU_LOAD_UP, AdpfVoteType::GPU_LOAD_RESET}) {
auto vint = static_cast<std::underlying_type_t<AdpfVoteType>>(vid);
sessValPtr->votes->setUseVote(vint, false);
if (ATRACE_ENABLED()) {
ATRACE_INT(sessValPtr->sessionTrace->trace_votes[vint].c_str(), 0);
}
}
}
}
template <class HintManagerT>
void PowerSessionManager<HintManagerT>::enableSystemTopAppBoost() {
if (HintManager::GetInstance()->IsHintSupported(kDisableBoostHintName)) {
ALOGV("PowerSessionManager::enableSystemTopAppBoost!!");
HintManager::GetInstance()->EndHint(kDisableBoostHintName);
}
}
template <class HintManagerT>
void PowerSessionManager<HintManagerT>::disableSystemTopAppBoost() {
if (HintManager::GetInstance()->IsHintSupported(kDisableBoostHintName)) {
ALOGV("PowerSessionManager::disableSystemTopAppBoost!!");
HintManager::GetInstance()->DoHint(kDisableBoostHintName);
}
}
template <class HintManagerT>
void PowerSessionManager<HintManagerT>::handleEvent(const EventSessionTimeout &eventTimeout) {
bool recalcUclamp = false;
const auto tNow = std::chrono::steady_clock::now();
{
std::lock_guard<std::mutex> lock(mSessionTaskMapMutex);
auto sessValPtr = mSessionTaskMap.findSession(eventTimeout.sessionId);
if (nullptr == sessValPtr) {
// It is ok for session timeouts to fire after a session has been
// removed
return;
}
// To minimize the number of events pushed into the queue, we are using
// the following logic to make use of a single timeout event which will
// requeue itself if the timeout has been changed since it was added to
// the work queue. Requeue Logic:
// if vote active and vote timeout <= sched time
// then deactivate vote and recalc uclamp (near end of function)
// if vote active and vote timeout > sched time
// then requeue timeout event for new deadline (which is vote timeout)
const bool voteIsActive = sessValPtr->votes->voteIsActive(eventTimeout.voteId);
const auto voteTimeout = sessValPtr->votes->voteTimeout(eventTimeout.voteId);
if (voteIsActive) {
if (voteTimeout <= tNow) {
sessValPtr->votes->setUseVote(eventTimeout.voteId, false);
recalcUclamp = true;
if (ATRACE_ENABLED()) {
ATRACE_INT(sessValPtr->sessionTrace->trace_votes[eventTimeout.voteId].c_str(),
0);
}
} else {
// Can unlock sooner than we do
auto eventTimeout2 = eventTimeout;
mEventSessionTimeoutWorker.schedule(eventTimeout2, voteTimeout);
}
}
}
if (!recalcUclamp) {
return;
}
// It is important to use the correct time here, time now is more reasonable
// than trying to use the event's timestamp which will be slightly off given
// the background priority queue introduces latency
applyCpuAndGpuVotes(eventTimeout.sessionId, tNow);
updateUniversalBoostMode();
}
template <class HintManagerT>
void PowerSessionManager<HintManagerT>::applyUclampLocked(
int64_t sessionId, std::chrono::steady_clock::time_point timePoint) {
auto config = HintManager::GetInstance()->GetAdpfProfile();
{
// TODO(kevindubois) un-indent this in followup patch to reduce churn.
auto sessValPtr = mSessionTaskMap.findSession(sessionId);
if (nullptr == sessValPtr) {
return;
}
if (!config->mUclampMinOn) {
ALOGV("PowerSessionManager::set_uclamp: skip");
} else {
auto &threadList = mSessionTaskMap.getTaskIds(sessionId);
auto tidIter = threadList.begin();
while (tidIter != threadList.end()) {
UclampRange uclampRange;
mSessionTaskMap.getTaskVoteRange(*tidIter, timePoint, uclampRange,
config->mUclampMaxEfficientBase,
config->mUclampMaxEfficientOffset);
int stat = set_uclamp(*tidIter, uclampRange);
if (stat == ESRCH) {
ALOGV("Removing dead thread %d from hint session %s.", *tidIter,
sessValPtr->idString.c_str());
if (mSessionTaskMap.removeDeadTaskSessionMap(sessionId, *tidIter)) {
ALOGV("Removed dead thread-session map.");
}
tidIter = threadList.erase(tidIter);
} else {
tidIter++;
}
}
}
sessValPtr->lastUpdatedTime = timePoint;
}
}
template <class HintManagerT>
void PowerSessionManager<HintManagerT>::applyGpuVotesLocked(
int64_t sessionId, std::chrono::steady_clock::time_point timePoint) {
auto const sessValPtr = mSessionTaskMap.findSession(sessionId);
if (!sessValPtr) {
return;
}
auto const gpuVotingOn = HintManager::GetInstance()->GetAdpfProfile()->mGpuBoostOn;
if (mGpuCapacityNode && gpuVotingOn) {
auto const capacity = mSessionTaskMap.getSessionsGpuCapacity(timePoint);
(*mGpuCapacityNode)->set_gpu_capacity(capacity);
}
sessValPtr->lastUpdatedTime = timePoint;
}
template <class HintManagerT>
void PowerSessionManager<HintManagerT>::applyCpuAndGpuVotes(
int64_t sessionId, std::chrono::steady_clock::time_point timePoint) {
std::lock_guard lock(mSessionTaskMapMutex);
applyUclampLocked(sessionId, timePoint);
applyGpuVotesLocked(sessionId, timePoint);
}
template <class HintManagerT>
std::optional<Frequency> PowerSessionManager<HintManagerT>::gpuFrequency() const {
if (mGpuCapacityNode) {
return (*mGpuCapacityNode)->gpu_frequency();
}
return {};
}
template <class HintManagerT>
void PowerSessionManager<HintManagerT>::forceSessionActive(int64_t sessionId, bool isActive) {
{
std::lock_guard<std::mutex> lock(mSessionTaskMapMutex);
auto sessValPtr = mSessionTaskMap.findSession(sessionId);
if (nullptr == sessValPtr) {
return;
}
sessValPtr->isActive = isActive;
}
// As currently written, call needs to occur synchronously so as to ensure
// that the SessionId remains valid and mapped to the proper threads/tasks
// which enables apply u clamp to work correctly
applyCpuAndGpuVotes(sessionId, std::chrono::steady_clock::now());
updateUniversalBoostMode();
}
template <class HintManagerT>
void PowerSessionManager<HintManagerT>::setPreferPowerEfficiency(int64_t sessionId, bool enabled) {
std::lock_guard<std::mutex> lock(mSessionTaskMapMutex);
auto sessValPtr = mSessionTaskMap.findSession(sessionId);
if (nullptr == sessValPtr) {
return;
}
if (enabled != sessValPtr->isPowerEfficient) {
sessValPtr->isPowerEfficient = enabled;
applyUclampLocked(sessionId, std::chrono::steady_clock::now());
}
}
template <class HintManagerT>
void PowerSessionManager<HintManagerT>::registerSession(std::shared_ptr<void> session,
int64_t sessionId) {
std::lock_guard<std::mutex> lock(mSessionMapMutex);
mSessionMap[sessionId] = session;
}
template <class HintManagerT>
void PowerSessionManager<HintManagerT>::unregisterSession(int64_t sessionId) {
std::lock_guard<std::mutex> lock(mSessionMapMutex);
mSessionMap.erase(sessionId);
}
template <class HintManagerT>
std::shared_ptr<void> PowerSessionManager<HintManagerT>::getSession(int64_t sessionId) {
std::scoped_lock lock(mSessionMapMutex);
auto ptr = mSessionMap.find(sessionId);
if (ptr == mSessionMap.end()) {
return nullptr;
}
std::shared_ptr<void> out = ptr->second.lock();
if (!out) {
mSessionMap.erase(sessionId);
return nullptr;
}
return out;
}
template <class HintManagerT>
void PowerSessionManager<HintManagerT>::clear() {
std::scoped_lock lock(mSessionMapMutex);
mSessionMap.clear();
}
template class PowerSessionManager<>;
template class PowerSessionManager<testing::NiceMock<mock::pixel::MockHintManager>>;
} // namespace pixel
} // namespace impl
} // namespace power
} // namespace hardware
} // namespace google
} // namespace aidl