#include "chrome/browser/ash/arc/instance_throttle/arc_instance_throttle.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/no_destructor.h"
#include "base/time/time.h"
#include "chrome/browser/ash/arc/boot_phase_monitor/arc_boot_phase_monitor_bridge.h"
#include "chrome/browser/ash/arc/instance_throttle/arc_active_audio_throttle_observer.h"
#include "chrome/browser/ash/arc/instance_throttle/arc_active_window_throttle_observer.h"
#include "chrome/browser/ash/arc/instance_throttle/arc_app_launch_throttle_observer.h"
#include "chrome/browser/ash/arc/instance_throttle/arc_boot_phase_throttle_observer.h"
#include "chrome/browser/ash/arc/instance_throttle/arc_pip_window_throttle_observer.h"
#include "chrome/browser/ash/arc/instance_throttle/arc_power_throttle_observer.h"
#include "chrome/browser/ash/arc/instance_throttle/arc_provisioning_throttle_observer.h"
#include "chrome/browser/ash/arc/instance_throttle/arc_switch_throttle_observer.h"
#include "chrome/browser/ash/arc/instance_throttle/arcvm_kiosk_mode_throttle_observer.h"
#include "chromeos/ash/components/dbus/concierge/concierge_client.h"
#include "chromeos/ash/components/dbus/session_manager/session_manager_client.h"
#include "chromeos/ash/experiences/arc/arc_browser_context_keyed_service_factory_base.h"
#include "chromeos/ash/experiences/arc/arc_features.h"
#include "chromeos/ash/experiences/arc/mojom/power.mojom.h"
#include "chromeos/ash/experiences/arc/session/arc_bridge_service.h"
namespace arc {
namespace {
enum class UnthrottlingReason {
kFasterBoot = 0,
kPreAnr = 1,
kOther = 2,
};
enum class CpuRestrictionVmResult {
kSuccess = 0,
kOther = 1,
kNoConciergeService = 2,
kNoConciergeClient = 3,
kConciergeDidNotRespond = 4,
kMaxValue = kConciergeDidNotRespond,
};
void RecordCpuRestrictionVMResult(CpuRestrictionVmResult result) {
base::UmaHistogramEnumeration("Arc.CpuRestrictionVmResult", result);
}
UnthrottlingReason GetUnthrottlingReason(
const std::vector<std::unique_ptr<ash::ThrottleObserver>>& observers) {
std::vector<ash::ThrottleObserver*> active_observers;
for (const auto& observer : observers) {
if (observer->active())
active_observers.push_back(observer.get());
}
UnthrottlingReason result = UnthrottlingReason::kOther;
DCHECK(!active_observers.empty()) << "All observers are inactive";
for (const auto* active_observer : active_observers) {
if (active_observer->name() == kArcBootPhaseThrottleObserverName) {
result = UnthrottlingReason::kFasterBoot;
} else if (active_observer->name() == kArcPowerThrottleObserverName) {
result = UnthrottlingReason::kPreAnr;
} else {
return UnthrottlingReason::kOther;
}
}
return result;
}
void OnSetArcVmCpuRestriction(
std::optional<vm_tools::concierge::SetVmCpuRestrictionResponse> response) {
if (!response) {
LOG(ERROR) << "Failed to call SetVmCpuRestriction";
RecordCpuRestrictionVMResult(
CpuRestrictionVmResult::kConciergeDidNotRespond);
return;
}
if (response->success()) {
RecordCpuRestrictionVMResult(CpuRestrictionVmResult::kSuccess);
} else {
LOG(ERROR) << "SetVmCpuRestriction for ARCVM failed";
RecordCpuRestrictionVMResult(CpuRestrictionVmResult::kOther);
}
}
void SetArcVmCpuRestrictionImpl(
vm_tools::concierge::SetVmCpuRestrictionRequest request,
bool service_is_available) {
if (!service_is_available) {
LOG(ERROR)
<< "vm_concierge is not available. ArcInstanceThrottle won't work.";
RecordCpuRestrictionVMResult(CpuRestrictionVmResult::kNoConciergeService);
return;
}
auto* const client = ash::ConciergeClient::Get();
if (!client) {
LOG(ERROR) << "ConciergeClient is not available";
RecordCpuRestrictionVMResult(CpuRestrictionVmResult::kNoConciergeClient);
return;
}
client->SetVmCpuRestriction(request,
base::BindOnce(&OnSetArcVmCpuRestriction));
}
void SetArcVmCpuRestriction(CpuRestrictionState cpu_restriction_state,
bool use_quota) {
auto* const client = ash::ConciergeClient::Get();
if (!client) {
LOG(ERROR) << "ConciergeClient is not available";
RecordCpuRestrictionVMResult(CpuRestrictionVmResult::kNoConciergeClient);
return;
}
vm_tools::concierge::SetVmCpuRestrictionRequest request;
request.set_cpu_cgroup(vm_tools::concierge::CPU_CGROUP_ARCVM);
switch (cpu_restriction_state) {
case CpuRestrictionState::CPU_RESTRICTION_FOREGROUND:
DCHECK(!use_quota);
request.set_cpu_restriction_state(
vm_tools::concierge::CPU_RESTRICTION_FOREGROUND);
break;
case CpuRestrictionState::CPU_RESTRICTION_BACKGROUND:
request.set_cpu_restriction_state(
use_quota ? vm_tools::concierge::
CPU_RESTRICTION_BACKGROUND_WITH_CFS_QUOTA_ENFORCED
: vm_tools::concierge::CPU_RESTRICTION_BACKGROUND);
break;
}
client->WaitForServiceToBeAvailable(
base::BindOnce(&SetArcVmCpuRestrictionImpl, std::move(request)));
}
void SetArcCpuRestrictionCallback(
login_manager::ContainerCpuRestrictionState state,
bool success) {
if (success)
return;
const char* message =
(state == login_manager::CONTAINER_CPU_RESTRICTION_BACKGROUND)
? "unprioritize"
: "prioritize";
LOG(ERROR) << "Failed to " << message << " ARC";
}
void SetArcContainerCpuRestriction(CpuRestrictionState cpu_restriction_state) {
if (!ash::SessionManagerClient::Get()) {
LOG(WARNING) << "SessionManagerClient is not available";
return;
}
login_manager::ContainerCpuRestrictionState state;
switch (cpu_restriction_state) {
case CpuRestrictionState::CPU_RESTRICTION_FOREGROUND:
state = login_manager::CONTAINER_CPU_RESTRICTION_FOREGROUND;
break;
case CpuRestrictionState::CPU_RESTRICTION_BACKGROUND:
state = login_manager::CONTAINER_CPU_RESTRICTION_BACKGROUND;
break;
}
ash::SessionManagerClient::Get()->SetArcCpuRestriction(
state, base::BindOnce(SetArcCpuRestrictionCallback, state));
}
void SetArcCpuRestriction(CpuRestrictionState cpu_restriction_state,
bool use_quota) {
if (IsArcVmEnabled()) {
SetArcVmCpuRestriction(cpu_restriction_state, use_quota);
} else {
SetArcContainerCpuRestriction(cpu_restriction_state);
}
}
class DefaultDelegateImpl : public ArcInstanceThrottle::Delegate {
public:
DefaultDelegateImpl() = default;
DefaultDelegateImpl(const DefaultDelegateImpl&) = delete;
DefaultDelegateImpl& operator=(const DefaultDelegateImpl&) = delete;
~DefaultDelegateImpl() override = default;
void SetCpuRestriction(CpuRestrictionState cpu_restriction_state,
bool use_quota) override {
SetArcCpuRestriction(cpu_restriction_state, use_quota);
}
void RecordCpuRestrictionDisabledUMA(const std::string& observer_name,
base::TimeDelta delta) override {
DVLOG(2) << "ARC throttling was disabled for "
<< delta.InMillisecondsRoundedUp()
<< " ms due to: " << observer_name;
UmaHistogramLongTimes("Arc.CpuRestrictionDisabled." + observer_name, delta);
}
};
class ArcInstanceThrottleFactory
: public internal::ArcBrowserContextKeyedServiceFactoryBase<
ArcInstanceThrottle,
ArcInstanceThrottleFactory> {
public:
static constexpr const char* kName = "ArcInstanceThrottleFactory";
static ArcInstanceThrottleFactory* GetInstance() {
static base::NoDestructor<ArcInstanceThrottleFactory> instance;
return instance.get();
}
private:
friend class base::NoDestructor<ArcInstanceThrottleFactory>;
ArcInstanceThrottleFactory() {
DependsOn(ArcBootPhaseMonitorBridgeFactory::GetInstance());
DependsOn(ArcMetricsServiceFactory::GetInstance());
DependsOn(ArcAppLaunchNotifierFactory::GetInstance());
}
~ArcInstanceThrottleFactory() override = default;
};
CpuRestrictionState ToCpuRestriction(bool should_throttle) {
return should_throttle ? CpuRestrictionState::CPU_RESTRICTION_BACKGROUND
: CpuRestrictionState::CPU_RESTRICTION_FOREGROUND;
}
}
const char ArcInstanceThrottle::kChromeArcPowerControlPageObserver[] =
"arc-power-control";
ArcInstanceThrottle* ArcInstanceThrottle::GetForBrowserContext(
content::BrowserContext* context) {
return ArcInstanceThrottleFactory::GetForBrowserContext(context);
}
ArcInstanceThrottle* ArcInstanceThrottle::GetForBrowserContextForTesting(
content::BrowserContext* context) {
return ArcInstanceThrottleFactory::GetForBrowserContextForTesting(context);
}
ArcInstanceThrottle::ArcInstanceThrottle(content::BrowserContext* context,
ArcBridgeService* bridge)
: ThrottleService(context),
delegate_(std::make_unique<DefaultDelegateImpl>()),
bridge_(bridge) {
AddObserver(std::make_unique<ArcActiveWindowThrottleObserver>());
AddObserver(std::make_unique<ArcAppLaunchThrottleObserver>());
AddObserver(std::make_unique<ArcBootPhaseThrottleObserver>());
AddObserver(std::make_unique<ArcvmKioskModeThrottleObserver>());
AddObserver(std::make_unique<ArcPipWindowThrottleObserver>());
AddObserver(std::make_unique<ArcPowerThrottleObserver>());
AddObserver(std::make_unique<ArcProvisioningThrottleObserver>());
AddObserver(std::make_unique<ArcSwitchThrottleObserver>());
AddObserver(std::make_unique<ash::ThrottleObserver>(
kChromeArcPowerControlPageObserver));
if (base::FeatureList::IsEnabled(arc::kUnthrottleOnActiveAudioV2)) {
AddObserver(std::make_unique<ArcActiveAudioThrottleObserver>());
}
StartObservers();
DCHECK(bridge_);
bridge_->power()->AddObserver(this);
ArcMetricsService::GetForBrowserContext(context)->AddBootTypeObserver(this);
}
ArcInstanceThrottle::~ArcInstanceThrottle() = default;
void ArcInstanceThrottle::Shutdown() {
ArcMetricsService::GetForBrowserContext(context())->RemoveBootTypeObserver(
this);
bridge_->power()->RemoveObserver(this);
StopObservers();
}
void ArcInstanceThrottle::OnConnectionReady() {
NotifyCpuRestriction(ToCpuRestriction(should_throttle()));
}
void ArcInstanceThrottle::OnBootTypeRetrieved(mojom::BootType boot_type) {
switch (boot_type) {
case mojom::BootType::UNKNOWN:
break;
case mojom::BootType::FIRST_BOOT:
case mojom::BootType::FIRST_BOOT_AFTER_UPDATE:
return;
case mojom::BootType::REGULAR_BOOT:
never_enforce_quota_ = true;
return;
}
NOTREACHED();
}
void ArcInstanceThrottle::ThrottleInstance(bool should_throttle) {
const CpuRestrictionState cpu_restriction_state =
ToCpuRestriction(should_throttle);
NotifyCpuRestriction(cpu_restriction_state);
bool use_quota = false;
if (!should_throttle && !never_enforce_quota_) {
switch (GetUnthrottlingReason(observers())) {
case UnthrottlingReason::kFasterBoot:
DVLOG(2) << "Unthrottling for faster boot. Quota is still applicable.";
break;
case UnthrottlingReason::kPreAnr:
DVLOG(2)
<< "Unthrottling for preventing ANRs. Quota is still applicable.";
break;
case UnthrottlingReason::kOther:
DVLOG(2) << "Unthrottling because of a user action. Quota is no longer "
"applicable.";
never_enforce_quota_ = true;
break;
}
}
const std::optional<bool>& arc_is_booting =
GetBootObserver()->arc_is_booting();
const bool arc_has_booted = (arc_is_booting && !*arc_is_booting);
const bool is_throttling = (cpu_restriction_state ==
CpuRestrictionState::CPU_RESTRICTION_BACKGROUND);
if (arc_has_booted && !never_enforce_quota_ && is_throttling) {
use_quota = true;
DVLOG(2) << "Enforcing cfs_quota";
}
delegate_->SetCpuRestriction(cpu_restriction_state, use_quota);
}
void ArcInstanceThrottle::RecordCpuRestrictionDisabledUMA(
const std::string& observer_name,
base::TimeDelta delta) {
delegate_->RecordCpuRestrictionDisabledUMA(observer_name, delta);
}
void ArcInstanceThrottle::NotifyCpuRestriction(
CpuRestrictionState cpu_restriction_state) {
auto* power =
ARC_GET_INSTANCE_FOR_METHOD(bridge_->power(), OnCpuRestrictionChanged);
if (!power)
return;
power->OnCpuRestrictionChanged(
static_cast<mojom::CpuRestrictionState>(cpu_restriction_state));
}
ArcBootPhaseThrottleObserver* ArcInstanceThrottle::GetBootObserver() {
ash::ThrottleObserver* observer =
GetObserverByName(kArcBootPhaseThrottleObserverName);
return static_cast<ArcBootPhaseThrottleObserver*>(observer);
}
void ArcInstanceThrottle::EnsureFactoryBuilt() {
ArcInstanceThrottleFactory::GetInstance();
}
}