* Copyright (c) 2024 Huawei Device Co., Ltd.
* 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 "ark_idle_monitor.h"
#if defined(LINUX_PLATFORM) || defined(OHOS_PLATFORM)
#include <dlfcn.h>
#include <signal.h>
#endif
#include "utils/log.h"
#include <cinttypes>
#if defined(ENABLE_FFRT)
#include "ffrt.h"
#include "c/executor_task.h"
#endif
#ifdef ENABLE_UCOLLECTION
#include "cpu_collector_client.h"
#endif
#if defined(ENABLE_HITRACE) || defined(ENABLE_EVENT_HANDLER)
#include "hitrace/trace.h"
#include "hitrace_meter.h"
#include "parameter.h"
#include "parameters.h"
#include "musl_preinit_common.h"
#include "memory_trace.h"
#endif
#include "ark_native_engine.h"
namespace panda::ecmascript {
const std::string RES_SCHED_CLIENT_SO = "libressched_client.z.so";
using ReportDataFunc = void (*)(uint32_t resType, int64_t value,
const std::unordered_map<std::string, std::string>& payload);
class CallbackTimerScope {
public:
static constexpr int64_t CALLBACK_TIMEOUT_THRESHOLD_MS = 100;
explicit CallbackTimerScope(const char* name, int64_t thresholdMs = CALLBACK_TIMEOUT_THRESHOLD_MS)
: name_(name), thresholdMs_(thresholdMs), startTime_(std::chrono::steady_clock::now()) {}
~CallbackTimerScope()
{
auto endTime = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime_).count();
if (duration > thresholdMs_) {
HILOG_WARN("ArkIdleMonitor: %{public}s timeout", name_);
}
}
private:
const char* name_;
int64_t thresholdMs_;
std::chrono::steady_clock::time_point startTime_;
};
#if defined(ENABLE_EVENT_HANDLER)
static constexpr uint64_t IDLE_GC_TIME_MIN = 1000;
static constexpr uint64_t IDLE_GC_TIME_MAX = 10000;
uint64_t ArkIdleMonitor::gIdleMonitoringInterval = ArkIdleMonitor::GetIdleMonitoringInterval();
bool ArkIdleMonitor::gEnableIdleGC =
OHOS::system::GetBoolParameter("persist.ark.enableidlegc", true);
bool ArkIdleMonitor::gEnableDeferFreeze =
OHOS::system::GetBoolParameter("persist.ark.deferfreeze", true);
#else
uint64_t ArkIdleMonitor::gIdleMonitoringInterval = 1000;
#endif
uint64_t ArkIdleMonitor::gDelayOverTime = gIdleMonitoringInterval + 100;
class TraceScope {
public:
explicit TraceScope(std::string context) : context_(context)
{
#ifdef ENABLE_HITRACE
StartTrace(HITRACE_TAG_ACE, context_);
#endif
};
~TraceScope()
{
#ifdef ENABLE_HITRACE
FinishTrace(HITRACE_TAG_ACE);
#endif
}
private:
std::string context_;
};
void ArkIdleMonitor::NotifyLooperIdleStart(int64_t timestamp, [[maybe_unused]] int idleTime)
{
SetIdleState(true);
AddIdleNotifyCount();
recordedRunningNotifyInterval_.Push(timestamp - idleEndTimestamp_);
#ifndef DISABLE_SHORT_IDLE_CHECK
CheckShortIdleTask(timestamp, idleTime);
#endif
SetNotifyTimestamp(timestamp);
}
void ArkIdleMonitor::CheckShortIdleTask(int64_t timestamp, int idleTime)
{
#if defined(ENABLE_FFRT)
while (handlerWaitToStopCount_ > 0) {
if (timerHandlerQueue_.size() <= 0) {
handlerWaitToStopCount_ = 0;
break;
}
int handler = timerHandlerQueue_.front();
timerHandlerQueue_.pop();
int ret = ffrt_timer_stop(ffrt_qos_user_initiated, handler);
if (ret != 0) {
HILOG_ERROR("ArkIdleMonitor: ffrt_timer_stop error handler: timerHandler='%{public}d', ret='%{public}d'",
handler, ret);
}
handlerWaitToStopCount_--;
}
#endif
if (triggeredGC_ && mainVM_ != nullptr) {
#ifdef ENABLE_HITRACE
StartTrace(HITRACE_TAG_ACE, "NotifyLooperIdleStart::TriggeredGC");
#endif
triggeredGC_ = JSNApi::NotifyLooperIdleStart(mainVM_, timestamp, idleTime);
#ifdef ENABLE_HITRACE
FinishTrace(HITRACE_TAG_ACE);
#endif
return;
}
if (ShouldTryTriggerGC(timestamp - GetNotifyTimestamp()) &&
idleTime > MIN_TRIGGER_GC_IDLE_INTERVAL &&
needCheckIntervalIdle_) {
PostIdleCheckTask();
}
if (!needCheckIntervalIdle_) {
needCheckIntervalIdle_ = true;
}
}
bool ArkIdleMonitor::ShouldTryTriggerGC(int64_t interval)
{
if (interval < MIN_TRIGGER_GC_IDLE_INTERVAL ||
recordedIdleNotifyInterval_.Count() != IDLE_CHECK_INTERVAL_LENGTH) {
return false;
}
int64_t sumIdleInterval = recordedIdleNotifyInterval_.Sum([](int64_t a, int64_t b) {return a + b;}, 0);
int64_t averageIdleInterval = sumIdleInterval / recordedIdleNotifyInterval_.Count();
int64_t sumRunningInterval = recordedRunningNotifyInterval_.Sum([](int64_t a, int64_t b) {return a + b;}, 0);
int64_t averageRunningInterval = sumRunningInterval / recordedRunningNotifyInterval_.Count();
if (averageIdleInterval > MIN_TRIGGER_GC_IDLE_INTERVAL &&
averageRunningInterval <= MAX_TRIGGER_GC_RUNNING_INTERVAL) {
return true;
}
return false;
}
void ArkIdleMonitor::NotifyLooperIdleEnd(int64_t timestamp)
{
idleEndTimestamp_ = timestamp;
SetIdleState(false);
int64_t duration = timestamp - GetNotifyTimestamp();
recordedIdleNotifyInterval_.Push(duration);
AddIdleDuration(duration);
if (mainVM_ != nullptr) {
JSNApi::NotifyLooperIdleEnd(mainVM_, timestamp);
}
}
bool ArkIdleMonitor::CheckLowNotifyState() const
{
uint32_t checkCounts = IsInBackground() ? IDLE_INBACKGROUND_CHECK_LENGTH : IDLE_CHECK_LENGTH;
HILOG_DEBUG("ArkIdleMonitor: low Notify checkCounts '%{public}d', result '%{public}d' ",
checkCounts, static_cast<int>(numberOfLowIdleNotifyCycles_));
return numberOfLowIdleNotifyCycles_ >= checkCounts;
}
bool ArkIdleMonitor::CheckLowRunningDurationState() const
{
uint32_t checkCounts = IsInBackground() ? IDLE_INBACKGROUND_CHECK_LENGTH : IDLE_CHECK_LENGTH;
HILOG_DEBUG("ArkIdleMonitor: low Duration checkCounts '%{public}d', result '%{public}d' ",
checkCounts, static_cast<int>(numberOfHighIdleTimeRatio_));
return numberOfHighIdleTimeRatio_ >= checkCounts;
}
void ArkIdleMonitor::IntervalMonitor()
{
if (!timerMutex_.try_lock()) {
HILOG_INFO("ArkIdleMonitor: IntervalMonitor stop by timerMutex_");
return;
}
auto nowTimestamp = std::chrono::time_point_cast<std::chrono::milliseconds>(
std::chrono::high_resolution_clock::now()).time_since_epoch().count();
int64_t idleDuration = GetTotalIdleDuration() - lastTotalIdleDuration_;
if (IsIdleState()) {
idleDuration += (nowTimestamp - GetNotifyTimestamp());
}
lastTotalIdleDuration_ = GetTotalIdleDuration();
if (GetIdleNotifyCount() <= LOW_IDLE_NOTIFY_THRESHOLD) {
numberOfLowIdleNotifyCycles_++;
} else {
numberOfLowIdleNotifyCycles_ = 0U;
}
ResetIdleNotifyCount();
int64_t recordTotalDuration = nowTimestamp - startRecordTimestamp_;
if (recordTotalDuration <= 0) {
numberOfHighIdleTimeRatio_ = 0U;
HILOG_ERROR("ArkIdleMonitor: recordTotalDuration <= 0");
} else {
double idleTimeRatio = static_cast<double>(idleDuration) / recordTotalDuration;
HILOG_DEBUG("ArkIdleMonitor: idleTimeRatio '%{public}.2f'", idleTimeRatio);
idleTimeRatio >= IDLE_RATIO ? numberOfHighIdleTimeRatio_++ : (numberOfHighIdleTimeRatio_ = 0U);
}
startRecordTimestamp_ = nowTimestamp;
CheckWorkerEnvQueue();
uint32_t checkCounts = IsInBackground() ? IDLE_INBACKGROUND_CHECK_LENGTH : IDLE_CHECK_LENGTH;
int64_t intervalDuration = nowTimestamp - intervalTimestamp_;
if (numberOfLowIdleNotifyCycles_ >= checkCounts &&
numberOfHighIdleTimeRatio_ >= checkCounts &&
intervalDuration < static_cast<int64_t>(gDelayOverTime)) {
triggerTaskStartTimestamp_ = nowTimestamp;
if (IsInBackground()) {
if (enableIdleProcessGCBackground_) {
TryTriggerCompressGCOfProcess();
} else {
HILOG_INFO("ArkIdleMonitor: not enable background gc.");
}
} else {
NotifyMainThreadTryCompressGC();
}
PostMonitorTask(SLEEP_MONITORING_INTERVAL);
ClearIdleStats();
} else {
PostMonitorTask(gIdleMonitoringInterval);
}
intervalTimestamp_ = nowTimestamp;
timerMutex_.unlock();
}
void ArkIdleMonitor::PostMonitorTask(uint64_t delayMs)
{
#if defined(ENABLE_FFRT)
auto task = [](void* idleMonitorPtr) {
if (idleMonitorPtr != nullptr) {
ArkIdleMonitor* arkIdleMonitor = reinterpret_cast<ArkIdleMonitor *>(idleMonitorPtr);
arkIdleMonitor->IntervalMonitor();
}
};
if (waitForStopTimerHandler_ != -1) {
int ret = ffrt_timer_stop(ffrt_qos_user_initiated, waitForStopTimerHandler_);
if (ret != 0) {
HILOG_ERROR("ArkIdleMonitor: ffrt_timer_stop error handler: timerHandler='%{public}d', ret='%{public}d'",
waitForStopTimerHandler_, ret);
}
}
waitForStopTimerHandler_ = currentTimerHandler_;
currentTimerHandler_ = ffrt_timer_start(ffrt_qos_user_initiated, delayMs, this, task, false);
#endif
}
void ArkIdleMonitor::ClearIdleStats()
{
ResetIdleNotifyCount();
numberOfLowIdleNotifyCycles_ = 0U;
numberOfHighIdleTimeRatio_ = 0U;
std::lock_guard<std::mutex> lock(envVectorMutex_);
for (auto it : workerEnvVector_) {
reinterpret_cast<ArkNativeEngine*>(it)->GetWorkerThreadState()->ResetCheckCount();
}
}
void ArkIdleMonitor::NotifyMainThreadTryCompressGC()
{
#if defined(ENABLE_EVENT_HANDLER)
double cpuUsage = GetCpuUsage();
if (cpuUsage >= IDLE_CPU_USAGE) {
HILOG_INFO("ArkIdleMonitor: Sending a quiet notification is canceled due to high CPU usage: %{public}.2f",
cpuUsage);
return;
}
if (mainVM_ == nullptr) {
return;
}
auto task = [this]() {
JSNApi::TriggerIdleGC(mainVM_, TRIGGER_IDLE_GC_TYPE::FULL_GC);
if (CheckWorkerEnvQueueAllInIdle(IDLE_WORKER_TRIGGER_COUNT)) {
JSNApi::TriggerIdleGC(mainVM_, TRIGGER_IDLE_GC_TYPE::SHARED_FULL_GC);
}
};
mainThreadHandler_->PostTask(task, "ARKTS_IDLE_COMPRESS", 0, OHOS::AppExecFwk::EventQueue::Priority::IMMEDIATE);
#endif
}
void ArkIdleMonitor::SetStartTimerCallback()
{
JSNApi::SetStartIdleMonitorCallback([this]() {
if (!started_) {
this->IntervalMonitor();
started_ = true;
}
});
}
void ArkIdleMonitor::NotifyNeedFreeze(bool needFreeze)
{
#if defined(ENABLE_EVENT_HANDLER)
if (!gEnableDeferFreeze || !IsSwitchToBackgroundTask()) {
return;
}
if (!needFreeze) {
if (!deferfreeze_.load(std::memory_order_relaxed)) {
deferfreeze_.store(true, std::memory_order_relaxed);
ReportDataToRSS(false);
} else {
HILOG_DEBUG("ArkIdleMonitor: NotifyFreeze Abandoned message");
}
} else {
if (deferfreeze_.load(std::memory_order_relaxed)) {
deferfreeze_.store(false, std::memory_order_relaxed);
SetSwitchToBackgroundTask(false);
if (externalClearCallback_) {
TraceScope trace("ArkIdleMonitor:ExternalClearCallback");
HILOG_DEBUG("ArkIdleMonitor: External Clear Callback.");
CallbackTimerScope timeScope("External Clear Callback");
externalClearCallback_();
}
ReportDataToRSS(true);
} else {
HILOG_DEBUG("ArkIdleMonitor: NotifyFreeze Abandoned message");
}
}
#endif
}
void ArkIdleMonitor::PostLooperTriggerIdleGCTask()
{
#if defined(ENABLE_EVENT_HANDLER)
std::shared_ptr<OHOS::AppExecFwk::EventRunner> mainThreadRunner =
OHOS::AppExecFwk::EventRunner::GetMainEventRunner();
if (mainThreadRunner.get() == nullptr) {
HILOG_FATAL("ArkNativeEngine:: the mainEventRunner is nullptr");
return;
}
std::weak_ptr<ArkIdleMonitor> weakArkIdleMonitor = ArkIdleMonitor::GetInstance();
auto callback = [weakArkIdleMonitor](OHOS::AppExecFwk::EventRunnerStage stage,
const OHOS::AppExecFwk::StageInfo* info) -> int {
auto arkIdleMonitor = weakArkIdleMonitor.lock();
if (nullptr == arkIdleMonitor) {
HILOG_ERROR("ArkIdleMonitor has been destructed.");
return 0;
}
switch (stage) {
case OHOS::AppExecFwk::EventRunnerStage::STAGE_BEFORE_WAITING:
arkIdleMonitor->NotifyLooperIdleStart(info->timestamp, info->sleepTime);
break;
case OHOS::AppExecFwk::EventRunnerStage::STAGE_AFTER_WAITING:
arkIdleMonitor->NotifyLooperIdleEnd(info->timestamp);
break;
default:
HILOG_ERROR("this branch is unreachable");
}
return 0;
};
uint32_t stage = (static_cast<uint32_t>(OHOS::AppExecFwk::EventRunnerStage::STAGE_BEFORE_WAITING) |
static_cast<uint32_t>(OHOS::AppExecFwk::EventRunnerStage::STAGE_AFTER_WAITING));
mainThreadRunner->GetEventQueue()->AddObserver(OHOS::AppExecFwk::Observer::ARKTS_GC, stage, callback);
#endif
}
void ArkIdleMonitor::EnableIdleGC(NativeEngine *engine)
{
#if defined(ENABLE_EVENT_HANDLER)
auto vm = const_cast<EcmaVM *>(engine->GetEcmaVm());
if (gEnableIdleGC && JSNApi::IsJSMainThreadOfEcmaVM(vm)) {
SetMainThreadEcmaVM(vm);
JSNApi::SetTriggerGCTaskCallback(vm, [engine](TriggerGCData& data) {
engine->PostTriggerGCTask(data, nullptr);
});
SetStartTimerCallback();
PostLooperTriggerIdleGCTask();
JSNApi::SetNotifyDeferFreezeCallback([this](bool isNeedFreeze) {
NotifyNeedFreeze(isNeedFreeze);
});
} else {
RegisterWorkerEnv(reinterpret_cast<napi_env>(engine));
}
if (mainThreadHandler_ == nullptr) {
mainThreadHandler_ = std::make_shared<OHOS::AppExecFwk::EventHandler>(
OHOS::AppExecFwk::EventRunner::GetMainEventRunner());
};
#elif defined(ANDROID_PLATFORM) || defined(IOS_PLATFORM)
auto vm = const_cast<EcmaVM *>(engine->GetEcmaVm());
if (JSNApi::IsJSMainThreadOfEcmaVM(vm)) {
SetMainThreadEcmaVM(vm);
mainLoop_ = engine->GetUVLoop();
} else {
RegisterWorkerEnv(reinterpret_cast<napi_env>(engine));
}
#endif
}
void ArkIdleMonitor::UnregisterEnv(NativeEngine *engine)
{
#if defined(ENABLE_EVENT_HANDLER)
if (!gEnableIdleGC || !JSNApi::IsJSMainThreadOfEcmaVM(engine->GetEcmaVm())) {
UnregisterWorkerEnv(reinterpret_cast<napi_env>(engine));
}
#elif defined(ANDROID_PLATFORM) || defined(IOS_PLATFORM)
if (!JSNApi::IsJSMainThreadOfEcmaVM(engine->GetEcmaVm())) {
UnregisterWorkerEnv(reinterpret_cast<napi_env>(engine));
}
#endif
}
uint64_t ArkIdleMonitor::GetIdleMonitoringInterval()
{
#if defined(ENABLE_EVENT_HANDLER)
uint64_t value =
static_cast<uint64_t>(OHOS::system::GetIntParameter("const.arkui.idle_monitoring_interval", 1000));
if (value < IDLE_GC_TIME_MIN) {
value = IDLE_GC_TIME_MIN;
}
if (value > IDLE_GC_TIME_MAX) {
value = IDLE_GC_TIME_MAX;
}
return value;
#else
return gIdleMonitoringInterval;
#endif
}
void ArkIdleMonitor::NotifyChangeBackgroundState(bool inBackground)
{
if (enableIdleProcessGCBackground_) {
enableIdleProcessGCBackground_ = false;
}
ClearIdleStats();
#if defined(ENABLE_FFRT)
if (started_ && inBackground) {
HILOG_DEBUG("ArkIdleMonitor post check switch background gc task");
StopIdleMonitorTimerTask();
CheckWorkerEnvQueue();
PostSwitchBackgroundGCTask();
}
#elif defined(ANDROID_PLATFORM) || defined(IOS_PLATFORM)
inBackground_.store(inBackground, std::memory_order_release);
if (inBackground) {
HILOG_DEBUG("ArkIdleMonitor post check switch background gc task");
PostCrossPlatformSwitchBackgroundGCTask();
}
return;
#endif
inBackground_.store(inBackground, std::memory_order_release);
if (!started_ && inBackground) {
HILOG_DEBUG("ArkIdleMonitor change to background but not started idle check");
}
}
double ArkIdleMonitor::GetCpuUsage() const
{
#ifdef ENABLE_UCOLLECTION
auto collector = OHOS::HiviewDFX::UCollectClient::CpuCollector::Create();
auto collectResult = collector->GetSysCpuUsage();
if (collectResult.retCode == OHOS::HiviewDFX::UCollect::UcError::SUCCESS) {
HILOG_DEBUG("ArkIdleMonitor cpu usage: %{public}.2f", collectResult.data);
return collectResult.data;
}
HILOG_ERROR("ArkIdleMonitor get cpu usage failed, error code:%{public}d", collectResult.retCode);
#endif
return 0.0f;
}
bool ArkIdleMonitor::CheckIntervalIdle(int64_t timestamp, int64_t idleDuration)
{
if (!IsIdleState()) {
return false;
}
int64_t nowTimestamp = std::chrono::time_point_cast<std::chrono::milliseconds>(
std::chrono::high_resolution_clock::now()).time_since_epoch().count();
int64_t sumDuration = nowTimestamp - timestamp;
int64_t sumIdleDuration = (GetTotalIdleDuration() - idleDuration) + (nowTimestamp - GetNotifyTimestamp());
[[maybe_unused]] double idlePercentage = static_cast<double>(sumIdleDuration) / static_cast<double>(sumDuration);
#ifdef ENABLE_HITRACE
StartTrace(HITRACE_TAG_ACE, "CheckIntervalIdle::sumDuration:" + std::to_string(sumDuration)
+ "sumIdleDuration:" + std::to_string(sumIdleDuration)
+ "idlePercentage" + std::to_string(idlePercentage));
#endif
#if defined(ENABLE_EVENT_HANDLER)
if (idlePercentage > SHORT_IDLE_RATIO && mainVM_!= nullptr) {
auto task = [this]() {
triggeredGC_ = JSNApi::NotifyLooperIdleStart(mainVM_, 0, 0);
if (!triggeredGC_ && !IsInBackground()) {
TryTriggerWorkerLocalGC();
}
needCheckIntervalIdle_ = false;
handlerWaitToStopCount_++;
if (!triggeredGC_) {
recordedIdleNotifyInterval_.Reset();
}
};
mainThreadHandler_->PostTask(task, "ARKTS_IDLE_NOTIFY", 0, OHOS::AppExecFwk::EventQueue::Priority::IMMEDIATE);
}
#endif
#ifdef ENABLE_HITRACE
FinishTrace(HITRACE_TAG_ACE);
#endif
return true;
}
void ArkIdleMonitor::PostIdleCheckTask()
{
#ifdef ENABLE_HITRACE
StartTrace(HITRACE_TAG_ACE, "NotifyLooperIdleStart::PostIdleCheckTask");
#endif
#if defined(ENABLE_FFRT)
auto nowTimestamp = std::chrono::time_point_cast<std::chrono::milliseconds>(
std::chrono::high_resolution_clock::now()).time_since_epoch().count();
std::tuple<ArkIdleMonitor*, int64_t, int64_t> myTuple = std::make_tuple(this, nowTimestamp, GetTotalIdleDuration());
std::tuple<ArkIdleMonitor*, int64_t, int64_t> *data = new std::tuple<ArkIdleMonitor*, int64_t, int64_t>(myTuple);
auto task = [](void* data) {
std::tuple<ArkIdleMonitor*, int64_t, int64_t>* tuple =
reinterpret_cast<std::tuple<ArkIdleMonitor*, int64_t, int64_t>*>(data);
if (tuple == nullptr || std::get<0>(*tuple) == nullptr) {
return;
}
std::get<0>(*tuple)->CheckIntervalIdle(std::get<1>(*tuple), std::get<2>(*tuple));
delete tuple;
};
int timerHandler = ffrt_timer_start(ffrt_qos_user_initiated, SHORT_IDLE_DELAY_INTERVAL,
reinterpret_cast<void*>(data), task, false);
timerHandlerQueue_.push(timerHandler);
#endif
#ifdef ENABLE_HITRACE
FinishTrace(HITRACE_TAG_ACE);
#endif
}
bool ArkIdleMonitor::SwitchBackgroundCheckGCTask(int64_t timestamp, int64_t idleDuration)
{
int64_t nowTimestamp = std::chrono::time_point_cast<std::chrono::milliseconds>(
std::chrono::high_resolution_clock::now()).time_since_epoch().count();
int64_t sumDuration = nowTimestamp - timestamp;
int64_t sumIdleDuration = (GetTotalIdleDuration() - idleDuration) + (nowTimestamp - GetNotifyTimestamp());
double idlePercentage = static_cast<double>(sumIdleDuration) / static_cast<double>(sumDuration);
double cpuUsage = GetCpuUsage();
if (idlePercentage > BACKGROUND_IDLE_RATIO && sumDuration < static_cast<int64_t>(BACKGROUND_GC_DELAY_OVER_TIME)) {
triggerTaskStartTimestamp_ = nowTimestamp;
CheckWorkerEnvQueue();
SetSwitchToBackgroundTask(true);
TryTriggerCompressGCOfProcess();
return true;
} else {
HILOG_INFO("ArkIdleMonitor skip BGGCTask, idlePer:%{public}.2f;cpuUsage:%{public}.2f"
";sumDuration:%{public}" PRId64,
idlePercentage, cpuUsage, sumDuration);
return false;
}
}
void ArkIdleMonitor::PostSwitchBackgroundGCTask()
{
#if defined(ENABLE_FFRT)
if (IsDuringBackgroundTask()) {
return;
}
if (switchBackgroundTimerHandler_ != -1) {
ffrt_timer_stop(ffrt_qos_user_initiated, switchBackgroundTimerHandler_);
}
auto nowTimestamp = std::chrono::time_point_cast<std::chrono::milliseconds>(
std::chrono::high_resolution_clock::now()).time_since_epoch().count();
std::tuple<ArkIdleMonitor*, int64_t, int64_t> myTuple = std::make_tuple(this, nowTimestamp, GetTotalIdleDuration());
auto* taskData = new std::tuple<ArkIdleMonitor*, int64_t, int64_t>(myTuple);
auto task = [](void* data) {
auto* tuple = reinterpret_cast<std::tuple<ArkIdleMonitor*, int64_t, int64_t>*>(data);
if (tuple == nullptr || std::get<0>(*tuple) == nullptr) {
delete tuple;
return;
}
auto* monitor = std::get<0>(*tuple);
for (uint32_t retry = 0; retry < BACKGROUND_GC_CHECK_COUNT; ++retry) {
if (!monitor->IsInBackground()) {
HILOG_INFO("ArkIdleMonitor exit BGGCTask loop, app switched to foreground");
break;
}
bool triggered = monitor->SwitchBackgroundCheckGCTask(std::get<1>(*tuple), std::get<2>(*tuple));
if (triggered) {
break;
}
HILOG_INFO("ArkIdleMonitor retry BGGCTask, count:%{public}u", retry + 1);
if (retry < BACKGROUND_GC_CHECK_COUNT - 1) {
std::get<1>(*tuple) = std::chrono::time_point_cast<std::chrono::milliseconds>(
std::chrono::high_resolution_clock::now()).time_since_epoch().count();
std::get<2>(*tuple) = monitor->GetTotalIdleDuration();
ffrt_usleep(BACKGROUND_GC_CHECK_INTERVAL * 1000);
}
}
JSNApi::ExecuteTaskpoolShrinkCallback(monitor->inBackground_);
monitor->StopIdleMonitorTimerTaskAndPostSleepTask();
monitor->ClearIdleStats();
monitor->SetDuringBackgroundTask(false);
delete tuple;
};
switchBackgroundTimerHandler_ = ffrt_timer_start(ffrt_qos_user_initiated, BACKGROUND_GC_CHECK_INTERVAL,
reinterpret_cast<void*>(taskData), task, false);
SetDuringBackgroundTask(true);
#endif
}
void ArkIdleMonitor::CheckWorkerEnvQueue()
{
std::lock_guard<std::mutex> lock(envVectorMutex_);
for (auto it = workerEnvVector_.begin(); it != workerEnvVector_.end();) {
auto arkNativeEngine = reinterpret_cast<ArkNativeEngine*>(*it);
arkNativeEngine->GetWorkerThreadState()->CheckIdleState();
HILOG_DEBUG("ArkIdleMonitor::CheckWorkerEnvQueue,tid=%{public}d, workerCount=%{public}d",
arkNativeEngine->GetSysTid(), arkNativeEngine->GetWorkerThreadState()->GetCheckCount());
++it;
}
}
bool ArkIdleMonitor::CheckWorkerEnvQueueAllInIdle(uint32_t idleCount)
{
std::lock_guard<std::mutex> lock(envVectorMutex_);
for (auto it = workerEnvVector_.begin(); it != workerEnvVector_.end();) {
auto arkNativeEngine = reinterpret_cast<ArkNativeEngine*>(*it);
if (arkNativeEngine->GetWorkerThreadState()->CheckIdleState() < idleCount) {
return false;
}
++it;
}
return true;
}
void ArkIdleMonitor::StopIdleMonitorTimerTask()
{
#if defined(ENABLE_FFRT)
std::lock_guard<std::mutex> lock(timerMutex_);
HILOG_INFO("Get timerMutex_");
if (currentTimerHandler_ != -1) {
ffrt_timer_stop(ffrt_qos_user_initiated, currentTimerHandler_);
currentTimerHandler_ = -1;
}
if (waitForStopTimerHandler_ != -1) {
ffrt_timer_stop(ffrt_qos_user_initiated, waitForStopTimerHandler_);
waitForStopTimerHandler_ = -1;
}
#endif
}
void ArkIdleMonitor::StopIdleMonitorTimerTaskAndPostSleepTask()
{
#if defined(ENABLE_FFRT)
std::lock_guard<std::mutex> lock(timerMutex_);
if (currentTimerHandler_ != -1) {
ffrt_timer_stop(ffrt_qos_user_initiated, currentTimerHandler_);
currentTimerHandler_ = -1;
}
if (waitForStopTimerHandler_ != -1) {
ffrt_timer_stop(ffrt_qos_user_initiated, waitForStopTimerHandler_);
waitForStopTimerHandler_ = -1;
}
PostMonitorTask(SLEEP_MONITORING_INTERVAL);
#endif
}
void ArkIdleMonitor::ReportDataToRSS(bool needFreeze)
{
#if defined(LINUX_PLATFORM) || defined(OHOS_PLATFORM)
auto pid = getpid();
auto bundleName = JSNApi::GetBundleName(mainVM_);
std::unordered_map<std::string, std::string> eventParams {
{ "pid", std::to_string(pid) },
{ "bundleName", bundleName }
};
if (!reportDataFunc_) {
reportDataFunc_ = LoadReportDataFunc();
}
if (reportDataFunc_ != nullptr) {
reportDataFunc_(RES_TYPE_GC_EVENT, needFreeze, eventParams);
HILOG_INFO("ArkIdleMonitor: ReportDataToRSS %{public}d, pid: %{public}d, bundleName: %{public}s",
needFreeze, pid, bundleName.c_str());
} else {
HILOG_WARN("ArkIdleMonitor: ReportDataToRSS func is nullptr.");
}
#else
HILOG_WARN("ArkIdleMonitor: Only linux supports LoadReportDataFunc");
#endif
}
ReportDataFunc ArkIdleMonitor::LoadReportDataFunc()
{
#if defined(LINUX_PLATFORM) || defined(OHOS_PLATFORM)
if (dynamicLoadHandle_ != nullptr) {
dlclose(dynamicLoadHandle_);
}
dynamicLoadHandle_ = dlopen(RES_SCHED_CLIENT_SO.c_str(), RTLD_NOW);
auto func = reinterpret_cast<ReportDataFunc>(dlsym(dynamicLoadHandle_, "ReportData"));
if (func == nullptr) {
dlclose(dynamicLoadHandle_);
HILOG_ERROR("ArkIdleMonitor: LoadReportDataFunc failed!");
return nullptr;
}
HILOG_INFO("ArkIdleMonitor: LoadReportDataFunc success.");
return func;
#else
HILOG_DEBUG("ArkIdleMonitor: Only linux supports LoadReportDataFunc");
return nullptr;
#endif
}
ArkIdleMonitor::~ArkIdleMonitor()
{
#if defined(LINUX_PLATFORM) || defined(OHOS_PLATFORM)
if (dynamicLoadHandle_ != nullptr) {
dlclose(dynamicLoadHandle_);
dynamicLoadHandle_ = nullptr;
}
#endif
#if defined(ENABLE_FFRT)
StopIdleMonitorTimerTask();
while (timerHandlerQueue_.size() > 0) {
ffrt_timer_stop(ffrt_qos_user_initiated, timerHandlerQueue_.front());
timerHandlerQueue_.pop();
}
#endif
#if defined(ANDROID_PLATFORM) || defined(IOS_PLATFORM)
if (CrossPlatformBgGCThreadStarted_) {
uv_thread_join(&CrossPlatformBgGCThread_);
CrossPlatformBgGCThreadStarted_ = false;
}
#endif
}
std::shared_ptr<ArkIdleMonitor> ArkIdleMonitor::GetInstance()
{
static std::shared_ptr<ArkIdleMonitor> instance = std::make_shared<ArkIdleMonitor>();
return instance;
}
bool ArkIdleMonitor::CheckIfInBackgroundInCompressGC()
{
if (!IsInBackground() && IsSwitchToBackgroundTask()) {
deferfreeze_.store(false, std::memory_order_relaxed);
SetSwitchToBackgroundTask(false);
return false;
}
return true;
}
ArkIdleMonitor::WorkerGCResult ArkIdleMonitor::EvaluateWorkerGC(napi_env workerEnv, bool isForeground)
{
auto arkNativeEngine = reinterpret_cast<ArkNativeEngine*>(workerEnv);
#if (defined(LINUX_PLATFORM) || defined(OHOS_PLATFORM))
if (kill(arkNativeEngine->GetSysTid(), 0) != 0) {
HILOG_ERROR("ArkIdleMonitor: tid: %{public}u is dead!", arkNativeEngine->GetSysTid());
return WorkerGCResult::ERASED;
}
#endif
if (IsSentTask(workerEnv)) {
HILOG_DEBUG("ArkIdleMonitor: tid: %{public}u is not respond!", arkNativeEngine->GetSysTid());
return WorkerGCResult::SKIPPED;
}
uint32_t checkCount = arkNativeEngine->GetWorkerThreadState()->GetCheckCount();
if (checkCount == 0) {
HILOG_DEBUG("ArkIdleMonitor: tid: %{public}u NOT idle", arkNativeEngine->GetSysTid());
return WorkerGCResult::NOT_IDLE;
}
if (isForeground && checkCount % IDLE_WORKER_GC_CHECK_COUNT != 0) {
HILOG_DEBUG("ArkIdleMonitor: tid: %{public}u checkCount:%{public}u skip",
arkNativeEngine->GetSysTid(), checkCount);
return WorkerGCResult::SKIPPED;
}
if (arkNativeEngine->IsNativeThread()) {
HILOG_DEBUG("ArkIdleMonitor: tid: %{public}u is native thread.", arkNativeEngine->GetSysTid());
return WorkerGCResult::SKIPPED;
}
size_t expectedSize = JSNApi::GetEcmaVMExpectedMemoryReclamationSize(arkNativeEngine->GetEcmaVm());
size_t minReclaimSize = isForeground ? IDLE_MIN_EXPECT_RECLAIM_SIZE_FOREGROUND : IDLE_MIN_EXPECT_RECLAIM_SIZE;
if (expectedSize <= minReclaimSize) {
HILOG_DEBUG("ArkIdleMonitor: tid: %{public}u expectedSize:%{public}zu too small",
arkNativeEngine->GetSysTid(), expectedSize);
return WorkerGCResult::SKIPPED;
}
return WorkerGCResult::READY;
}
void ArkIdleMonitor::PostWorkerFullGC(napi_env workerEnv, bool notifyFinished)
{
auto arkNativeEngine = reinterpret_cast<ArkNativeEngine*>(workerEnv);
std::pair<void*, uint16_t> data(reinterpret_cast<void*>(const_cast<EcmaVM*>(arkNativeEngine->GetEcmaVm())),
static_cast<uint16_t>(TRIGGER_IDLE_GC_TYPE::FULL_GC));
auto callbackTask = [this, workerEnv, notifyFinished]() {
HILOG_DEBUG("ArkIdleMonitor: try trigger thread full gc end");
UnRegisterSentTaskWorkerEnv(workerEnv);
if (notifyFinished) {
GCTaskFinishedCallback();
}
};
HILOG_DEBUG("ArkIdleMonitor: try trigger thread full gc start,tid: %{public}u", arkNativeEngine->GetSysTid());
RegisterSentTaskWorkerEnv(workerEnv);
arkNativeEngine->PostTriggerGCTask(data, callbackTask);
}
void ArkIdleMonitor::TryTriggerWorkerLocalGC()
{
HILOG_DEBUG("ArkIdleMonitor: try trigger worker local gc");
int64_t nowTimestamp = std::chrono::time_point_cast<std::chrono::milliseconds>(
std::chrono::high_resolution_clock::now()).time_since_epoch().count();
if (nowTimestamp - lastWorkerLocalGCTimestamp_ < IDLE_WORKER_GC_COOLDOWN_MS) {
HILOG_DEBUG("ArkIdleMonitor: worker local gc in cooldown, last triggered %{public}s ms ago",
std::to_string(nowTimestamp - lastWorkerLocalGCTimestamp_).c_str());
return;
}
size_t triggeredCount = 0;
size_t skippedCount = 0;
std::lock_guard<std::mutex> vectorLock(envVectorMutex_);
for (auto it = workerEnvVector_.begin(); it != workerEnvVector_.end();) {
WorkerGCResult result = EvaluateWorkerGC(*it, true );
if (result == WorkerGCResult::READY) {
PostWorkerFullGC(*it, false );
triggeredCount++;
} else {
skippedCount++;
}
if (result == WorkerGCResult::ERASED) {
it = workerEnvVector_.erase(it);
} else {
++it;
}
}
if (triggeredCount > 0) {
lastWorkerLocalGCTimestamp_ = nowTimestamp;
HILOG_INFO("ArkIdleMonitor: worker local gc triggered:%{public}zu, skipped:%{public}zu", triggeredCount,
skippedCount);
} else {
HILOG_DEBUG("ArkIdleMonitor: worker local gc skipped all:%{public}zu", skippedCount);
}
}
void ArkIdleMonitor::TryTriggerCompressGCOfProcess()
{
NotifyNeedFreeze(false);
#if defined(ANDROID_PLATFORM) || defined(IOS_PLATFORM)
TryTriggerCompressGCOfProcessCrossPlatformGC(TRIGGER_IDLE_GC_TYPE::FULL_GC);
#elif defined(ENABLE_EVENT_HANDLER)
auto mainThreadLocalTask = [this]() {
HILOG_DEBUG("ArkIdleMonitor: try trigger local full gc start");
std::unique_lock<std::mutex> lock(waitGCFinishjedMutex_);
JSNApi::TriggerIdleGC(mainVM_, TRIGGER_IDLE_GC_TYPE::FULL_GC);
HILOG_DEBUG("ArkIdleMonitor: try trigger local full gc end");
gcFinishCV_.notify_one();
};
{
TraceScope trace("TriggerMainThreadLocalGC");
std::unique_lock<std::mutex> lock(waitGCFinishjedMutex_);
mainThreadHandler_->PostTask(mainThreadLocalTask, "ARKTS_IDLE_COMPRESS", 0,
OHOS::AppExecFwk::EventQueue::Priority::IMMEDIATE);
gcFinishCV_.wait(lock);
}
#else
HILOG_WARN("ArkIdleMonitor: not enable ENABLE_EVENT_HANDLER");
return;
#endif
bool isAllInIdle = true;
size_t index = 0;
while (true) {
#if !defined(ANDROID_PLATFORM) && !defined(IOS_PLATFORM)
if (!gEnableDeferFreeze) {
isAllInIdle = CheckWorkerEnvQueueAllInIdle(IDLE_WORKER_TRIGGER_COUNT);
break;
}
#endif
{
if (!CheckIfInBackgroundInCompressGC()) {
return;
}
std::unique_lock<std::mutex> vectorLock(envVectorMutex_);
if (index >= workerEnvVector_.size()) {
break;
}
napi_env it = workerEnvVector_.at(index);
WorkerGCResult result = EvaluateWorkerGC(it, false );
if (result == WorkerGCResult::ERASED) {
workerEnvVector_.erase(workerEnvVector_.begin() + index);
continue;
}
if (result == WorkerGCResult::NOT_IDLE) {
isAllInIdle = false;
}
if (result != WorkerGCResult::READY) {
index++;
continue;
}
TraceScope trace("TriggerWorkerThreadLocalGC");
std::unique_lock<std::mutex> lock(waitGCFinishjedMutex_);
PostWorkerFullGC(it, true );
vectorLock.unlock();
gcFinishCV_.wait_for(lock, std::chrono::milliseconds(WAIT_GC_FINISH_INTERVAL));
auto nowTimestamp = std::chrono::time_point_cast<std::chrono::milliseconds>(
std::chrono::high_resolution_clock::now()).time_since_epoch().count();
int64_t totalSpentTime = nowTimestamp - triggerTaskStartTimestamp_;
if (totalSpentTime > WAIT_LOCAL_GC_LOWER_INTERVAL && totalSpentTime <= WAIT_LOCAL_GC_UPPER_INTERVAL) {
HILOG_INFO("ArkIdleMonitor: local gc overtime, try to trigger shared gc directly.");
break;
} else if (totalSpentTime > WAIT_LOCAL_GC_UPPER_INTERVAL) {
NotifyNeedFreeze(true);
HILOG_INFO("ArkIdleMonitor: cancel shared gc because over time.");
return;
}
index++;
}
}
if (!isAllInIdle) {
NotifyNeedFreeze(true);
HILOG_INFO("ArkIdleMonitor: cancel shared gc because not all in idle");
return;
}
#if !defined(ANDROID_PLATFORM) && !defined(IOS_PLATFORM)
double cpuUsage = GetCpuUsage();
if (cpuUsage > IDLE_BACKGROUND_CPU_USAGE) {
NotifyNeedFreeze(true);
SetSwitchToBackgroundTask(false);
HILOG_INFO("ArkIdleMonitor: cancel process gc because high cpu usage:%{public}.2f", cpuUsage);
return;
}
#endif
#if defined(ENABLE_EVENT_HANDLER)
auto mainThreadSharedTask = [this]() {
HILOG_DEBUG("ArkIdleMonitor: try trigger shared cc");
std::unique_lock<std::mutex> lock(waitGCFinishjedMutex_);
JSNApi::TriggerIdleGC(mainVM_, TRIGGER_IDLE_GC_TYPE::SHARED_CC);
gcFinishCV_.notify_one();
};
if (CheckIfInBackgroundInCompressGC()) {
TraceScope trace("TriggerMainThreadSharedGC");
std::unique_lock<std::mutex> lock(waitGCFinishjedMutex_);
mainThreadHandler_->PostTask(mainThreadSharedTask, "ARKTS_IDLE_SHARED_COMPRESS", 0,
OHOS::AppExecFwk::EventQueue::Priority::IMMEDIATE);
} else {
NotifyNeedFreeze(true);
HILOG_WARN("ArkIdleMonitor: app is not in idle or in background.");
}
#elif defined(ANDROID_PLATFORM) || defined(IOS_PLATFORM)
TryTriggerCompressGCOfProcessCrossPlatformGC(TRIGGER_IDLE_GC_TYPE::SHARED_CC);
#endif
}
#if defined(ANDROID_PLATFORM) || defined(IOS_PLATFORM)
void ArkIdleMonitor::PostCrossPlatformSwitchBackgroundGCTask()
{
auto monitorPtr = ArkIdleMonitor::GetInstance();
ArkIdleMonitor* monitor = monitorPtr.get();
if (monitor == nullptr) {
HILOG_ERROR("ArkIdleMonitor: PostCrossPlatformSwitchBackgroundGCTask called but monitor is null");
return;
}
HILOG_DEBUG("ArkIdleMonitor: background GC triggered, inBackground=%{public}d, during=%{public}d",
static_cast<int>(monitor->IsInBackground()),
static_cast<int>(monitor->IsDuringBackgroundTask()));
if (!monitor->IsInBackground() || monitor->IsDuringBackgroundTask()) {
return;
}
monitor->SetDuringBackgroundTask(true);
if (monitor->CrossPlatformBgGCThreadStarted_) {
uv_thread_join(&monitor->CrossPlatformBgGCThread_);
monitor->CrossPlatformBgGCThreadStarted_ = false;
}
if (uv_thread_create(&monitor->CrossPlatformBgGCThread_, ArkIdleMonitor::OnCrossPlatformBackgroundGCThreadStart,
reinterpret_cast<void*>(monitor)) != 0) {
HILOG_ERROR("ArkIdleMonitor: failed to create cross-platform background GC thread");
monitor->SetDuringBackgroundTask(false);
return;
}
monitor->CrossPlatformBgGCThreadStarted_ = true;
}
void ArkIdleMonitor::OnCrossPlatformBackgroundGCThreadStart(void* arg)
{
auto* monitor = reinterpret_cast<ArkIdleMonitor*>(arg);
if (monitor == nullptr) {
HILOG_ERROR("ArkIdleMonitor: OnCrossPlatformBackgroundGCThreadStart called with null monitor");
return;
}
HILOG_DEBUG("ArkIdleMonitor: cross-platform background GC thread started");
int64_t nowTimestamp = std::chrono::time_point_cast<std::chrono::milliseconds>(
std::chrono::high_resolution_clock::now()).time_since_epoch().count();
monitor->triggerTaskStartTimestamp_ = nowTimestamp;
monitor->CheckWorkerEnvQueue();
monitor->SetSwitchToBackgroundTask(true);
monitor->TryTriggerCompressGCOfProcess();
monitor->SetDuringBackgroundTask(false);
HILOG_DEBUG("ArkIdleMonitor: cross-platform background GC thread finished");
}
struct MainThreadGCData {
ArkIdleMonitor* monitor;
std::mutex mtx;
std::condition_variable cv;
bool finished {false};
panda::JSNApi::TRIGGER_IDLE_GC_TYPE type;
};
void ArkIdleMonitor::OnMainThreadGCTask(uv_async_t* handle)
{
if (handle == nullptr) {
HILOG_ERROR("ArkIdleMonitor: OnMainThreadGCTask called with null handle");
return;
}
MainThreadGCData* data = reinterpret_cast<MainThreadGCData*>(handle->data);
if (data == nullptr) {
HILOG_ERROR("ArkIdleMonitor: OnMainThreadGCTask called with null data");
return;
}
EcmaVM* vm = nullptr;
if (data->monitor != nullptr) {
vm = data->monitor->GetMainThreadEcmaVM();
} else {
HILOG_ERROR("ArkIdleMonitor: OnMainThreadGCTask monitor is null");
}
if (vm != nullptr) {
JSNApi::TriggerIdleGC(vm, data->type);
} else {
HILOG_ERROR("ArkIdleMonitor: OnMainThreadGCTask skipped TriggerIdleGC because vm is null");
}
{
std::lock_guard<std::mutex> lock(data->mtx);
data->finished = true;
data->cv.notify_one();
}
HILOG_DEBUG("ArkIdleMonitor: OnMainThreadGCTask completed and notified");
uv_close(reinterpret_cast<uv_handle_t*>(handle), [](uv_handle_t* h) {
delete reinterpret_cast<uv_async_t*>(h);
});
}
void ArkIdleMonitor::TryTriggerCompressGCOfProcessCrossPlatformGC(TRIGGER_IDLE_GC_TYPE type)
{
if (mainLoop_ == nullptr) {
HILOG_ERROR("ArkIdleMonitor: skip GC, no mainLoop");
return;
}
if (!CheckIfInBackgroundInCompressGC()) {
HILOG_WARN("ArkIdleMonitor: skip GC, not in background");
return;
}
MainThreadGCData* data = new MainThreadGCData();
data->monitor = this;
data->type = type;
uv_async_t* async = new uv_async_t();
async->data = data;
if (uv_async_init(mainLoop_, async, ArkIdleMonitor::OnMainThreadGCTask) != 0) {
HILOG_ERROR("ArkIdleMonitor: uv_async_init failed");
delete data;
delete async;
return;
}
const char* gcTypeStr = (type == TRIGGER_IDLE_GC_TYPE::FULL_GC) ? "local full gc" : "shared full gc";
HILOG_DEBUG("ArkIdleMonitor: try trigger %s start", gcTypeStr);
uv_async_send(async);
{
std::unique_lock<std::mutex> lock(data->mtx);
data->cv.wait(lock, [data]() { return data->finished; });
HILOG_DEBUG("ArkIdleMonitor: try trigger %s end", gcTypeStr);
}
delete data;
}
#endif
}