blob: 62e9061029f017eb0999207e81c102416e78a78c [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 "components/arc/metrics/arc_metrics_service.h"
#include <string>
#include <utility>
#include "ash/public/cpp/app_types.h"
#include "base/bind.h"
#include "base/logging.h"
#include "base/memory/singleton.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_util.h"
#include "chromeos/dbus/power_manager/idle.pb.h"
#include "chromeos/dbus/session_manager/session_manager_client.h"
#include "components/arc/arc_prefs.h"
#include "components/arc/arc_util.h"
#include "components/arc/metrics/stability_metrics_manager.h"
#include "components/arc/session/arc_bridge_service.h"
#include "components/exo/wm_helper.h"
#include "components/prefs/pref_service.h"
#include "components/user_prefs/user_prefs.h"
#include "content/public/browser/browser_context.h"
#include "ui/events/ozone/gamepad/gamepad_provider_ozone.h"
namespace arc {
namespace {
constexpr char kUmaPrefix[] = "Arc";
constexpr base::TimeDelta kUmaMinTime = base::TimeDelta::FromMilliseconds(1);
constexpr base::TimeDelta kUmaMaxTime = base::TimeDelta::FromSeconds(60);
constexpr int kUmaNumBuckets = 50;
constexpr int kUmaPriAbiMigMaxFailedAttempts = 10;
constexpr base::TimeDelta kRequestProcessListPeriod =
base::TimeDelta::FromMinutes(5);
constexpr char kArcProcessNamePrefix[] = "org.chromium.arc.";
constexpr char kGmsProcessNamePrefix[] = "com.google.android.gms";
constexpr char kBootProgressEnableScreen[] = "boot_progress_enable_screen";
constexpr char kBootProgressArcUpgraded[] = "boot_progress_arc_upgraded";
// App types to report.
constexpr char kAppTypeArcAppLauncher[] = "ArcAppLauncher";
constexpr char kAppTypeArcOther[] = "ArcOther";
constexpr char kAppTypeFirstParty[] = "FirstParty";
constexpr char kAppTypeGmsCore[] = "GmsCore";
constexpr char kAppTypePlayStore[] = "PlayStore";
constexpr char kAppTypeSystemServer[] = "SystemServer";
constexpr char kAppTypeSystem[] = "SystemApp";
constexpr char kAppTypeOther[] = "Other";
std::string AnrSourceToTableName(mojom::AnrSource value) {
switch (value) {
case mojom::AnrSource::OTHER:
return kAppTypeOther;
case mojom::AnrSource::SYSTEM_SERVER:
return kAppTypeSystemServer;
case mojom::AnrSource::SYSTEM_APP:
return kAppTypeSystem;
case mojom::AnrSource::GMS_CORE:
return kAppTypeGmsCore;
case mojom::AnrSource::PLAY_STORE:
return kAppTypePlayStore;
case mojom::AnrSource::FIRST_PARTY:
return kAppTypeFirstParty;
case mojom::AnrSource::ARC_OTHER:
return kAppTypeArcOther;
case mojom::AnrSource::ARC_APP_LAUNCHER:
return kAppTypeArcAppLauncher;
default:
LOG(ERROR) << "Unrecognized source ANR " << value;
return kAppTypeOther;
}
}
std::string BootTypeToString(mojom::BootType boot_type) {
switch (boot_type) {
case mojom::BootType::UNKNOWN:
break;
case mojom::BootType::FIRST_BOOT:
return ".FirstBoot";
case mojom::BootType::FIRST_BOOT_AFTER_UPDATE:
return ".FirstBootAfterUpdate";
case mojom::BootType::REGULAR_BOOT:
return ".RegularBoot";
}
NOTREACHED();
return "";
}
} // namespace
// static
ArcMetricsServiceFactory* ArcMetricsServiceFactory::GetInstance() {
return base::Singleton<ArcMetricsServiceFactory>::get();
}
// static
ArcMetricsService* ArcMetricsService::GetForBrowserContext(
content::BrowserContext* context) {
return ArcMetricsServiceFactory::GetForBrowserContext(context);
}
// static
ArcMetricsService* ArcMetricsService::GetForBrowserContextForTesting(
content::BrowserContext* context) {
return ArcMetricsServiceFactory::GetForBrowserContextForTesting(context);
}
// static
BrowserContextKeyedServiceFactory* ArcMetricsService::GetFactory() {
return ArcMetricsServiceFactory::GetInstance();
}
ArcMetricsService::ArcMetricsService(content::BrowserContext* context,
ArcBridgeService* bridge_service)
: arc_bridge_service_(bridge_service),
guest_os_engagement_metrics_(user_prefs::UserPrefs::Get(context),
base::BindRepeating(ash::IsArcWindow),
prefs::kEngagementPrefsPrefix,
kUmaPrefix),
process_observer_(this),
intent_helper_observer_(this, &arc_bridge_service_observer_),
app_launcher_observer_(this, &arc_bridge_service_observer_) {
arc_bridge_service_->AddObserver(&arc_bridge_service_observer_);
arc_bridge_service_->app()->AddObserver(&app_launcher_observer_);
arc_bridge_service_->intent_helper()->AddObserver(&intent_helper_observer_);
arc_bridge_service_->metrics()->SetHost(this);
arc_bridge_service_->process()->AddObserver(&process_observer_);
// If WMHelper doesn't exist, do nothing. This occurs in tests.
if (exo::WMHelper::HasInstance())
exo::WMHelper::GetInstance()->AddActivationObserver(this);
ui::GamepadProviderOzone::GetInstance()->AddGamepadObserver(this);
StabilityMetricsManager::Get()->SetArcNativeBridgeType(
NativeBridgeType::UNKNOWN);
}
ArcMetricsService::~ArcMetricsService() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
ui::GamepadProviderOzone::GetInstance()->RemoveGamepadObserver(this);
// If WMHelper is already destroyed, do nothing.
// TODO(crbug.com/748380): Fix shutdown order.
if (exo::WMHelper::HasInstance())
exo::WMHelper::GetInstance()->RemoveActivationObserver(this);
arc_bridge_service_->process()->RemoveObserver(&process_observer_);
arc_bridge_service_->metrics()->SetHost(nullptr);
arc_bridge_service_->intent_helper()->RemoveObserver(
&intent_helper_observer_);
arc_bridge_service_->app()->RemoveObserver(&app_launcher_observer_);
arc_bridge_service_->RemoveObserver(&arc_bridge_service_observer_);
}
void ArcMetricsService::Shutdown() {
for (auto& obs : app_kill_observers_)
obs.OnArcMetricsServiceDestroyed();
app_kill_observers_.Clear();
}
// static
void ArcMetricsService::RecordArcUserInteraction(
content::BrowserContext* context,
UserInteractionType type) {
DCHECK(context);
auto* service = GetForBrowserContext(context);
if (!service) {
LOG(WARNING) << "Cannot get ArcMetricsService for context " << context;
return;
}
service->RecordArcUserInteraction(type);
}
void ArcMetricsService::RecordArcUserInteraction(UserInteractionType type) {
UMA_HISTOGRAM_ENUMERATION("Arc.UserInteraction", type);
for (auto& obs : user_interaction_observers_)
obs.OnUserInteraction(type);
}
void ArcMetricsService::SetHistogramNamer(HistogramNamer histogram_namer) {
histogram_namer_ = histogram_namer;
}
void ArcMetricsService::OnProcessConnectionReady() {
VLOG(2) << "Start updating process list.";
request_process_list_timer_.Start(FROM_HERE, kRequestProcessListPeriod, this,
&ArcMetricsService::RequestProcessList);
}
void ArcMetricsService::OnProcessConnectionClosed() {
VLOG(2) << "Stop updating process list.";
request_process_list_timer_.Stop();
}
void ArcMetricsService::RequestProcessList() {
mojom::ProcessInstance* process_instance = ARC_GET_INSTANCE_FOR_METHOD(
arc_bridge_service_->process(), RequestProcessList);
if (!process_instance)
return;
VLOG(2) << "RequestProcessList";
process_instance->RequestProcessList(base::BindOnce(
&ArcMetricsService::ParseProcessList, weak_ptr_factory_.GetWeakPtr()));
}
void ArcMetricsService::ParseProcessList(
std::vector<mojom::RunningAppProcessInfoPtr> processes) {
int running_app_count = 0;
for (const auto& process : processes) {
const std::string& process_name = process->process_name;
const mojom::ProcessState& process_state = process->process_state;
// Processes like the ARC launcher and intent helper are always running
// and not counted as apps running by users. With the same reasoning,
// GMS (Google Play Services) and its related processes are skipped as
// well. The process_state check below filters out system processes,
// services, apps that are cached because they've run before.
if (base::StartsWith(process_name, kArcProcessNamePrefix,
base::CompareCase::SENSITIVE) ||
base::StartsWith(process_name, kGmsProcessNamePrefix,
base::CompareCase::SENSITIVE) ||
process_state != mojom::ProcessState::TOP) {
VLOG(2) << "Skipped " << process_name << " " << process_state;
} else {
++running_app_count;
}
}
UMA_HISTOGRAM_COUNTS_100("Arc.AppCount", running_app_count);
}
void ArcMetricsService::OnArcStartTimeRetrieved(
std::vector<mojom::BootProgressEventPtr> events,
mojom::BootType boot_type,
absl::optional<base::TimeTicks> arc_start_time) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (!arc_start_time.has_value()) {
LOG(ERROR) << "Failed to retrieve ARC start timeticks.";
return;
}
VLOG(2) << "ARC start @" << arc_start_time.value();
DCHECK_NE(mojom::BootType::UNKNOWN, boot_type);
const std::string suffix = BootTypeToString(boot_type);
for (const auto& event : events) {
VLOG(2) << "Report boot progress event:" << event->event << "@"
<< event->uptimeMillis;
const std::string name = "Arc." + event->event + suffix;
const base::TimeTicks uptime =
base::TimeDelta::FromMilliseconds(event->uptimeMillis) +
base::TimeTicks();
const base::TimeDelta elapsed_time = uptime - arc_start_time.value();
base::UmaHistogramCustomTimes(name, elapsed_time, kUmaMinTime, kUmaMaxTime,
kUmaNumBuckets);
if (event->event.compare(kBootProgressEnableScreen) == 0) {
base::UmaHistogramCustomTimes("Arc.AndroidBootTime" + suffix,
elapsed_time, kUmaMinTime, kUmaMaxTime,
kUmaNumBuckets);
}
}
}
void ArcMetricsService::ReportBootProgress(
std::vector<mojom::BootProgressEventPtr> events,
mojom::BootType boot_type) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (boot_type == mojom::BootType::UNKNOWN) {
LOG(WARNING) << "boot_type is unknown. Skip recording UMA.";
return;
}
if (IsArcVmEnabled()) {
// For VM builds, do not call into session_manager since we don't use it
// for the builds. The upgrade time is included in the events vector so we
// can extract it here.
absl::optional<base::TimeTicks> arc_start_time =
GetArcStartTimeFromEvents(events);
OnArcStartTimeRetrieved(std::move(events), boot_type, arc_start_time);
return;
}
// Retrieve ARC full container's start time from session manager.
chromeos::SessionManagerClient::Get()->GetArcStartTime(base::BindOnce(
&ArcMetricsService::OnArcStartTimeRetrieved,
weak_ptr_factory_.GetWeakPtr(), std::move(events), boot_type));
}
void ArcMetricsService::ReportNativeBridge(
mojom::NativeBridgeType mojo_native_bridge_type) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
VLOG(2) << "Mojo native bridge type is " << mojo_native_bridge_type;
NativeBridgeType native_bridge_type = NativeBridgeType::UNKNOWN;
switch (mojo_native_bridge_type) {
case mojom::NativeBridgeType::NONE:
native_bridge_type = NativeBridgeType::NONE;
break;
case mojom::NativeBridgeType::HOUDINI:
native_bridge_type = NativeBridgeType::HOUDINI;
break;
case mojom::NativeBridgeType::NDK_TRANSLATION:
native_bridge_type = NativeBridgeType::NDK_TRANSLATION;
break;
}
DCHECK_NE(native_bridge_type, NativeBridgeType::UNKNOWN)
<< mojo_native_bridge_type;
StabilityMetricsManager::Get()->SetArcNativeBridgeType(native_bridge_type);
}
void ArcMetricsService::ReportCompanionLibApiUsage(
mojom::CompanionLibApiId api_id) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
UMA_HISTOGRAM_ENUMERATION("Arc.CompanionLibraryApisCounter", api_id);
}
void ArcMetricsService::ReportAppKill(mojom::AppKillPtr app_kill) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
switch (app_kill->type) {
case mojom::AppKillType::LMKD_KILL:
NotifyLowMemoryKill();
break;
case mojom::AppKillType::OOM_KILL:
NotifyOOMKillCount(app_kill->count);
break;
}
}
void ArcMetricsService::NotifyLowMemoryKill() {
for (auto& obs : app_kill_observers_)
obs.OnArcLowMemoryKill();
}
void ArcMetricsService::NotifyOOMKillCount(unsigned long count) {
for (auto& obs : app_kill_observers_)
obs.OnArcOOMKillCount(count);
}
void ArcMetricsService::ReportArcCorePriAbiMigEvent(
mojom::ArcCorePriAbiMigEvent event_type) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
UMA_HISTOGRAM_ENUMERATION("Arc.AbiMigration.Event", event_type);
}
void ArcMetricsService::ReportArcCorePriAbiMigFailedTries(
uint32_t failed_attempts) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
UMA_HISTOGRAM_EXACT_LINEAR("Arc.AbiMigration.FailedAttempts", failed_attempts,
kUmaPriAbiMigMaxFailedAttempts);
}
void ArcMetricsService::ReportArcCorePriAbiMigDowngradeDelay(
base::TimeDelta delay) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
base::UmaHistogramCustomTimes("Arc.AbiMigration.DowngradeDelay", delay,
kUmaMinTime, kUmaMaxTime, kUmaNumBuckets);
}
void ArcMetricsService::OnArcStartTimeForPriAbiMigration(
base::TimeTicks durationTicks,
absl::optional<base::TimeTicks> arc_start_time) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (!arc_start_time.has_value()) {
LOG(ERROR) << "Failed to retrieve ARC start timeticks.";
return;
}
VLOG(2) << "ARC start for Primary Abi Migration @" << arc_start_time.value();
const base::TimeDelta elapsed_time = durationTicks - arc_start_time.value();
base::UmaHistogramCustomTimes("Arc.AbiMigration.BootTime", elapsed_time,
kUmaMinTime, kUmaMaxTime, kUmaNumBuckets);
}
void ArcMetricsService::ReportArcCorePriAbiMigBootTime(
base::TimeDelta duration) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// For VM builds, we are directly reporting the boot time duration from
// ARC Metrics code.
if (IsArcVmEnabled()) {
base::UmaHistogramCustomTimes("Arc.AbiMigration.BootTime", duration,
kUmaMinTime, kUmaMaxTime, kUmaNumBuckets);
return;
}
// For container builds, we report the time of boot_progress_enable_screen
// event, and boot time duration is calculated by subtracting the ARC start
// time, which is fetched from session manager.
const base::TimeTicks durationTicks = duration + base::TimeTicks();
// Retrieve ARC full container's start time from session manager.
chromeos::SessionManagerClient::Get()->GetArcStartTime(
base::BindOnce(&ArcMetricsService::OnArcStartTimeForPriAbiMigration,
weak_ptr_factory_.GetWeakPtr(), durationTicks));
}
void ArcMetricsService::ReportClipboardDragDropEvent(
mojom::ArcClipboardDragDropEvent event_type) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
base::UmaHistogramEnumeration("Arc.ClipboardDragDrop", event_type);
}
void ArcMetricsService::ReportAnr(mojom::AnrPtr anr) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
base::UmaHistogramEnumeration("Arc.Anr.Overall", anr->type);
base::UmaHistogramEnumeration("Arc.Anr." + AnrSourceToTableName(anr->source),
anr->type);
}
void ArcMetricsService::OnWindowActivated(
wm::ActivationChangeObserver::ActivationReason reason,
aura::Window* gained_active,
aura::Window* lost_active) {
was_arc_window_active_ = ash::IsArcWindow(gained_active);
if (!was_arc_window_active_) {
gamepad_interaction_recorded_ = false;
return;
}
RecordArcUserInteraction(UserInteractionType::APP_CONTENT_WINDOW_INTERACTION);
}
void ArcMetricsService::OnGamepadEvent(const ui::GamepadEvent& event) {
if (!was_arc_window_active_)
return;
if (gamepad_interaction_recorded_)
return;
gamepad_interaction_recorded_ = true;
RecordArcUserInteraction(UserInteractionType::GAMEPAD_INTERACTION);
}
void ArcMetricsService::OnTaskCreated(int32_t task_id,
const std::string& package_name,
const std::string& activity,
const std::string& intent) {
task_ids_.push_back(task_id);
guest_os_engagement_metrics_.SetBackgroundActive(true);
}
void ArcMetricsService::OnTaskDestroyed(int32_t task_id) {
auto it = std::find(task_ids_.begin(), task_ids_.end(), task_id);
if (it == task_ids_.end()) {
LOG(WARNING) << "unknown task_id, background time might be undermeasured";
return;
}
task_ids_.erase(it);
guest_os_engagement_metrics_.SetBackgroundActive(!task_ids_.empty());
}
void ArcMetricsService::AddAppKillObserver(AppKillObserver* obs) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
app_kill_observers_.AddObserver(obs);
}
void ArcMetricsService::RemoveAppKillObserver(AppKillObserver* obs) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
app_kill_observers_.RemoveObserver(obs);
}
void ArcMetricsService::AddUserInteractionObserver(
UserInteractionObserver* obs) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
user_interaction_observers_.AddObserver(obs);
}
void ArcMetricsService::RemoveUserInteractionObserver(
UserInteractionObserver* obs) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
user_interaction_observers_.RemoveObserver(obs);
}
absl::optional<base::TimeTicks> ArcMetricsService::GetArcStartTimeFromEvents(
std::vector<mojom::BootProgressEventPtr>& events) {
mojom::BootProgressEventPtr arc_upgraded_event;
for (auto it = events.begin(); it != events.end(); ++it) {
if (!(*it)->event.compare(kBootProgressArcUpgraded)) {
arc_upgraded_event = std::move(*it);
events.erase(it);
return base::TimeDelta::FromMilliseconds(
arc_upgraded_event->uptimeMillis) +
base::TimeTicks();
}
}
return absl::nullopt;
}
ArcMetricsService::ProcessObserver::ProcessObserver(
ArcMetricsService* arc_metrics_service)
: arc_metrics_service_(arc_metrics_service) {}
ArcMetricsService::ProcessObserver::~ProcessObserver() = default;
void ArcMetricsService::ProcessObserver::OnConnectionReady() {
arc_metrics_service_->OnProcessConnectionReady();
}
void ArcMetricsService::ProcessObserver::OnConnectionClosed() {
arc_metrics_service_->OnProcessConnectionClosed();
}
ArcMetricsService::ArcBridgeServiceObserver::ArcBridgeServiceObserver() =
default;
ArcMetricsService::ArcBridgeServiceObserver::~ArcBridgeServiceObserver() =
default;
void ArcMetricsService::ArcBridgeServiceObserver::BeforeArcBridgeClosed() {
arc_bridge_closing_ = true;
}
void ArcMetricsService::ArcBridgeServiceObserver::AfterArcBridgeClosed() {
arc_bridge_closing_ = false;
}
ArcMetricsService::IntentHelperObserver::IntentHelperObserver(
ArcMetricsService* arc_metrics_service,
ArcBridgeServiceObserver* arc_bridge_service_observer)
: arc_metrics_service_(arc_metrics_service),
arc_bridge_service_observer_(arc_bridge_service_observer) {}
ArcMetricsService::IntentHelperObserver::~IntentHelperObserver() = default;
void ArcMetricsService::IntentHelperObserver::OnConnectionClosed() {
// Ignore closed connections due to the container shutting down.
if (!arc_bridge_service_observer_->arc_bridge_closing_) {
base::UmaHistogramEnumeration(arc_metrics_service_->histogram_namer_.Run(
"Arc.Session.MojoDisconnection"),
MojoConnectionType::INTENT_HELPER);
}
}
ArcMetricsService::AppLauncherObserver::AppLauncherObserver(
ArcMetricsService* arc_metrics_service,
ArcBridgeServiceObserver* arc_bridge_service_observer)
: arc_metrics_service_(arc_metrics_service),
arc_bridge_service_observer_(arc_bridge_service_observer) {}
ArcMetricsService::AppLauncherObserver::~AppLauncherObserver() = default;
void ArcMetricsService::AppLauncherObserver::OnConnectionClosed() {
// Ignore closed connections due to the container shutting down.
if (!arc_bridge_service_observer_->arc_bridge_closing_) {
base::UmaHistogramEnumeration(arc_metrics_service_->histogram_namer_.Run(
"Arc.Session.MojoDisconnection"),
MojoConnectionType::APP_LAUNCHER);
}
}
} // namespace arc