blob: 9db60f286d8ac321bbac82e2dff20f55b9201f53 [file] [log] [blame]
// Copyright 2017 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 "services/resource_coordinator/observers/metrics_collector.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_macros.h"
#include "services/resource_coordinator/coordination_unit/coordination_unit_graph.h"
#include "services/resource_coordinator/coordination_unit/frame_coordination_unit_impl.h"
#include "services/resource_coordinator/coordination_unit/page_coordination_unit_impl.h"
#include "services/resource_coordinator/coordination_unit/process_coordination_unit_impl.h"
#include "services/resource_coordinator/public/cpp/coordination_unit_id.h"
#include "services/resource_coordinator/public/cpp/resource_coordinator_features.h"
#include "services/resource_coordinator/resource_coordinator_clock.h"
namespace resource_coordinator {
// Audio is considered to have started playing if the page has never
// previously played audio, or has been silent for at least one minute.
const base::TimeDelta kMaxAudioSlientTimeout = base::TimeDelta::FromMinutes(1);
// Delay the metrics report from GRC to UMA/UKM for 5 minutes from when the main
// frame navigation is committed.
const base::TimeDelta kMetricsReportDelayTimeout =
base::TimeDelta::FromMinutes(5);
const char kTabFromBackgroundedToFirstAlertFiredUMA[] =
"TabManager.Heuristics.FromBackgroundedToFirstAlertFired";
const char kTabFromBackgroundedToFirstAudioStartsUMA[] =
"TabManager.Heuristics.FromBackgroundedToFirstAudioStarts";
const char kTabFromBackgroundedToFirstFaviconUpdatedUMA[] =
"TabManager.Heuristics.FromBackgroundedToFirstFaviconUpdated";
const char kTabFromBackgroundedToFirstTitleUpdatedUMA[] =
"TabManager.Heuristics.FromBackgroundedToFirstTitleUpdated";
const char kTabFromBackgroundedToFirstNonPersistentNotificationCreatedUMA[] =
"TabManager.Heuristics."
"FromBackgroundedToFirstNonPersistentNotificationCreated";
const int kDefaultFrequencyUkmEQTReported = 5u;
// Gets the number of tabs that are co-resident in all of the render processes
// associated with a |CoordinationUnitType::kPage| coordination unit.
size_t GetNumCoresidentTabs(const PageCoordinationUnitImpl* page_cu) {
std::set<CoordinationUnitBase*> coresident_tabs;
for (auto* process_cu : page_cu->GetAssociatedProcessCoordinationUnits()) {
for (auto* associated_page_cu :
process_cu->GetAssociatedPageCoordinationUnits()) {
coresident_tabs.insert(associated_page_cu);
}
}
// A tab cannot be co-resident with itself.
return coresident_tabs.size() - 1;
}
MetricsCollector::MetricsCollector() {
UpdateWithFieldTrialParams();
}
MetricsCollector::~MetricsCollector() = default;
bool MetricsCollector::ShouldObserve(
const CoordinationUnitBase* coordination_unit) {
return coordination_unit->id().type == CoordinationUnitType::kFrame ||
coordination_unit->id().type == CoordinationUnitType::kPage ||
coordination_unit->id().type == CoordinationUnitType::kProcess;
}
void MetricsCollector::OnCoordinationUnitCreated(
const CoordinationUnitBase* coordination_unit) {
if (coordination_unit->id().type == CoordinationUnitType::kPage) {
metrics_report_record_map_.emplace(coordination_unit->id(),
MetricsReportRecord());
}
}
void MetricsCollector::OnBeforeCoordinationUnitDestroyed(
const CoordinationUnitBase* coordination_unit) {
if (coordination_unit->id().type == CoordinationUnitType::kPage) {
metrics_report_record_map_.erase(coordination_unit->id());
ukm_collection_state_map_.erase(coordination_unit->id());
}
}
void MetricsCollector::OnFramePropertyChanged(
const FrameCoordinationUnitImpl* frame_cu,
const mojom::PropertyType property_type,
int64_t value) {
if (property_type == mojom::PropertyType::kAudible) {
bool audible = static_cast<bool>(value);
if (!audible)
return;
auto* page_cu = frame_cu->GetPageCoordinationUnit();
// Only record metrics while it is backgrounded.
if (!page_cu || page_cu->IsVisible() || !ShouldReportMetrics(page_cu)) {
return;
}
// Audio is considered to have started playing if the page has never
// previously played audio, or has been silent for at least one minute.
auto now = ResourceCoordinatorClock::NowTicks();
if (frame_cu->last_audible_time() + kMaxAudioSlientTimeout < now) {
MetricsReportRecord& record =
metrics_report_record_map_.find(page_cu->id())->second;
record.first_audible.OnSignalReceived(
frame_cu->IsMainFrame(), page_cu->TimeSinceLastVisibilityChange(),
coordination_unit_graph().ukm_recorder());
}
}
}
void MetricsCollector::OnPagePropertyChanged(
const PageCoordinationUnitImpl* page_cu,
const mojom::PropertyType property_type,
int64_t value) {
const auto page_cu_id = page_cu->id();
if (property_type == mojom::PropertyType::kVisible) {
if (value) {
// The page becomes visible again, clear all records in order to
// report metrics when page becomes invisible next time.
ResetMetricsReportRecord(page_cu_id);
return;
}
} else if (property_type == mojom::PropertyType::kUKMSourceId) {
ukm::SourceId ukm_source_id = value;
UpdateUkmSourceIdForPage(page_cu_id, ukm_source_id);
MetricsReportRecord& record =
metrics_report_record_map_.find(page_cu_id)->second;
record.UpdateUKMSourceID(ukm_source_id);
}
}
void MetricsCollector::OnProcessPropertyChanged(
const ProcessCoordinationUnitImpl* process_cu,
const mojom::PropertyType property_type,
int64_t value) {
if (property_type == mojom::PropertyType::kExpectedTaskQueueingDuration) {
for (auto* page_cu : process_cu->GetAssociatedPageCoordinationUnits()) {
if (IsCollectingExpectedQueueingTimeForUkm(page_cu->id())) {
int64_t expected_queueing_time;
if (!page_cu->GetExpectedTaskQueueingDuration(&expected_queueing_time))
continue;
RecordExpectedQueueingTimeForUkm(page_cu->id(), expected_queueing_time);
}
}
}
}
void MetricsCollector::OnFrameEventReceived(
const FrameCoordinationUnitImpl* frame_cu,
const mojom::Event event) {
if (event == mojom::Event::kAlertFired) {
auto* page_cu = frame_cu->GetPageCoordinationUnit();
// Only record metrics while it is backgrounded.
if (!page_cu || page_cu->IsVisible() || !ShouldReportMetrics(page_cu)) {
return;
}
MetricsReportRecord& record =
metrics_report_record_map_.find(page_cu->id())->second;
record.first_alert_fired.OnSignalReceived(
frame_cu->IsMainFrame(), page_cu->TimeSinceLastVisibilityChange(),
coordination_unit_graph().ukm_recorder());
} else if (event == mojom::Event::kNonPersistentNotificationCreated) {
auto* page_cu = frame_cu->GetPageCoordinationUnit();
// Only record metrics while it is backgrounded.
if (!page_cu || page_cu->IsVisible() || !ShouldReportMetrics(page_cu)) {
return;
}
MetricsReportRecord& record =
metrics_report_record_map_.find(page_cu->id())->second;
record.first_non_persistent_notification_created.OnSignalReceived(
frame_cu->IsMainFrame(), page_cu->TimeSinceLastVisibilityChange(),
coordination_unit_graph().ukm_recorder());
}
}
void MetricsCollector::OnPageEventReceived(
const PageCoordinationUnitImpl* page_cu,
const mojom::Event event) {
if (event == mojom::Event::kTitleUpdated) {
// Only record metrics while it is backgrounded.
if (page_cu->IsVisible() || !ShouldReportMetrics(page_cu))
return;
MetricsReportRecord& record =
metrics_report_record_map_.find(page_cu->id())->second;
record.first_title_updated.OnSignalReceived(
true, page_cu->TimeSinceLastVisibilityChange(),
coordination_unit_graph().ukm_recorder());
} else if (event == mojom::Event::kFaviconUpdated) {
// Only record metrics while it is backgrounded.
if (page_cu->IsVisible() || !ShouldReportMetrics(page_cu))
return;
MetricsReportRecord& record =
metrics_report_record_map_.find(page_cu->id())->second;
record.first_favicon_updated.OnSignalReceived(
true, page_cu->TimeSinceLastVisibilityChange(),
coordination_unit_graph().ukm_recorder());
}
}
bool MetricsCollector::ShouldReportMetrics(
const PageCoordinationUnitImpl* page_cu) {
return page_cu->TimeSinceLastNavigation() > kMetricsReportDelayTimeout;
}
bool MetricsCollector::IsCollectingExpectedQueueingTimeForUkm(
const CoordinationUnitID& page_cu_id) {
UkmCollectionState& state = ukm_collection_state_map_[page_cu_id];
return state.ukm_source_id != ukm::kInvalidSourceId &&
++state.num_unreported_eqt_measurements >= frequency_ukm_eqt_reported_;
}
void MetricsCollector::RecordExpectedQueueingTimeForUkm(
const CoordinationUnitID& page_cu_id,
int64_t expected_queueing_time) {
UkmCollectionState& state = ukm_collection_state_map_[page_cu_id];
state.num_unreported_eqt_measurements = 0u;
ukm::builders::ResponsivenessMeasurement(state.ukm_source_id)
.SetExpectedTaskQueueingDuration(expected_queueing_time)
.Record(coordination_unit_graph().ukm_recorder());
}
void MetricsCollector::UpdateUkmSourceIdForPage(
const CoordinationUnitID& page_cu_id,
ukm::SourceId ukm_source_id) {
UkmCollectionState& state = ukm_collection_state_map_[page_cu_id];
state.ukm_source_id = ukm_source_id;
// Updating the |ukm_source_id| restarts usage collection.
state.num_unreported_eqt_measurements = 0u;
}
void MetricsCollector::UpdateWithFieldTrialParams() {
frequency_ukm_eqt_reported_ = base::GetFieldTrialParamByFeatureAsInt(
ukm::kUkmFeature, "FrequencyUKMExpectedQueueingTime",
kDefaultFrequencyUkmEQTReported);
}
void MetricsCollector::ResetMetricsReportRecord(CoordinationUnitID cu_id) {
DCHECK(metrics_report_record_map_.find(cu_id) !=
metrics_report_record_map_.end());
metrics_report_record_map_.find(cu_id)->second.Reset();
}
MetricsCollector::MetricsReportRecord::MetricsReportRecord() = default;
MetricsCollector::MetricsReportRecord::MetricsReportRecord(
const MetricsReportRecord& other) = default;
void MetricsCollector::MetricsReportRecord::UpdateUKMSourceID(
int64_t ukm_source_id) {
first_alert_fired.SetUKMSourceID(ukm_source_id);
first_audible.SetUKMSourceID(ukm_source_id);
first_favicon_updated.SetUKMSourceID(ukm_source_id);
first_non_persistent_notification_created.SetUKMSourceID(ukm_source_id);
first_title_updated.SetUKMSourceID(ukm_source_id);
}
void MetricsCollector::MetricsReportRecord::Reset() {
first_alert_fired.Reset();
first_audible.Reset();
first_favicon_updated.Reset();
first_non_persistent_notification_created.Reset();
first_title_updated.Reset();
}
} // namespace resource_coordinator