[go: nahoru, domu]

blob: 500d181a3797cf2d28fac91940e24b1a44b591fe [file] [log] [blame]
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/components/arc/metrics/arc_metrics_service.h"
#include <sys/sysinfo.h>
#include <string>
#include <utility>
#include "ash/components/arc/arc_features.h"
#include "ash/components/arc/arc_prefs.h"
#include "ash/components/arc/arc_util.h"
#include "ash/components/arc/metrics/arc_metrics_anr.h"
#include "ash/components/arc/metrics/arc_wm_metrics.h"
#include "ash/components/arc/metrics/stability_metrics_manager.h"
#include "ash/components/arc/session/arc_bridge_service.h"
#include "ash/public/cpp/app_types_util.h"
#include "base/functional/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/ranges/algorithm.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "chromeos/ash/components/dbus/concierge/concierge_client.h"
#include "chromeos/ash/components/dbus/session_manager/session_manager_client.h"
#include "chromeos/dbus/power_manager/idle.pb.h"
#include "components/exo/wm_helper.h"
#include "components/metrics/psi_memory_parser.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::Milliseconds(1);
constexpr base::TimeDelta kUmaMaxTime = base::Seconds(60);
constexpr int kUmaNumBuckets = 50;
constexpr int kUmaPriAbiMigMaxFailedAttempts = 10;
constexpr int kUmaFixupDirectoriesCountMin = 0;
constexpr int kUmaFixupDirectoriesCountMax = 5000000;
constexpr int kUmaFixupAppsCountMin = 0;
constexpr int kUmaFixupAppsCountMax = 10000;
constexpr base::TimeDelta kRequestProcessListPeriod = base::Minutes(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";
// Interval for collecting UMA "Arc.App.LowMemoryKills.*Count10Minutes" metrics.
constexpr base::TimeDelta kRequestKillCountPeriod = base::Minutes(10);
// Memory pressure histograms.
const char kPSIMemoryPressureSomeARC[] = "ChromeOS.CWP.PSIMemPressure.ArcSome";
const char kPSIMemoryPressureFullARC[] = "ChromeOS.CWP.PSIMemPressure.ArcFull";
// Provisioning pre-sign-in time delta histogram.
constexpr char kProvisioningPreSignInTimeDelta[] =
"Arc.Provisioning.PreSignIn.TimeDelta";
// Logs UMA enum values to facilitate finding feedback reports in Xamine.
template <typename T>
void LogStabilityUmaEnum(const std::string& name, T sample) {
base::UmaHistogramEnumeration(name, sample);
VLOG(1) << name << ": " << static_cast<std::underlying_type_t<T>>(sample);
}
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 "";
}
const char* LowLatencyStylusLibraryTypeToString(
mojom::LowLatencyStylusLibraryType library_type) {
switch (library_type) {
case mojom::LowLatencyStylusLibraryType::kUnsupported:
break;
case mojom::LowLatencyStylusLibraryType::kCPU:
return ".CPU";
case mojom::LowLatencyStylusLibraryType::kGPU:
return ".GPU";
}
NOTREACHED();
return "";
}
const char* DnsQueryToString(mojom::ArcDnsQuery query) {
switch (query) {
case mojom::ArcDnsQuery::OTHER_HOST_NAME:
return "Other";
case mojom::ArcDnsQuery::ANDROID_API_HOST_NAME:
return "AndroidApi";
}
NOTREACHED();
return "";
}
const char* WaylandTimingEventToString(mojom::WaylandTimingEvent event) {
switch (event) {
case mojom::WaylandTimingEvent::kOther:
return ".Other";
case mojom::WaylandTimingEvent::kBinderReleaseClipboardData:
return ".BinderReleaseClipboardData";
case mojom::WaylandTimingEvent::kWlBufferRelease:
return ".WlBufferRelease";
case mojom::WaylandTimingEvent::kWlKeyboardLeave:
return ".WlKeyboardLeave";
case mojom::WaylandTimingEvent::kWlPointerMotion:
return ".WlPointerMotion";
case mojom::WaylandTimingEvent::kWlPointerLeave:
return ".WlPointerLeave";
case mojom::WaylandTimingEvent::kZauraShellActivated:
return ".ZauraShellActivated";
case mojom::WaylandTimingEvent::kZauraSurfaceOcclusionChanged:
return ".ZauraSurfaceOcclusionChanged";
case mojom::WaylandTimingEvent::kZcrRemoteSurfaceWindowGeometryChanged:
return ".ZcrRemoteSurfaceWindowGeometryChanged";
case mojom::WaylandTimingEvent::kZcrRemoteSurfaceBoundsChangedInOutput:
return ".ZcrRemoteSurfaceBoundsChangedInOutput";
case mojom::WaylandTimingEvent::kZcrVsyncTimingUpdate:
return ".ZcrVsyncTimingUpdate";
}
NOTREACHED();
return "";
}
struct LoadAverageHistogram {
const char* name;
base::TimeDelta duration;
};
constexpr LoadAverageHistogram kLoadAverageHistograms[] = {
{"Arc.LoadAverageX100PerProcessor1MinuteAfterArcStart", base::Minutes(1)},
{"Arc.LoadAverageX100PerProcessor5MinutesAfterArcStart", base::Minutes(5)},
{"Arc.LoadAverageX100PerProcessor15MinutesAfterArcStart",
base::Minutes(15)},
};
} // 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);
DCHECK(StabilityMetricsManager::Get());
StabilityMetricsManager::Get()->SetArcNativeBridgeType(
NativeBridgeType::UNKNOWN);
if (base::FeatureList::IsEnabled(kVmMemoryPSIReports)) {
psi_parser_ = std::make_unique<metrics::PSIMemoryParser>(
kVmMemoryPSIReportsPeriod.Get());
}
arc_wm_metrics_ = std::make_unique<ArcWmMetrics>();
}
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() {
metrics_anr_.reset();
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::SetHistogramNamerCallback(
HistogramNamerCallback histogram_namer_cb) {
histogram_namer_cb_ = histogram_namer_cb;
}
void ArcMetricsService::OnProcessConnectionReady() {
VLOG(2) << "Start updating process list.";
request_process_list_timer_.Start(FROM_HERE, kRequestProcessListPeriod, this,
&ArcMetricsService::RequestProcessList);
if (IsArcVmEnabled()) {
prev_logged_memory_kills_.reset();
// Initialize prev_logged_memory_kills_ by immediately requesting new
// values. We don't need the VM list to exist to update it, so pass nullopt.
OnListVmsResponse(std::nullopt);
request_kill_count_timer_.Start(
FROM_HERE, kRequestKillCountPeriod, this,
&ArcMetricsService::OnRequestKillCountTimer);
}
}
void ArcMetricsService::OnProcessConnectionClosed() {
VLOG(2) << "Stop updating process list.";
request_process_list_timer_.Stop();
request_kill_count_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);
}
// Helper that logs a single kill count type to a histogram, or logs a warning
// if the counter decreased.
static void LogLowMemoryKillCount(const char* vm_desc,
const char* name,
uint32_t prev_kills,
uint32_t curr_kills) {
if (prev_kills <= curr_kills) {
std::string histogram =
base::StringPrintf("Arc.App.LowMemoryKills%s%s", vm_desc, name);
base::UmaHistogramExactLinear(histogram, curr_kills - prev_kills, 50);
} else {
LOG(WARNING) << "LowMemoryKillCounts reported a decrease for " << vm_desc
<< name << ", previous: " << prev_kills
<< ", current: " << curr_kills;
}
}
// Helper that logs kill counts for a specific background VM.
static void LogLowMemoryKillCountsForVm(
const char* vm_desc,
const mojom::LowMemoryKillCountsPtr& prev,
const mojom::LowMemoryKillCountsPtr& curr) {
LogLowMemoryKillCount(vm_desc, ".LinuxOOMCount10Minutes", prev->guest_oom,
curr->guest_oom);
LogLowMemoryKillCount(vm_desc, ".LMKD.ForegroundCount10Minutes",
prev->lmkd_foreground, curr->lmkd_foreground);
LogLowMemoryKillCount(vm_desc, ".LMKD.PerceptibleCount10Minutes",
prev->lmkd_perceptible, curr->lmkd_perceptible);
LogLowMemoryKillCount(vm_desc, ".LMKD.CachedCount10Minutes",
prev->lmkd_cached, curr->lmkd_cached);
LogLowMemoryKillCount(vm_desc, ".Pressure.ForegroundCount10Minutes",
prev->pressure_foreground, curr->pressure_foreground);
LogLowMemoryKillCount(vm_desc, ".Pressure.PerceptibleCount10Minutes",
prev->pressure_perceptible, curr->pressure_perceptible);
LogLowMemoryKillCount(vm_desc, ".Pressure.CachedCount10Minutes",
prev->pressure_cached, curr->pressure_cached);
}
static void LogLowMemoryKillCounts(
const mojom::LowMemoryKillCountsPtr& prev,
const mojom::LowMemoryKillCountsPtr& curr,
std::optional<vm_tools::concierge::ListVmsResponse> vms_list) {
// Only log to the histograms if we have a previous sample to compute deltas
// from.
if (!prev)
return;
LogLowMemoryKillCountsForVm("", prev, curr);
// Only log the background VMs counters if we have a valid list of background
// VMs.
if (!vms_list || !vms_list->success())
return;
// Use a set to de-duplicate VM types.
std::unordered_set<vm_tools::concierge::VmInfo_VmType> vm_types;
for (int i = 0; i < vms_list->vms_size(); i++) {
const auto& vm = vms_list->vms(i);
if (vm.has_vm_info()) {
const auto& info = vm.vm_info();
vm_types.emplace(info.vm_type());
} else {
LOG(WARNING) << "OnLowMemoryKillCounts got VM " << vm.name()
<< " with no vm_info.";
}
}
for (auto vm_type : vm_types) {
switch (vm_type) {
case vm_tools::concierge::VmInfo_VmType_ARC_VM:
if (vm_types.size() == 1) {
// Only ARCVM, log to a special set of counters.
LogLowMemoryKillCountsForVm(".OnlyArc", prev, curr);
}
break;
case vm_tools::concierge::VmInfo_VmType_BOREALIS:
LogLowMemoryKillCountsForVm(".Steam", prev, curr);
break;
case vm_tools::concierge::VmInfo_VmType_TERMINA:
LogLowMemoryKillCountsForVm(".Crostini", prev, curr);
break;
case vm_tools::concierge::VmInfo_VmType_PLUGIN_VM:
LogLowMemoryKillCountsForVm(".PluginVm", prev, curr);
break;
default:
LogLowMemoryKillCountsForVm(".UnknownVm", prev, curr);
break;
}
}
}
void ArcMetricsService::RequestKillCountsForTesting() {
OnRequestKillCountTimer();
}
void ArcMetricsService::OnRequestKillCountTimer() {
auto* client = ash::ConciergeClient::Get();
if (!client) {
LOG(WARNING)
<< "Cannot get ConciergeClient for method OnRequestKillCountTimer";
return;
}
vm_tools::concierge::ListVmsRequest request;
request.set_owner_id(user_id_hash_);
client->ListVms(request, base::BindOnce(&ArcMetricsService::OnListVmsResponse,
weak_ptr_factory_.GetWeakPtr()));
}
void ArcMetricsService::OnListVmsResponse(
std::optional<vm_tools::concierge::ListVmsResponse> response) {
mojom::ProcessInstance* process_instance = ARC_GET_INSTANCE_FOR_METHOD(
arc_bridge_service_->process(), RequestLowMemoryKillCounts);
if (!process_instance) {
LOG(WARNING) << "Cannot get ProcessInstance for method OnListVmsResponse";
return;
}
process_instance->RequestLowMemoryKillCounts(
base::BindOnce(&ArcMetricsService::OnLowMemoryKillCounts,
weak_ptr_factory_.GetWeakPtr(), response));
}
void ArcMetricsService::OnLowMemoryKillCounts(
std::optional<vm_tools::concierge::ListVmsResponse> vms_list,
mojom::LowMemoryKillCountsPtr counts) {
DCHECK(daily_);
if (daily_) {
int oom = counts->guest_oom;
int foreground = counts->lmkd_foreground + counts->pressure_foreground;
int perceptible = counts->lmkd_perceptible + counts->pressure_foreground;
int cached = counts->lmkd_cached + counts->pressure_cached;
if (prev_logged_memory_kills_) {
oom -= prev_logged_memory_kills_->guest_oom;
foreground -= prev_logged_memory_kills_->lmkd_foreground +
prev_logged_memory_kills_->pressure_foreground;
perceptible -= prev_logged_memory_kills_->lmkd_perceptible +
prev_logged_memory_kills_->pressure_perceptible;
cached -= prev_logged_memory_kills_->lmkd_cached +
prev_logged_memory_kills_->pressure_cached;
}
if (oom >= 0 && foreground >= 0 && perceptible >= 0 && cached >= 0) {
daily_->OnLowMemoryKillCounts(vms_list, oom, foreground, perceptible,
cached);
} else {
LOG(WARNING) << "OnLowMemoryKillCounts observed a decrease in monotonic "
"kill counters";
}
}
LogLowMemoryKillCounts(prev_logged_memory_kills_, counts, vms_list);
prev_logged_memory_kills_ = std::move(counts);
}
void ArcMetricsService::OnArcStartTimeRetrieved(
std::vector<mojom::BootProgressEventPtr> events,
mojom::BootType boot_type,
std::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 base::TimeTicks uptime =
base::Milliseconds(event->uptimeMillis) + base::TimeTicks();
const base::TimeDelta elapsed_time = uptime - arc_start_time.value();
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;
}
boot_type_ = boot_type;
for (auto& obs : boot_type_observers_)
obs.OnBootTypeRetrieved(boot_type);
if (metrics_anr_)
metrics_anr_->set_uma_suffix(BootTypeToString(boot_type));
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.
std::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.
ash::SessionManagerClient::Get()->GetArcStartTime(base::BindOnce(
&ArcMetricsService::OnArcStartTimeRetrieved,
weak_ptr_factory_.GetWeakPtr(), std::move(events), boot_type));
// Record load average in case they are already measured.
MaybeRecordLoadAveragePerProcessor();
}
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;
case mojom::AppKillType::GMS_UPDATE_KILL:
case mojom::AppKillType::GMS_START_KILL:
for (uint32_t i = 0; i < app_kill->count; i++) {
UMA_HISTOGRAM_ENUMERATION(
"Arc.App.GmsCoreKill" + BootTypeToString(boot_type_),
app_kill->type);
}
break;
}
}
void ArcMetricsService::ReportDnsQueryResult(mojom::ArcDnsQuery query,
bool success) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
std::string metric_name =
base::StrCat({"Arc.Net.DnsQuery.", DnsQueryToString(query)});
if (!success)
VLOG(4) << metric_name << ": " << success;
base::UmaHistogramBoolean(metric_name, success);
}
void ArcMetricsService::ReportImageCopyPasteCompatActionDeprecated(
mojom::ArcImageCopyPasteCompatAction action_type) {
// Intentionally no-op. This metric was deprecated.
}
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::ReportAppPrimaryAbi(mojom::AppPrimaryAbi abi) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
base::UmaHistogramEnumeration("Arc.App.PrimaryAbi", abi);
}
void ArcMetricsService::ReportArcCorePriAbiMigEvent(
mojom::ArcCorePriAbiMigEvent event_type) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
LogStabilityUmaEnum("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,
std::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.
ash::SessionManagerClient::Get()->GetArcStartTime(
base::BindOnce(&ArcMetricsService::OnArcStartTimeForPriAbiMigration,
weak_ptr_factory_.GetWeakPtr(), durationTicks));
}
void ArcMetricsService::ReportArcSystemHealthUpgrade(base::TimeDelta duration,
bool packages_deleted) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
base::UmaHistogramCustomTimes("Arc.SystemHealth.Upgrade.TimeDelta", duration,
kUmaMinTime, kUmaMaxTime, kUmaNumBuckets);
base::UmaHistogramBoolean("Arc.SystemHealth.Upgrade.PackagesDeleted",
packages_deleted);
}
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_);
if (!metrics_anr_) {
LOG(ERROR) << "ANR is reported when no ANR metric service is connected";
return;
}
metrics_anr_->Report(std::move(anr));
}
void ArcMetricsService::ReportLowLatencyStylusLibApiUsage(
mojom::LowLatencyStylusLibApiId api_id) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
UMA_HISTOGRAM_ENUMERATION("Arc.LowLatencyStylusLibraryApisCounter", api_id);
}
void ArcMetricsService::ReportVpnServiceBuilderCompatApiUsage(
mojom::VpnServiceBuilderCompatApiId api_id) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
base::UmaHistogramEnumeration("Arc.VpnServiceBuilderCompatApisCounter",
api_id);
}
void ArcMetricsService::ReportLowLatencyStylusLibPredictionTarget(
mojom::LowLatencyStylusLibPredictionTargetPtr prediction_target) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
base::UmaHistogramCounts100(
base::StrCat(
{"Arc.LowLatencyStylusLibrary.PredictionTarget",
LowLatencyStylusLibraryTypeToString(prediction_target->type)}),
prediction_target->target);
}
void ArcMetricsService::ReportEntireFixupMetrics(base::TimeDelta duration,
uint32_t number_of_directories,
uint32_t number_of_failures) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
base::UmaHistogramLongTimes("Arc.Fixup.Entire.Duration", duration);
base::UmaHistogramCustomCounts("Arc.Fixup.Entire.Directories",
number_of_directories,
kUmaFixupDirectoriesCountMin,
kUmaFixupDirectoriesCountMax, kUmaNumBuckets);
base::UmaHistogramCustomCounts("Arc.Fixup.Entire.Failures",
number_of_failures, kUmaFixupAppsCountMin,
kUmaFixupAppsCountMax, kUmaNumBuckets);
}
void ArcMetricsService::ReportPerAppFixupMetrics(
base::TimeDelta duration,
uint32_t number_of_directories) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
base::UmaHistogramLongTimes("Arc.Fixup.PerApp.Duration", duration);
base::UmaHistogramCustomCounts("Arc.Fixup.PerApp.Directories",
number_of_directories,
kUmaFixupDirectoriesCountMin,
kUmaFixupDirectoriesCountMax, kUmaNumBuckets);
}
void ArcMetricsService::ReportMainAccountHashMigrationMetrics(
mojom::MainAccountHashMigrationStatus status) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
UMA_HISTOGRAM_ENUMERATION("Arc.Auth.MainAccountHashMigration.Status", status);
}
void ArcMetricsService::ReportDataRestore(mojom::DataRestoreStatus status,
int64_t duration_ms) {
base::UmaHistogramEnumeration("Arc.DataRestore.Status", status);
if (status == mojom::DataRestoreStatus::kNotNeeded)
return;
base::UmaHistogramMediumTimes("Arc.DataRestore.Duration",
base::Milliseconds(duration_ms));
}
void ArcMetricsService::ReportMemoryPressure(
const std::vector<uint8_t>& psi_file_contents) {
if (!psi_parser_) {
LOG(WARNING) << "Unexpected PSI reporting call detected";
return;
}
int metric_some;
int metric_full;
auto stat = psi_parser_->ParseMetrics(psi_file_contents.data(),
psi_file_contents.size(), &metric_some,
&metric_full);
psi_parser_->LogParseStatus(
stat); // Log success and failure, for histograms.
if (stat != metrics::ParsePSIMemStatus::kSuccess)
return;
base::UmaHistogramCustomCounts(
kPSIMemoryPressureSomeARC, metric_some, metrics::kMemPressureMin,
metrics::kMemPressureExclusiveMax, metrics::kMemPressureHistogramBuckets);
base::UmaHistogramCustomCounts(
kPSIMemoryPressureFullARC, metric_full, metrics::kMemPressureMin,
metrics::kMemPressureExclusiveMax, metrics::kMemPressureHistogramBuckets);
}
void ArcMetricsService::SetPrefService(PrefService* prefs) {
prefs_ = prefs;
daily_ = std::make_unique<ArcDailyMetrics>(prefs);
}
void ArcMetricsService::ReportProvisioningStartTime(
const base::TimeTicks& start_time,
const std::string& account_type_suffix) {
arc_provisioning_start_time_.emplace(start_time);
arc_provisioning_account_type_suffix_.emplace(account_type_suffix);
}
void ArcMetricsService::ReportProvisioningPreSignIn() {
if (arc_provisioning_start_time_.has_value() &&
arc_provisioning_account_type_suffix_.has_value()) {
base::UmaHistogramCustomTimes(
kProvisioningPreSignInTimeDelta +
arc_provisioning_account_type_suffix_.value(),
base::TimeTicks::Now() - arc_provisioning_start_time_.value(),
base::Seconds(1), base::Minutes(5), 50);
arc_provisioning_start_time_.reset();
arc_provisioning_account_type_suffix_.reset();
} else {
LOG(ERROR) << "PreSignIn reported without prior starting time";
}
}
void ArcMetricsService::ReportWaylandLateTimingEvent(
mojom::WaylandTimingEvent event,
base::TimeDelta duration) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
const std::string suffix = WaylandTimingEventToString(event);
base::UmaHistogramLongTimes("Arc.Wayland.LateTiming.Duration" + suffix,
duration);
base::UmaHistogramEnumeration("Arc.Wayland.LateTiming.Event", event);
}
void ArcMetricsService::ReportWebViewProcessStarted() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(prefs_);
prefs_->SetBoolean(prefs::kWebViewProcessStarted, true);
}
void ArcMetricsService::ReportNewQosSocketCount(int count) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
base::UmaHistogramCounts100000("Arc.Qos.NewQosSocketCount", count);
}
void ArcMetricsService::ReportQosSocketPercentage(int perc) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
base::UmaHistogramCounts100("Arc.Qos.QosSocketPercentage", perc);
}
void ArcMetricsService::ReportArcKeyMintError(mojom::ArcKeyMintError error) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
base::UmaHistogramEnumeration("Arc.KeyMint.KeyMintError", error);
}
void ArcMetricsService::ReportDragResizeLatency(
const std::vector<base::TimeDelta>& durations) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
for (const auto duration : durations) {
base::UmaHistogramCustomTimes("Arc.WM.WindowDragResizeTime", duration,
/*minimum=*/base::Milliseconds(1),
/*maximum=*/base::Seconds(3), 100);
}
}
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 = base::ranges::find(task_ids_, 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::OnArcStarted() {
DCHECK(prefs_);
metrics_anr_ = std::make_unique<ArcMetricsAnr>(prefs_);
// Post tasks to record load average.
for (size_t index = 0; index < std::size(kLoadAverageHistograms); ++index) {
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&ArcMetricsService::MeasureLoadAverage,
weak_ptr_factory_.GetWeakPtr(), index),
kLoadAverageHistograms[index].duration);
}
}
void ArcMetricsService::OnArcSessionStopped() {
DCHECK(prefs_);
boot_type_ = mojom::BootType::UNKNOWN;
metrics_anr_.reset();
base::UmaHistogramBoolean("Arc.Session.HasWebViewUsage",
prefs_->GetBoolean(prefs::kWebViewProcessStarted));
prefs_->SetBoolean(prefs::kWebViewProcessStarted, false);
}
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);
}
void ArcMetricsService::AddBootTypeObserver(BootTypeObserver* obs) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
boot_type_observers_.AddObserver(obs);
}
void ArcMetricsService::RemoveBootTypeObserver(BootTypeObserver* obs) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
boot_type_observers_.RemoveObserver(obs);
}
std::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::Milliseconds(arc_upgraded_event->uptimeMillis) +
base::TimeTicks();
}
}
return std::nullopt;
}
void ArcMetricsService::ReportMemoryPressureArcVmKills(int count,
int estimated_freed_kb) {
for (auto& obs : app_kill_observers_)
obs.OnArcMemoryPressureKill(count, estimated_freed_kb);
}
void ArcMetricsService::ReportArcNetworkEvent(mojom::ArcNetworkEvent event) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
base::UmaHistogramEnumeration("Arc.Net.ArcNetworkEvent", event);
}
void ArcMetricsService::ReportArcNetworkError(mojom::ArcNetworkError error) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
base::UmaHistogramEnumeration("Arc.Net.ArcNetworkError", error);
}
void ArcMetricsService::MeasureLoadAverage(size_t index) {
struct sysinfo info = {};
if (sysinfo(&info) < 0) {
DCHECK_LT(index, std::size(kLoadAverageHistograms));
PLOG(ERROR) << "sysinfo() failed when trying to record "
<< kLoadAverageHistograms[index].name;
return;
}
DCHECK_LT(index, std::size(info.loads));
// Load average values returned by sysinfo() are scaled up by
// 1 << SI_LOAD_SHIFT.
const int loadx100 = info.loads[index] * 100 / (1 << SI_LOAD_SHIFT);
// _SC_NPROCESSORS_ONLN instead of base::SysInfo::NumberOfProcessors() which
// uses _SC_NPROCESSORS_CONF to get the number of online processors in case
// some cores are disabled.
const int loadx100_per_processor = loadx100 / sysconf(_SC_NPROCESSORS_ONLN);
load_averages_after_arc_start_[index] = loadx100_per_processor;
MaybeRecordLoadAveragePerProcessor();
}
void ArcMetricsService::MaybeRecordLoadAveragePerProcessor() {
if (boot_type_ == mojom::BootType::UNKNOWN) {
// Not ready because the boot type is still unknown.
return;
}
for (const auto& value : load_averages_after_arc_start_) {
const size_t index = value.first;
const int loadx100_per_processor = value.second;
DCHECK_LT(index, std::size(kLoadAverageHistograms));
base::UmaHistogramCustomCounts(
kLoadAverageHistograms[index].name + BootTypeToString(boot_type_),
loadx100_per_processor, 0, 5000, 50);
}
// Erase the values to avoid recording them again.
load_averages_after_arc_start_.clear();
}
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_) {
DCHECK(arc_metrics_service_->histogram_namer_cb_);
LogStabilityUmaEnum(arc_metrics_service_->histogram_namer_cb_.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_) {
DCHECK(arc_metrics_service_->histogram_namer_cb_);
LogStabilityUmaEnum(arc_metrics_service_->histogram_namer_cb_.Run(
"Arc.Session.MojoDisconnection"),
MojoConnectionType::APP_LAUNCHER);
}
}
} // namespace arc