blob: eae992af67a94a0cd3b548af2053d15430c4e3dc [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/arc/boot_phase_monitor/arc_boot_phase_monitor_bridge.h"
#include <utility>
#include "base/bind.h"
#include "base/logging.h"
#include "base/memory/singleton.h"
#include "base/metrics/histogram_macros.h"
#include "chrome/browser/chromeos/arc/arc_util.h"
#include "chrome/browser/chromeos/arc/boot_phase_monitor/arc_instance_throttle.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sessions/session_restore.h"
#include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
#include "chromeos/cryptohome/cryptohome_parameters.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/session_manager_client.h"
#include "components/arc/arc_bridge_service.h"
#include "components/arc/arc_browser_context_keyed_service_factory_base.h"
#include "components/arc/arc_prefs.h"
#include "components/arc/arc_util.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/extension_system_provider.h"
#include "extensions/browser/extensions_browser_client.h"
#include "extensions/common/one_shot_event.h"
namespace arc {
namespace {
void OnEmitArcBooted(bool success) {
if (!success)
VLOG(1) << "Failed to emit arc booted signal.";
}
class DefaultDelegateImpl : public ArcBootPhaseMonitorBridge::Delegate {
public:
DefaultDelegateImpl() = default;
~DefaultDelegateImpl() override = default;
void DisableCpuRestriction() override {
SetArcCpuRestriction(false /* do_restrict */);
}
void RecordFirstAppLaunchDelayUMA(base::TimeDelta delta) override {
VLOG(2) << "Launching the first app took "
<< delta.InMillisecondsRoundedUp() << " ms.";
UMA_HISTOGRAM_CUSTOM_TIMES("Arc.FirstAppLaunchDelay.TimeDelta", delta,
base::TimeDelta::FromMilliseconds(1),
base::TimeDelta::FromMinutes(2), 50);
}
private:
DISALLOW_COPY_AND_ASSIGN(DefaultDelegateImpl);
};
// Singleton factory for ArcBootPhaseMonitorBridge.
class ArcBootPhaseMonitorBridgeFactory
: public internal::ArcBrowserContextKeyedServiceFactoryBase<
ArcBootPhaseMonitorBridge,
ArcBootPhaseMonitorBridgeFactory> {
public:
// Factory name used by ArcBrowserContextKeyedServiceFactoryBase.
static constexpr const char* kName = "ArcBootPhaseMonitorBridgeFactory";
static ArcBootPhaseMonitorBridgeFactory* GetInstance() {
return base::Singleton<ArcBootPhaseMonitorBridgeFactory>::get();
}
private:
friend base::DefaultSingletonTraits<ArcBootPhaseMonitorBridgeFactory>;
ArcBootPhaseMonitorBridgeFactory() {
DependsOn(extensions::ExtensionsBrowserClient::Get()
->GetExtensionSystemFactory());
}
~ArcBootPhaseMonitorBridgeFactory() override = default;
};
} // namespace
// static
ArcBootPhaseMonitorBridge* ArcBootPhaseMonitorBridge::GetForBrowserContext(
content::BrowserContext* context) {
return ArcBootPhaseMonitorBridgeFactory::GetForBrowserContext(context);
}
// static
ArcBootPhaseMonitorBridge*
ArcBootPhaseMonitorBridge::GetForBrowserContextForTesting(
content::BrowserContext* context) {
return ArcBootPhaseMonitorBridgeFactory::GetForBrowserContextForTesting(
context);
}
// static
void ArcBootPhaseMonitorBridge::RecordFirstAppLaunchDelayUMA(
content::BrowserContext* context) {
auto* bridge = arc::ArcBootPhaseMonitorBridge::GetForBrowserContext(context);
if (bridge)
bridge->RecordFirstAppLaunchDelayUMAInternal();
}
ArcBootPhaseMonitorBridge::ArcBootPhaseMonitorBridge(
content::BrowserContext* context,
ArcBridgeService* bridge_service)
: context_(context),
arc_bridge_service_(bridge_service),
account_id_(multi_user_util::GetAccountIdFromProfile(
Profile::FromBrowserContext(context))),
// Set the default delegate. Unit tests may use a different one.
delegate_(std::make_unique<DefaultDelegateImpl>()),
weak_ptr_factory_(this) {
arc_bridge_service_->boot_phase_monitor()->SetHost(this);
auto* arc_session_manager = ArcSessionManager::Get();
DCHECK(arc_session_manager);
arc_session_manager->AddObserver(this);
SessionRestore::AddObserver(this);
auto* profile = Profile::FromBrowserContext(context);
auto* extension_system = extensions::ExtensionSystem::Get(profile);
DCHECK(extension_system);
extension_system->ready().Post(
FROM_HERE, base::Bind(&ArcBootPhaseMonitorBridge::OnExtensionsReady,
weak_ptr_factory_.GetWeakPtr()));
// Initialize |enabled_by_policy_| now.
OnArcPlayStoreEnabledChanged(IsArcPlayStoreEnabledForProfile(profile));
}
ArcBootPhaseMonitorBridge::~ArcBootPhaseMonitorBridge() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
arc_bridge_service_->boot_phase_monitor()->SetHost(nullptr);
auto* arc_session_manager = ArcSessionManager::Get();
DCHECK(arc_session_manager);
arc_session_manager->RemoveObserver(this);
SessionRestore::RemoveObserver(this);
}
void ArcBootPhaseMonitorBridge::RecordFirstAppLaunchDelayUMAInternal() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (first_app_launch_delay_recorded_)
return;
first_app_launch_delay_recorded_ = true;
if (boot_completed_) {
VLOG(2) << "ARC has already fully started. Recording the UMA now.";
if (delegate_)
delegate_->RecordFirstAppLaunchDelayUMA(base::TimeDelta());
return;
}
app_launch_time_ = base::TimeTicks::Now();
}
void ArcBootPhaseMonitorBridge::OnBootCompleted() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
VLOG(2) << "OnBootCompleted";
boot_completed_ = true;
chromeos::SessionManagerClient* session_manager_client =
chromeos::DBusThreadManager::Get()->GetSessionManagerClient();
session_manager_client->EmitArcBooted(cryptohome::Identification(account_id_),
base::BindOnce(&OnEmitArcBooted));
ArcSessionManager* arc_session_manager = ArcSessionManager::Get();
DCHECK(arc_session_manager);
if (arc_session_manager->is_directly_started()) {
// Unless this is opt-in boot, start monitoring window activation changes to
// prioritize/throttle the container when needed.
throttle_ = std::make_unique<ArcInstanceThrottle>();
VLOG(2) << "ArcInstanceThrottle created in OnBootCompleted()";
}
if (!app_launch_time_.is_null() && delegate_) {
delegate_->RecordFirstAppLaunchDelayUMA(base::TimeTicks::Now() -
app_launch_time_);
}
}
void ArcBootPhaseMonitorBridge::OnArcPlayStoreEnabledChanged(bool enabled) {
auto* profile = Profile::FromBrowserContext(context_);
enabled_by_policy_ =
enabled && IsArcPlayStoreEnabledPreferenceManagedForProfile(profile);
if (enabled_by_policy_)
MaybeDisableCpuRestriction();
}
void ArcBootPhaseMonitorBridge::OnArcInitialStart() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// ARC apps for opt-in finished doing their jobs. Start the throttle.
throttle_ = std::make_unique<ArcInstanceThrottle>();
VLOG(2) << "ArcInstanceThrottle created in OnArcInitialStart()";
}
void ArcBootPhaseMonitorBridge::OnArcSessionStopped(ArcStopReason stop_reason) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// Remove the throttle so that the window observer won't interfere with the
// container startup when the user opts in to ARC.
Reset();
VLOG(2) << "ArcInstanceThrottle has been removed in OnArcSessionStopped()";
}
void ArcBootPhaseMonitorBridge::OnArcSessionRestarting() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// Remove the throttle so that the window observer won't interfere with the
// container restart.
Reset();
VLOG(2) << "ArcInstanceThrottle has been removed in OnArcSessionRestarting()";
// We assume that a crash tends to happen while the user is actively using
// the instance. For that reason, we try to restart the instance without the
// restricted cgroups.
if (delegate_)
delegate_->DisableCpuRestriction();
}
void ArcBootPhaseMonitorBridge::OnSessionRestoreFinishedLoadingTabs() {
VLOG(2) << "All tabs have been restored";
if (throttle_)
return;
// |throttle_| is not available. This means either of the following:
// 1) This is an opt-in boot, and OnArcInitialStart() hasn't been called.
// 2) This is not an opt-in boot, and OnBootCompleted() hasn't been called.
// In both cases, relax the restriction to let the instance fully start.
VLOG(2) << "Allowing the instance to use more CPU resources";
if (delegate_)
delegate_->DisableCpuRestriction();
}
void ArcBootPhaseMonitorBridge::OnExtensionsReady() {
VLOG(2) << "All extensions are loaded";
extensions_ready_ = true;
MaybeDisableCpuRestriction();
}
void ArcBootPhaseMonitorBridge::MaybeDisableCpuRestriction() {
if (throttle_)
return;
if (!extensions_ready_ || !enabled_by_policy_)
return;
VLOG(1) << "ARC is enabled by policy. "
<< "Allowing the instance to use more CPU resources";
if (delegate_)
delegate_->DisableCpuRestriction();
}
void ArcBootPhaseMonitorBridge::Reset() {
throttle_.reset();
app_launch_time_ = base::TimeTicks();
first_app_launch_delay_recorded_ = false;
boot_completed_ = false;
enabled_by_policy_ = false;
// Do not reset |extensions_ready_| here. That variable is not tied to the
// instance.
}
void ArcBootPhaseMonitorBridge::SetDelegateForTesting(
std::unique_ptr<Delegate> delegate) {
delegate_ = std::move(delegate);
}
} // namespace arc