/* * 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 #include #include #include #include #include #include #include #include "PowerHintSession.h" #include "PowerSessionManager.h" namespace aidl { namespace google { namespace hardware { namespace power { namespace impl { namespace pixel { using ::android::base::StringPrintf; using std::chrono::duration_cast; using std::chrono::nanoseconds; using std::literals::chrono_literals::operator""s; constexpr char kPowerHalAdpfPidOffset[] = "vendor.powerhal.adpf.pid.offset"; constexpr char kPowerHalAdpfPidP[] = "vendor.powerhal.adpf.pid.p"; constexpr char kPowerHalAdpfPidI[] = "vendor.powerhal.adpf.pid.i"; constexpr char kPowerHalAdpfPidIClamp[] = "vendor.powerhal.adpf.pid.i_clamp"; constexpr char kPowerHalAdpfPidD[] = "vendor.powerhal.adpf.pid.d"; constexpr char kPowerHalAdpfPidInitialIntegral[] = "vendor.powerhal.adpf.pid.i_init"; constexpr char kPowerHalAdpfUclampEnable[] = "vendor.powerhal.adpf.uclamp"; constexpr char kPowerHalAdpfUclampCapRatio[] = "vendor.powerhal.adpf.uclamp.cap_ratio"; constexpr char kPowerHalAdpfUclampGranularity[] = "vendor.powerhal.adpf.uclamp.granularity"; constexpr char kPowerHalAdpfStaleTimeout[] = "vendor.powerhal.adpf.stale_timeout_ms"; constexpr char kPowerHalAdpfSamplingWindow[] = "vendor.powerhal.adpf.sampling_window"; 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 sched_setattr(int pid, struct sched_attr *attr, unsigned int flags) { static const bool kPowerHalAdpfUclamp = ::android::base::GetBoolProperty(kPowerHalAdpfUclampEnable, true); if (!kPowerHalAdpfUclamp) { ALOGV("PowerHintSession:%s: skip", __func__); return 0; } return syscall(__NR_sched_setattr, pid, attr, flags); } static inline void TRACE_ADPF_PID(uintptr_t session_id, int32_t uid, int32_t tgid, uint64_t count, int64_t err, int64_t integral, int64_t previous, int64_t p, int64_t i, int64_t d, int32_t output) { if (ATRACE_ENABLED()) { std::string idstr = StringPrintf("adpf.%" PRId32 "-%" PRId32 "-%" PRIxPTR, tgid, uid, session_id & 0xffff); std::string sz = StringPrintf("%s-pid.count", idstr.c_str()); ATRACE_INT(sz.c_str(), count); sz = StringPrintf("%s-pid.err", idstr.c_str()); ATRACE_INT(sz.c_str(), err); sz = StringPrintf("%s-pid.accu", idstr.c_str()); ATRACE_INT(sz.c_str(), integral); sz = StringPrintf("%s-pid.prev", idstr.c_str()); ATRACE_INT(sz.c_str(), previous); sz = StringPrintf("%s-pid.pOut", idstr.c_str()); ATRACE_INT(sz.c_str(), p); sz = StringPrintf("%s-pid.iOut", idstr.c_str()); ATRACE_INT(sz.c_str(), i); sz = StringPrintf("%s-pid.dOut", idstr.c_str()); ATRACE_INT(sz.c_str(), d); sz = StringPrintf("%s-pid.output", idstr.c_str()); ATRACE_INT(sz.c_str(), output); } } static int64_t ns_to_100us(int64_t ns) { return ns / 100000; } double getDoubleProperty(const char *prop, double value) { std::string result = ::android::base::GetProperty(prop, std::to_string(value).c_str()); if (!::android::base::ParseDouble(result.c_str(), &value)) { ALOGE("PowerHintSession : failed to parse double in %s", prop); } return value; } static double sPidOffset = getDoubleProperty(kPowerHalAdpfPidOffset, 0.0); static double sPidP = getDoubleProperty(kPowerHalAdpfPidP, 2.0); static double sPidI = getDoubleProperty(kPowerHalAdpfPidI, 0.001); static double sPidD = getDoubleProperty(kPowerHalAdpfPidD, 100.0); static const int64_t sPidIInit = (sPidI == 0) ? 0 : static_cast(::android::base::GetIntProperty( kPowerHalAdpfPidInitialIntegral, 100) / sPidI); static const int64_t sPidIClamp = (sPidI == 0) ? 0 : std::abs(static_cast(::android::base::GetIntProperty( kPowerHalAdpfPidIClamp, 512) / sPidI)); static const int sUclampCap = static_cast(getDoubleProperty(kPowerHalAdpfUclampCapRatio, 0.5) * 1024); static const uint32_t sUclampGranularity = ::android::base::GetUintProperty(kPowerHalAdpfUclampGranularity, 5); static const int64_t sStaleTimeoutMs = ::android::base::GetIntProperty(kPowerHalAdpfStaleTimeout, 3000); static const size_t sSamplingWindow = ::android::base::GetUintProperty(kPowerHalAdpfSamplingWindow, 1); } // namespace PowerHintSession::PowerHintSession(int32_t tgid, int32_t uid, const std::vector &threadIds, int64_t durationNanos) { mDescriptor = new AppHintDesc(tgid, uid, threadIds, sUclampCap); mDescriptor->duration = std::chrono::nanoseconds(durationNanos); mStaleHandler = sp(new StaleHandler(this, sStaleTimeoutMs)); mPowerManagerHandler = PowerSessionManager::getInstance(); if (ATRACE_ENABLED()) { std::string sz = StringPrintf("adpf.%" PRId32 "-%" PRId32 "-%" PRIxPTR "-target", mDescriptor->tgid, mDescriptor->uid, reinterpret_cast(this) & 0xffff); ATRACE_INT(sz.c_str(), (int64_t)mDescriptor->duration.count()); sz = StringPrintf("adpf.%" PRId32 "-%" PRId32 "-%" PRIxPTR "-active", mDescriptor->tgid, mDescriptor->uid, reinterpret_cast(this) & 0xffff); ATRACE_INT(sz.c_str(), mDescriptor->is_active.load()); } PowerSessionManager::getInstance()->addPowerSession(this); ALOGD("PowerHintSession created: %s", mDescriptor->toString().c_str()); } PowerHintSession::~PowerHintSession() { close(); ALOGD("PowerHintSession deleted: %s", mDescriptor->toString().c_str()); if (ATRACE_ENABLED()) { std::string sz = StringPrintf("adpf.%" PRId32 "-%" PRId32 "-%" PRIxPTR "-target", mDescriptor->tgid, mDescriptor->uid, reinterpret_cast(this) & 0xffff); ATRACE_INT(sz.c_str(), 0); sz = StringPrintf("adpf.%" PRId32 "-%" PRId32 "-%" PRIxPTR "-actl_last", mDescriptor->tgid, mDescriptor->uid, reinterpret_cast(this) & 0xffff); ATRACE_INT(sz.c_str(), 0); sz = StringPrintf("adpf.%" PRId32 "-%" PRId32 "-%" PRIxPTR "-active", mDescriptor->tgid, mDescriptor->uid, reinterpret_cast(this) & 0xffff); ATRACE_INT(sz.c_str(), 0); } delete mDescriptor; } void PowerHintSession::updateUniveralBoostMode() { PowerHintMonitor::getInstance()->getLooper()->sendMessage(mPowerManagerHandler, NULL); } int PowerHintSession::setUclamp(int32_t min, int32_t max) { std::lock_guard guard(mLock); min = std::max(0, min); min = std::min(min, max); max = std::max(0, max); max = std::max(min, max); if (ATRACE_ENABLED()) { std::string sz = StringPrintf("adpf.%" PRId32 "-%" PRId32 "-%" PRIxPTR "-min", mDescriptor->tgid, mDescriptor->uid, reinterpret_cast(this) & 0xffff); ATRACE_INT(sz.c_str(), min); sz = StringPrintf("adpf.%" PRId32 "-%" PRId32 "-%" PRIxPTR "-max", mDescriptor->tgid, mDescriptor->uid, reinterpret_cast(this) & 0xffff); ATRACE_INT(sz.c_str(), max); } for (const auto tid : mDescriptor->threadIds) { sched_attr attr = {}; attr.size = sizeof(attr); attr.sched_flags = (SCHED_FLAG_KEEP_ALL | SCHED_FLAG_UTIL_CLAMP); attr.sched_util_min = min; attr.sched_util_max = max; int ret = sched_setattr(tid, &attr, 0); if (ret) { ALOGW("sched_setattr failed for thread %d, err=%d", tid, errno); } ALOGV("PowerHintSession tid: %d, uclamp(%d, %d)", tid, min, max); } return 0; } ndk::ScopedAStatus PowerHintSession::pause() { if (!mDescriptor->is_active.load()) return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); // Reset to default uclamp value. setUclamp(0, 1024); mDescriptor->is_active.store(false); if (ATRACE_ENABLED()) { std::string sz = StringPrintf("%" PRId32 "-%" PRId32 "-%" PRIxPTR "-active", mDescriptor->tgid, mDescriptor->uid, reinterpret_cast(this) & 0xffff); ATRACE_INT(sz.c_str(), mDescriptor->is_active.load()); } updateUniveralBoostMode(); return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus PowerHintSession::resume() { if (mDescriptor->is_active.load()) return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); mDescriptor->is_active.store(true); mDescriptor->integral_error = std::max(sPidIInit, mDescriptor->integral_error); if (ATRACE_ENABLED()) { std::string sz = StringPrintf("%" PRId32 "-%" PRId32 "-%" PRIxPTR "-active", mDescriptor->tgid, mDescriptor->uid, reinterpret_cast(this) & 0xffff); ATRACE_INT(sz.c_str(), mDescriptor->is_active.load()); } updateUniveralBoostMode(); return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus PowerHintSession::close() { PowerHintMonitor::getInstance()->getLooper()->removeMessages(mStaleHandler); // Reset to (0, 1024) uclamp value -- instead of threads' original setting. setUclamp(0, 1024); PowerSessionManager::getInstance()->removePowerSession(this); updateUniveralBoostMode(); return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus PowerHintSession::updateTargetWorkDuration(int64_t targetDurationNanos) { if (targetDurationNanos <= 0) { ALOGE("Error: targetDurationNanos(%" PRId64 ") should bigger than 0", targetDurationNanos); return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); } ALOGV("update target duration: %" PRId64 " ns", targetDurationNanos); double ratio = targetDurationNanos == 0 ? 1.0 : mDescriptor->duration.count() / targetDurationNanos; mDescriptor->integral_error = std::max(sPidIInit, static_cast(mDescriptor->integral_error * ratio)); mDescriptor->duration = std::chrono::nanoseconds(targetDurationNanos); if (ATRACE_ENABLED()) { std::string sz = StringPrintf("adpf.%" PRId32 "-%" PRId32 "-%" PRIxPTR "-target", mDescriptor->tgid, mDescriptor->uid, reinterpret_cast(this) & 0xffff); ATRACE_INT(sz.c_str(), (int64_t)mDescriptor->duration.count()); } return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus PowerHintSession::reportActualWorkDuration( const std::vector &actualDurations) { if (mDescriptor->duration.count() == 0LL) { ALOGE("Expect to call updateTargetWorkDuration() first."); return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); } if (actualDurations.size() == 0) { ALOGE("Error: duration.size() shouldn't be %zu.", actualDurations.size()); return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); } if (!mDescriptor->is_active.load()) { ALOGE("Error: shouldn't report duration during pause state."); return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); } if (PowerHintMonitor::getInstance()->isRunning() && isStale()) { if (ATRACE_ENABLED()) { std::string sz = StringPrintf("adpf.%" PRId32 "-%" PRId32 "-%" PRIxPTR "-stale", mDescriptor->tgid, mDescriptor->uid, reinterpret_cast(this) & 0xffff); ATRACE_INT(sz.c_str(), 0); } mDescriptor->integral_error = std::max(sPidIInit, mDescriptor->integral_error); } int64_t targetDurationNanos = (int64_t)mDescriptor->duration.count(); size_t length = actualDurations.size(); size_t start = sSamplingWindow == 0 || sSamplingWindow > length ? 0 : length - sSamplingWindow; int64_t actualDurationNanos = 0; int64_t dt = ns_to_100us(targetDurationNanos); int64_t error = 0; int64_t derivative = 0; for (size_t i = start; i < length; i++) { actualDurationNanos = actualDurations[i].durationNanos; if (std::abs(actualDurationNanos) > targetDurationNanos * 20) { ALOGW("The actual duration is way far from the target (%" PRId64 " >> %" PRId64 ")", actualDurationNanos, targetDurationNanos); } if (ATRACE_ENABLED()) { std::string sz = StringPrintf("adpf.%" PRId32 "-%" PRId32 "-%" PRIxPTR "-actl_last", mDescriptor->tgid, mDescriptor->uid, reinterpret_cast(this) & 0xffff); ATRACE_INT(sz.c_str(), actualDurationNanos); sz = StringPrintf("adpf.%" PRId32 "-%" PRId32 "-%" PRIxPTR "-target", mDescriptor->tgid, mDescriptor->uid, reinterpret_cast(this) & 0xffff); ATRACE_INT(sz.c_str(), (int64_t)mDescriptor->duration.count()); } // PID control algorithm error = ns_to_100us(actualDurationNanos - targetDurationNanos) + static_cast(sPidOffset); mDescriptor->integral_error = mDescriptor->integral_error + error * dt; mDescriptor->integral_error = std::min(sPidIClamp, mDescriptor->integral_error); mDescriptor->integral_error = std::max(-sPidIClamp, mDescriptor->integral_error); derivative = (error - mDescriptor->previous_error) / dt; mDescriptor->previous_error = error; } int64_t pOut = static_cast(sPidP * error); int64_t iOut = static_cast(sPidI * mDescriptor->integral_error); int64_t dOut = static_cast(sPidD * derivative); int64_t output = pOut + iOut + dOut; TRACE_ADPF_PID(reinterpret_cast(this) & 0xffff, mDescriptor->uid, mDescriptor->tgid, mDescriptor->update_count, error, mDescriptor->integral_error, derivative, pOut, iOut, dOut, static_cast(output)); mDescriptor->update_count++; mStaleHandler->updateStaleTimer(); /* apply to all the threads in the group */ if (output != 0) { int next_min = std::min(sUclampCap, mDescriptor->current_min + static_cast(output)); next_min = std::max(0, next_min); if (std::abs(mDescriptor->current_min - next_min) > sUclampGranularity) { setUclamp(next_min, 1024); mDescriptor->current_min = next_min; } } return ndk::ScopedAStatus::ok(); } std::string AppHintDesc::toString() const { std::string out = StringPrintf("session %" PRIxPTR "\n", reinterpret_cast(this) & 0xffff); const int64_t durationNanos = duration.count(); out.append(StringPrintf(" duration: %" PRId64 " ns\n", durationNanos)); out.append(StringPrintf(" uclamp.min: %d \n", current_min)); out.append(StringPrintf(" uid: %d, tgid: %d\n", uid, tgid)); out.append(" threadIds: ["); bool first = true; for (int tid : threadIds) { if (!first) { out.append(", "); } out.append(std::to_string(tid)); first = false; } out.append("]\n"); return out; } bool PowerHintSession::isActive() { return mDescriptor->is_active.load(); } bool PowerHintSession::isStale() { auto now = std::chrono::steady_clock::now(); return now >= mStaleHandler->getStaleTime(); } void PowerHintSession::setStale() { if (ATRACE_ENABLED()) { std::string sz = StringPrintf("adpf.%" PRId32 "-%" PRId32 "-%" PRIxPTR "-stale", mDescriptor->tgid, mDescriptor->uid, reinterpret_cast(this) & 0xffff); ATRACE_INT(sz.c_str(), 1); } // Reset to default uclamp value. setUclamp(0, 1024); // Deliver a task to check if all sessions are inactive. updateUniveralBoostMode(); } void PowerHintSession::StaleHandler::updateStaleTimer() { std::lock_guard guard(mStaleLock); if (PowerHintMonitor::getInstance()->isRunning()) { auto when = getStaleTime(); auto now = std::chrono::steady_clock::now(); mLastUpdatedTime.store(now); if (now > when) { mSession->updateUniveralBoostMode(); } if (!mIsMonitoringStale.load()) { auto next = getStaleTime(); PowerHintMonitor::getInstance()->getLooper()->sendMessageDelayed( duration_cast(next - now).count(), this, NULL); mIsMonitoringStale.store(true); } } } time_point PowerHintSession::StaleHandler::getStaleTime() { return mLastUpdatedTime.load() + kStaleTimeout; } void PowerHintSession::StaleHandler::handleMessage(const Message &) { std::lock_guard guard(mStaleLock); auto now = std::chrono::steady_clock::now(); auto when = getStaleTime(); // Check if the session is stale based on the last_updated_time. if (now > when) { mSession->setStale(); mIsMonitoringStale.store(false); return; } // Schedule for the next checking time. PowerHintMonitor::getInstance()->getLooper()->sendMessageDelayed( duration_cast(when - now).count(), this, NULL); } } // namespace pixel } // namespace impl } // namespace power } // namespace hardware } // namespace google } // namespace aidl