| // Copyright 2022 The Chromium OS 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 "feature_usage/feature_usage_metrics.h" |
| |
| #include <base/bind.h> |
| #include <base/callback.h> |
| #include <base/location.h> |
| #include <base/logging.h> |
| #include <base/power_monitor/power_monitor.h> |
| #include <base/time/clock.h> |
| #include <base/time/default_clock.h> |
| #include <base/time/default_tick_clock.h> |
| #include <base/time/tick_clock.h> |
| #include <base/time/time.h> |
| #include <base/timer/timer.h> |
| |
| namespace feature_usage { |
| |
| namespace { |
| |
| constexpr char kFeatureUsageMetricPrefix[] = "ChromeOS.FeatureUsage."; |
| constexpr char kFeatureUsetimeMetricPostfix[] = ".Usetime"; |
| |
| std::string FeatureToHistogram(const std::string& feature_name) { |
| return kFeatureUsageMetricPrefix + feature_name; |
| } |
| |
| } // namespace |
| |
| std::optional<bool> FeatureUsageMetrics::Delegate::IsAccessible() const { |
| return std::nullopt; |
| } |
| |
| // First time periodic metrics are reported after 'kInitialInterval` time. |
| constexpr base::TimeDelta FeatureUsageMetrics::kInitialInterval = |
| base::Minutes(1); |
| |
| // Consecutive reports run every `kRepeatedInterval` |
| constexpr base::TimeDelta FeatureUsageMetrics::kRepeatedInterval = |
| base::Minutes(30); |
| |
| FeatureUsageMetrics::FeatureUsageMetrics(const std::string& feature_name, |
| Delegate* const delegate) |
| : FeatureUsageMetrics(feature_name, |
| delegate, |
| base::DefaultClock::GetInstance(), |
| base::DefaultTickClock::GetInstance()) {} |
| |
| FeatureUsageMetrics::FeatureUsageMetrics(const std::string& feature_name, |
| Delegate* const delegate, |
| const base::Clock* clock, |
| const base::TickClock* tick_clock) |
| : histogram_name_(FeatureToHistogram(feature_name)), |
| delegate_(delegate), |
| clock_(clock), |
| timer_(tick_clock), |
| metrics_lib_(std::make_unique<MetricsLibrary>()) { |
| DCHECK(delegate_); |
| |
| // Schedule the first run some time in the future to not overload startup |
| // flow. |
| SetupTimer(kInitialInterval); |
| base::PowerMonitor::AddPowerSuspendObserver(this); |
| } |
| |
| void FeatureUsageMetrics::SetMetricsLibraryForTesting( |
| std::unique_ptr<MetricsLibraryInterface> metrics_lib) { |
| metrics_lib_ = std::move(metrics_lib); |
| } |
| |
| void FeatureUsageMetrics::SetupTimer(base::TimeDelta delta) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| timer_.Start(FROM_HERE, delta, |
| base::BindOnce(&FeatureUsageMetrics::MaybeReportPeriodicMetrics, |
| base::Unretained(this))); |
| } |
| |
| FeatureUsageMetrics::~FeatureUsageMetrics() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| base::PowerMonitor::RemovePowerSuspendObserver(this); |
| MaybeReportUseTime(); |
| } |
| |
| void FeatureUsageMetrics::RecordUsage(bool success) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(delegate_->IsEligible()); |
| DCHECK(delegate_->IsEnabled()); |
| DCHECK(start_usage_.is_null()); |
| #if DCHECK_IS_ON() |
| last_record_usage_outcome_ = success; |
| #endif |
| MaybeReportPeriodicMetrics(); |
| |
| Event e = success ? Event::kUsedWithSuccess : Event::kUsedWithFailure; |
| metrics_lib_->SendEnumToUMA(histogram_name_, e); |
| } |
| |
| void FeatureUsageMetrics::RecordUsetime(base::TimeDelta usetime) const { |
| metrics_lib_->SendToUMA(histogram_name_ + kFeatureUsetimeMetricPostfix, |
| usetime.InMilliseconds(), |
| base::Milliseconds(1).InMilliseconds(), |
| base::Hours(1).InMilliseconds(), 100); |
| } |
| |
| void FeatureUsageMetrics::StartSuccessfulUsage() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(start_usage_.is_null()); |
| #if DCHECK_IS_ON() |
| DCHECK(last_record_usage_outcome_.value_or(false)) |
| << "Start usage must be preceded by RecordUsage(true)"; |
| last_record_usage_outcome_.reset(); |
| #endif |
| start_usage_ = Now(); |
| } |
| |
| void FeatureUsageMetrics::StopSuccessfulUsage() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(!start_usage_.is_null()); |
| #if DCHECK_IS_ON() |
| DCHECK(!last_record_usage_outcome_.has_value()) |
| << "There must be no RecordUsage calls between Start and " |
| "StopSuccessfulUsage"; |
| #endif |
| MaybeReportUseTime(); |
| start_usage_ = base::Time(); |
| } |
| |
| void FeatureUsageMetrics::OnSuspend() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| timer_.AbandonAndStop(); |
| ReportPeriodicMetrics(); |
| } |
| |
| void FeatureUsageMetrics::OnResume() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (!start_usage_.is_null()) { |
| // Do not report usage during suspension. |
| start_usage_ = Now(); |
| } |
| SetupTimer(kInitialInterval); |
| } |
| |
| void FeatureUsageMetrics::MaybeReportPeriodicMetrics() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (!last_time_enabled_reported_.is_null()) { |
| const base::TimeDelta delta_since_enabled_reported = |
| Now() - last_time_enabled_reported_; |
| // Do not report periodic metrics more often than once per |
| // `kRepeatedInterval`. |
| if (kRepeatedInterval > delta_since_enabled_reported) { |
| // This could only happen when `RecordUsage` is called. In that case |
| // `IsEnabled` must be true. And because `last_time_enabled_reported_` is |
| // not null - `IsEnabled` was already reported recently. |
| SetupTimer(kRepeatedInterval - delta_since_enabled_reported); |
| return; |
| } |
| } |
| ReportPeriodicMetrics(); |
| } |
| |
| void FeatureUsageMetrics::ReportPeriodicMetrics() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| bool is_eligible = delegate_->IsEligible(); |
| bool is_enabled = delegate_->IsEnabled(); |
| DCHECK(!is_enabled || is_eligible); |
| |
| if (is_eligible) { |
| metrics_lib_->SendEnumToUMA(histogram_name_, Event::kEligible); |
| } |
| |
| std::optional<bool> is_accessible = delegate_->IsAccessible(); |
| |
| if (is_accessible.has_value()) { |
| // If accessible must be eligible. |
| DCHECK(!is_accessible.value() || is_eligible); |
| |
| // If enabled must be accessible |
| DCHECK(!is_enabled || is_accessible.value()); |
| |
| if (is_accessible.value()) { |
| metrics_lib_->SendEnumToUMA(histogram_name_, Event::kAccessible); |
| } |
| } |
| |
| if (is_enabled) { |
| last_time_enabled_reported_ = Now(); |
| metrics_lib_->SendEnumToUMA(histogram_name_, Event::kEnabled); |
| } |
| |
| // If the feature in the active use - it must be enabled. |
| DCHECK(start_usage_.is_null() || is_enabled); |
| MaybeReportUseTime(); |
| |
| SetupTimer(kRepeatedInterval); |
| } |
| |
| void FeatureUsageMetrics::MaybeReportUseTime() { |
| if (start_usage_.is_null()) |
| return; |
| base::TimeDelta use_time = Now() - start_usage_; |
| RecordUsetime(use_time); |
| start_usage_ = Now(); |
| } |
| |
| base::Time FeatureUsageMetrics::Now() const { |
| return clock_->Now(); |
| } |
| |
| } // namespace feature_usage |