| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "android_webview/browser/metrics/visibility_metrics_logger.h" |
| |
| #include "base/compiler_specific.h" |
| #include "base/containers/contains.h" |
| #include "base/metrics/histogram.h" |
| #include "base/metrics/histogram_base.h" |
| #include "base/time/time.h" |
| #include "base/trace_event/trace_event.h" |
| #include "base/trace_event/typed_macros.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/common/url_constants.h" |
| |
| using content::BrowserThread; |
| |
| namespace android_webview { |
| |
| namespace { |
| |
| const char* SchemeEnumToString(VisibilityMetricsLogger::Scheme scheme) { |
| switch (scheme) { |
| case VisibilityMetricsLogger::Scheme::kEmpty: |
| return "empty"; |
| case VisibilityMetricsLogger::Scheme::kUnknown: |
| return "unknown"; |
| case VisibilityMetricsLogger::Scheme::kHttp: |
| return url::kHttpScheme; |
| case VisibilityMetricsLogger::Scheme::kHttps: |
| return url::kHttpsScheme; |
| case VisibilityMetricsLogger::Scheme::kFile: |
| return url::kFileScheme; |
| case VisibilityMetricsLogger::Scheme::kFtp: |
| return url::kFtpScheme; |
| case VisibilityMetricsLogger::Scheme::kData: |
| return url::kDataScheme; |
| case VisibilityMetricsLogger::Scheme::kJavaScript: |
| return url::kJavaScriptScheme; |
| case VisibilityMetricsLogger::Scheme::kAbout: |
| return url::kAboutScheme; |
| case VisibilityMetricsLogger::Scheme::kChrome: |
| return content::kChromeUIScheme; |
| case VisibilityMetricsLogger::Scheme::kBlob: |
| return url::kBlobScheme; |
| case VisibilityMetricsLogger::Scheme::kContent: |
| return url::kContentScheme; |
| case VisibilityMetricsLogger::Scheme::kIntent: |
| return "intent"; |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| // Have bypassed the usual macros here because they do not support a |
| // means to increment counters by more than 1 per call. |
| base::HistogramBase* GetOrCreateHistogramForDurationTracking( |
| const std::string& name, |
| int max_value) { |
| return base::Histogram::FactoryGet( |
| name, 1, max_value + 1, max_value + 2, |
| base::HistogramBase::kUmaTargetedHistogramFlag); |
| } |
| |
| // base::Histogram::FactoryGet would internally convert to std::string anyway, |
| // this overload is for convenience. |
| base::HistogramBase* GetOrCreateHistogramForDurationTracking(const char* name, |
| int max_value) { |
| return GetOrCreateHistogramForDurationTracking(std::string(name), max_value); |
| } |
| |
| base::HistogramBase* GetGlobalVisibilityHistogram() { |
| static base::HistogramBase* histogram(GetOrCreateHistogramForDurationTracking( |
| "Android.WebView.Visibility.Global", |
| static_cast<int>(VisibilityMetricsLogger::Visibility::kMaxValue))); |
| return histogram; |
| } |
| |
| base::HistogramBase* GetPerWebViewVisibilityHistogram() { |
| static base::HistogramBase* histogram(GetOrCreateHistogramForDurationTracking( |
| "Android.WebView.Visibility.PerWebView", |
| static_cast<int>(VisibilityMetricsLogger::Visibility::kMaxValue))); |
| return histogram; |
| } |
| |
| void LogGlobalVisibleScheme(VisibilityMetricsLogger::Scheme scheme, |
| int32_t seconds) { |
| static base::HistogramBase* histogram(GetOrCreateHistogramForDurationTracking( |
| "Android.WebView.VisibleScheme.Global", |
| static_cast<int>(VisibilityMetricsLogger::Scheme::kMaxValue))); |
| histogram->AddCount(static_cast<int32_t>(scheme), seconds); |
| } |
| |
| void LogPerWebViewVisibleScheme(VisibilityMetricsLogger::Scheme scheme, |
| int32_t seconds) { |
| static base::HistogramBase* histogram(GetOrCreateHistogramForDurationTracking( |
| "Android.WebView.VisibleScheme.PerWebView", |
| static_cast<int>(VisibilityMetricsLogger::Scheme::kMaxValue))); |
| histogram->AddCount(static_cast<int32_t>(scheme), seconds); |
| } |
| |
| void LogGlobalVisibleScreenCoverage(int percentage, int32_t seconds) { |
| static base::HistogramBase* histogram(GetOrCreateHistogramForDurationTracking( |
| "Android.WebView.VisibleScreenCoverage.Global", 100)); |
| histogram->AddCount(percentage, seconds); |
| } |
| |
| void LogPerWebViewVisibleScreenCoverage(int percentage, int32_t seconds) { |
| static base::HistogramBase* histogram(GetOrCreateHistogramForDurationTracking( |
| "Android.WebView.VisibleScreenCoverage.PerWebView", 100)); |
| histogram->AddCount(percentage, seconds); |
| } |
| |
| void LogPerSchemeVisibleScreenCoverage(VisibilityMetricsLogger::Scheme scheme, |
| int percentage, |
| int32_t seconds) { |
| GetOrCreateHistogramForDurationTracking( |
| std::string("Android.WebView.VisibleScreenCoverage.PerWebView.") + |
| SchemeEnumToString(scheme), |
| 100) |
| ->AddCount(percentage, seconds); |
| } |
| |
| } // anonymous namespace |
| |
| // static |
| VisibilityMetricsLogger::Scheme VisibilityMetricsLogger::SchemeStringToEnum( |
| const std::string& scheme) { |
| if (scheme.empty()) |
| return Scheme::kEmpty; |
| if (scheme == url::kHttpScheme) |
| return Scheme::kHttp; |
| if (scheme == url::kHttpsScheme) |
| return Scheme::kHttps; |
| if (scheme == url::kFileScheme) |
| return Scheme::kFile; |
| if (scheme == url::kFtpScheme) |
| return Scheme::kFtp; |
| if (scheme == url::kDataScheme) |
| return Scheme::kData; |
| if (scheme == url::kJavaScriptScheme) |
| return Scheme::kJavaScript; |
| if (scheme == url::kAboutScheme) |
| return Scheme::kAbout; |
| if (scheme == content::kChromeUIScheme) |
| return Scheme::kChrome; |
| if (scheme == url::kBlobScheme) |
| return Scheme::kBlob; |
| if (scheme == url::kContentScheme) |
| return Scheme::kContent; |
| if (scheme == "intent") |
| return Scheme::kIntent; |
| return Scheme::kUnknown; |
| } |
| |
| VisibilityMetricsLogger::VisibilityMetricsLogger() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| last_update_time_ = base::TimeTicks::Now(); |
| } |
| |
| VisibilityMetricsLogger::~VisibilityMetricsLogger() = default; |
| |
| void VisibilityMetricsLogger::AddClient(Client* client) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DCHECK(!base::Contains(client_visibility_, client)); |
| |
| UpdateDurations(); |
| |
| client_visibility_[client] = VisibilityInfo(); |
| ProcessClientUpdate(client, client->GetVisibilityInfo(), |
| ClientAction::kAdded); |
| } |
| |
| void VisibilityMetricsLogger::RemoveClient(Client* client) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DCHECK(base::Contains(client_visibility_, client)); |
| |
| UpdateDurations(); |
| |
| ProcessClientUpdate(client, VisibilityInfo(), ClientAction::kRemoved); |
| client_visibility_.erase(client); |
| } |
| |
| void VisibilityMetricsLogger::ClientVisibilityChanged(Client* client) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DCHECK(base::Contains(client_visibility_, client)); |
| |
| UpdateDurations(); |
| |
| ProcessClientUpdate(client, client->GetVisibilityInfo(), |
| ClientAction::kVisibilityChanged); |
| } |
| |
| void VisibilityMetricsLogger::UpdateScreenCoverage( |
| int global_percentage, |
| const std::vector<Scheme>& schemes, |
| const std::vector<int>& scheme_percentages) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DCHECK(schemes.size() == scheme_percentages.size()); |
| |
| UpdateDurations(); |
| |
| DCHECK(global_percentage >= 0); |
| DCHECK(global_percentage <= 100); |
| global_coverage_percentage_ = global_percentage; |
| |
| schemes_to_coverage_percentages_.clear(); |
| for (size_t i = 0; i < schemes.size(); i++) { |
| DCHECK(scheme_percentages[i] >= 0); |
| DCHECK(scheme_percentages[i] <= 100); |
| schemes_to_coverage_percentages_.emplace(schemes[i], scheme_percentages[i]); |
| } |
| } |
| |
| void VisibilityMetricsLogger::UpdateDurations() { |
| base::TimeTicks update_time = base::TimeTicks::Now(); |
| base::TimeDelta delta = update_time - last_update_time_; |
| if (all_clients_visible_count_ > 0) { |
| all_clients_tracker_.any_webview_tracked_duration_ += delta; |
| } else { |
| all_clients_tracker_.no_webview_tracked_duration_ += delta; |
| } |
| all_clients_tracker_.per_webview_duration_ += |
| delta * all_clients_visible_count_; |
| all_clients_tracker_.per_webview_untracked_duration_ += |
| delta * (client_visibility_.size() - all_clients_visible_count_); |
| |
| for (size_t i = 0; i < std::size(per_scheme_visible_counts_); i++) { |
| if (!UNSAFE_TODO(per_scheme_visible_counts_[i])) { |
| continue; |
| } |
| UNSAFE_TODO(per_scheme_trackers_[i]).any_webview_tracked_duration_ += delta; |
| UNSAFE_TODO(per_scheme_trackers_[i]).per_webview_duration_ += |
| delta * UNSAFE_TODO(per_scheme_visible_counts_[i]); |
| } |
| |
| if (all_clients_visible_count_ > 0) { |
| UNSAFE_TODO( |
| global_coverage_percentage_durations_[global_coverage_percentage_]) += |
| delta; |
| |
| for (auto& scheme_and_percentage : schemes_to_coverage_percentages_) { |
| schemes_to_percentages_to_durations_[scheme_and_percentage.first] |
| [scheme_and_percentage.second] += |
| delta; |
| } |
| } |
| |
| last_update_time_ = update_time; |
| } |
| |
| bool VisibilityMetricsLogger::VisibilityInfo::IsVisible() const { |
| return view_attached && view_visible && window_visible; |
| } |
| |
| void VisibilityMetricsLogger::ProcessClientUpdate(Client* client, |
| const VisibilityInfo& info, |
| ClientAction action) { |
| VisibilityInfo curr_info = client_visibility_[client]; |
| bool was_visible = curr_info.IsVisible(); |
| bool is_visible = info.IsVisible(); |
| Scheme old_scheme = curr_info.scheme; |
| Scheme new_scheme = info.scheme; |
| client_visibility_[client] = info; |
| DCHECK(!was_visible || all_clients_visible_count_ > 0); |
| |
| bool any_client_was_visible = all_clients_visible_count_ > 0; |
| |
| if (action == ClientAction::kAdded) { |
| // Only emit the event if the WebView is visible so that the track gets the |
| // appropriate name. |
| // TODO(b/280334022): set the track name explicitly after the Perfetto SDK |
| // migration is finished (crbug/1006541). |
| if (is_visible) { |
| TRACE_EVENT_BEGIN("android_webview.timeline", "WebViewVisible", |
| perfetto::Track::FromPointer(client)); |
| } |
| } |
| |
| // If visibility changes or the client is removed, close the event |
| // corresponding to the previous visibility state. |
| if (action == ClientAction::kRemoved || was_visible != is_visible) { |
| TRACE_EVENT_END("android_webview.timeline", |
| perfetto::Track::FromPointer(client)); |
| } |
| |
| if (!was_visible && is_visible) { |
| if (action != ClientAction::kRemoved) { |
| TRACE_EVENT_BEGIN("android_webview.timeline", "WebViewVisible", |
| perfetto::Track::FromPointer(client)); |
| } |
| ++all_clients_visible_count_; |
| } else if (was_visible && !is_visible) { |
| if (action != ClientAction::kRemoved) { |
| TRACE_EVENT_BEGIN("android_webview.timeline", "WebViewInvisible", |
| perfetto::Track::FromPointer(client)); |
| } |
| --all_clients_visible_count_; |
| } |
| |
| if (was_visible) |
| UNSAFE_TODO(per_scheme_visible_counts_[static_cast<size_t>(old_scheme)])--; |
| if (is_visible) |
| UNSAFE_TODO(per_scheme_visible_counts_[static_cast<size_t>(new_scheme)])++; |
| |
| bool any_client_is_visible = all_clients_visible_count_ > 0; |
| if (on_visibility_changed_callback_ && |
| any_client_was_visible != any_client_is_visible) { |
| on_visibility_changed_callback_.Run(any_client_is_visible); |
| } |
| } |
| |
| void VisibilityMetricsLogger::SetOnVisibilityChangedCallback( |
| OnVisibilityChangedCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| on_visibility_changed_callback_ = std::move(callback); |
| } |
| |
| void VisibilityMetricsLogger::RecordMetrics() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| UpdateDurations(); |
| RecordVisibilityMetrics(); |
| RecordVisibleSchemeMetrics(); |
| RecordScreenCoverageMetrics(); |
| } |
| |
| void VisibilityMetricsLogger::RecordVisibilityMetrics() { |
| int32_t any_webview_visible_seconds; |
| int32_t no_webview_visible_seconds; |
| int32_t total_webview_visible_seconds; |
| int32_t total_no_webview_visible_seconds; |
| |
| any_webview_visible_seconds = |
| all_clients_tracker_.any_webview_tracked_duration_.InSeconds(); |
| all_clients_tracker_.any_webview_tracked_duration_ -= |
| base::Seconds(any_webview_visible_seconds); |
| no_webview_visible_seconds = |
| all_clients_tracker_.no_webview_tracked_duration_.InSeconds(); |
| all_clients_tracker_.no_webview_tracked_duration_ -= |
| base::Seconds(no_webview_visible_seconds); |
| |
| total_webview_visible_seconds = |
| all_clients_tracker_.per_webview_duration_.InSeconds(); |
| all_clients_tracker_.per_webview_duration_ -= |
| base::Seconds(total_webview_visible_seconds); |
| total_no_webview_visible_seconds = |
| all_clients_tracker_.per_webview_untracked_duration_.InSeconds(); |
| all_clients_tracker_.per_webview_untracked_duration_ -= |
| base::Seconds(total_no_webview_visible_seconds); |
| |
| if (any_webview_visible_seconds) { |
| GetGlobalVisibilityHistogram()->AddCount( |
| static_cast<int>(Visibility::kVisible), any_webview_visible_seconds); |
| } |
| if (no_webview_visible_seconds) { |
| GetGlobalVisibilityHistogram()->AddCount( |
| static_cast<int>(Visibility::kNotVisible), no_webview_visible_seconds); |
| } |
| |
| if (total_webview_visible_seconds) { |
| GetPerWebViewVisibilityHistogram()->AddCount( |
| static_cast<int>(Visibility::kVisible), total_webview_visible_seconds); |
| } |
| if (total_no_webview_visible_seconds) { |
| GetPerWebViewVisibilityHistogram()->AddCount( |
| static_cast<int>(Visibility::kNotVisible), |
| total_no_webview_visible_seconds); |
| } |
| } |
| |
| void VisibilityMetricsLogger::RecordVisibleSchemeMetrics() { |
| for (size_t i = 0; i < std::size(per_scheme_trackers_); i++) { |
| Scheme scheme = static_cast<Scheme>(i); |
| auto& tracker = UNSAFE_TODO(per_scheme_trackers_[i]); |
| |
| int32_t any_webview_seconds = |
| tracker.any_webview_tracked_duration_.InSeconds(); |
| if (any_webview_seconds) { |
| tracker.any_webview_tracked_duration_ -= |
| base::Seconds(any_webview_seconds); |
| LogGlobalVisibleScheme(scheme, any_webview_seconds); |
| } |
| |
| int32_t per_webview_seconds = tracker.per_webview_duration_.InSeconds(); |
| if (per_webview_seconds) { |
| tracker.per_webview_duration_ -= base::Seconds(per_webview_seconds); |
| LogPerWebViewVisibleScheme(scheme, per_webview_seconds); |
| } |
| } |
| } |
| |
| void VisibilityMetricsLogger::RecordScreenCoverageMetrics() { |
| for (size_t i = 0; i < std::size(global_coverage_percentage_durations_); |
| i++) { |
| int32_t seconds = |
| UNSAFE_TODO(global_coverage_percentage_durations_[i]).InSeconds(); |
| if (seconds == 0) |
| continue; |
| |
| UNSAFE_TODO(global_coverage_percentage_durations_[i]) -= |
| base::Seconds(seconds); |
| LogGlobalVisibleScreenCoverage(i, seconds); |
| } |
| |
| for (auto& scheme_and_map : schemes_to_percentages_to_durations_) { |
| for (auto& percentage_and_duration : scheme_and_map.second) { |
| int32_t seconds = percentage_and_duration.second.InSeconds(); |
| if (seconds == 0) |
| continue; |
| |
| percentage_and_duration.second -= base::Seconds(seconds); |
| LogPerWebViewVisibleScreenCoverage(percentage_and_duration.first, |
| seconds); |
| LogPerSchemeVisibleScreenCoverage(scheme_and_map.first, |
| percentage_and_duration.first, seconds); |
| } |
| } |
| } |
| |
| } // namespace android_webview |