Index: services/resource_coordinator/coordination_unit/tab_cpu_usage_observer.cc |
diff --git a/services/resource_coordinator/coordination_unit/tab_cpu_usage_observer.cc b/services/resource_coordinator/coordination_unit/tab_cpu_usage_observer.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..2659b8e3c7c3f70234bc5129545202401cb63a23 |
--- /dev/null |
+++ b/services/resource_coordinator/coordination_unit/tab_cpu_usage_observer.cc |
@@ -0,0 +1,250 @@ |
+// 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/coordination_unit/tab_cpu_usage_observer.h" |
+ |
+#include <stdint.h> |
+ |
+#include <ostream> |
+#include <utility> |
+ |
+#include "base/strings/string_number_conversions.h" |
+#include "base/values.h" |
+#include "services/resource_coordinator/coordination_unit/coordination_unit_impl.h" |
+#include "services/resource_coordinator/coordination_unit/coordination_unit_manager.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/interfaces/coordination_unit.mojom.h" |
+ |
+namespace resource_coordinator { |
+ |
+namespace { |
+ |
+const char* kCPUUsageMetricsName = "TabCPUUsage"; |
+const char* kCPUProfilingIntervalInSecondsEntryName = |
+ "CPUProfilingIntervalInSeconds"; |
+const char* kMaxCPUUsageMeasurementTicksEntryName = |
+ "MaxCPUUsageMeasurementTicks"; |
+ |
+} // namespace |
+ |
+const size_t TabObserver::kCPUUsageMeasurementTimeInSeconds = 30u; |
+const size_t TabObserver::kMaxCPUUsageMeasurementTicks = |
+ TabObserver::kCPUUsageMeasurementTimeInSeconds / |
+ ProcessCoordinationUnitImpl::kCPUProfilingIntervalInSeconds; |
+ |
+TabObserver::TabObserver() : cpu_measurement_ticks_(0u) {} |
+ |
+TabObserver::~TabObserver() = default; |
+ |
+void TabObserver::AttributeCPUUsage(double cpu_usage) { |
+ DCHECK_NE(nullptr, ukm_entry_builder_.get()); |
+ |
+ // Each |cpu_usage| measurement is assigned a metric name |
+ // that corresponds to the |cpu_measurement_ticks_| value |
+ // it was observed at. |
+ ukm_entry_builder_->AddMetric( |
+ base::Uint64ToString(cpu_measurement_ticks_++).c_str(), |
+ static_cast<int>(cpu_usage)); |
+} |
+ |
+bool TabObserver::IsObserving() { |
+ return source_id_ && ukm_entry_builder_.get() != nullptr && |
+ cpu_measurement_ticks_ < kMaxCPUUsageMeasurementTicks; |
+} |
+ |
+void TabObserver::RestartObserving() { |
+ cpu_measurement_ticks_ = 0u; |
+ |
+ ukm_entry_builder_ = |
+ tab_cpu_usage_observer_->coordination_unit_manager()->GetUkmEntryBuilder( |
+ source_id_, kCPUUsageMetricsName); |
+ ukm_entry_builder_->AddMetric( |
+ kCPUProfilingIntervalInSecondsEntryName, |
+ ProcessCoordinationUnitImpl::kCPUProfilingIntervalInSeconds); |
+ ukm_entry_builder_->AddMetric(kMaxCPUUsageMeasurementTicksEntryName, |
+ kMaxCPUUsageMeasurementTicks); |
+} |
+ |
+void TabObserver::UkmSourceIdChanged(ukm::SourceId source_id) { |
+ source_id_ = source_id; |
+ |
+ // Receiving a new ukm::SourceId implies the tab has navigated to |
+ // a new page thus initiating a new observation period for the tab. |
+ RestartObserving(); |
+} |
+ |
+ProcessObserver::ProcessObserver() = default; |
+ |
+ProcessObserver::~ProcessObserver() = default; |
+ |
+void ProcessObserver::RecalculateAttributableTabs( |
+ const CoordinationUnitImpl* process_coordination_unit, |
+ const CoordinationUnitImpl* frame_coordination_unit) { |
+ attributable_tabs_.clear(); |
+ |
+ DCHECK(tab_cpu_usage_observer_); |
+ std::unordered_map<CoordinationUnitID, TabObserver>& |
+ registered_tab_observers = tab_cpu_usage_observer_->tab_observers(); |
+ // Any WebContents CoordinationUnit that is a parent of any of the |
+ // Process's children that are FrameCoordinationUnits is considered to |
+ // to be attributable for the Process's measured CPU usage. |
+ for (auto* child : process_coordination_unit->children()) { |
+ if (child->id().type == CoordinationUnitType::kFrame) { |
+ for (auto* frame_parent : child->parents()) { |
+ if (frame_parent->id().type == CoordinationUnitType::kWebContents) { |
+ auto tab_observer_it = |
+ registered_tab_observers.find(frame_parent->id()); |
+ DCHECK(tab_observer_it != registered_tab_observers.end()); |
+ |
+ // It is possible that another frame within the tab has inserted the |
+ // tab CoordinationUnit so checking insertion success is unecessary. |
+ attributable_tabs_.insert( |
+ std::make_pair(frame_parent->id(), &tab_observer_it->second)); |
+ } |
+ } |
+ } |
+ } |
+} |
+ |
+void ProcessObserver::CPUUsageUpdated(double cpu_usage) { |
+ if (attributable_tabs_.empty()) { |
+ return; |
+ } |
+ |
+ // CPU attribution for multiple tabs within a process is currently naive in |
+ // that the measured process CPU utilization is attributed equally. |
+ double tab_cpu_usage = |
+ cpu_usage / static_cast<double>(attributable_tabs_.size()); |
+ |
+ for (auto& tab_observer : attributable_tabs_) { |
+ if (tab_observer.second->IsObserving()) { |
+ tab_observer.second->AttributeCPUUsage(tab_cpu_usage); |
+ } |
+ } |
+} |
+ |
+TabCPUUsageObserver::TabCPUUsageObserver() = default; |
+ |
+TabCPUUsageObserver::~TabCPUUsageObserver() = default; |
+ |
+bool TabCPUUsageObserver::ShouldObserve( |
+ const CoordinationUnitImpl* coordination_unit) { |
+ // This observer is only interested in tracking render processes and tabs. |
+ CoordinationUnitType cu_type = coordination_unit->id().type; |
+ return cu_type == CoordinationUnitType::kProcess || |
+ cu_type == CoordinationUnitType::kWebContents; |
+} |
+ |
+void TabCPUUsageObserver::TabCoordinationUnitCreated( |
+ const CoordinationUnitImpl* tab_coordination_unit) { |
+ const CoordinationUnitID& tab_cu_id = tab_coordination_unit->id(); |
+ DCHECK_EQ(0u, tab_observers_.count(tab_cu_id)); |
+ auto& tab_observer = tab_observers_[tab_cu_id]; |
+ tab_observer.SetTabCPUUsageObserver(this); |
+} |
+ |
+void TabCPUUsageObserver::ProcessCoordinationUnitCreated( |
+ const CoordinationUnitImpl* process_coordination_unit) { |
+ const CoordinationUnitID& process_cu_id = process_coordination_unit->id(); |
+ DCHECK_EQ(0u, process_observers_.count(process_cu_id)); |
+ ProcessObserver& process_observer = process_observers_[process_cu_id]; |
+ process_observer.SetTabCPUUsageObserver(this); |
+} |
+ |
+void TabCPUUsageObserver::OnCoordinationUnitCreated( |
+ const CoordinationUnitImpl* coordination_unit) { |
+ if (coordination_unit->id().type == CoordinationUnitType::kWebContents) { |
+ TabCoordinationUnitCreated(coordination_unit); |
+ } else if (coordination_unit->id().type == CoordinationUnitType::kProcess) { |
+ ProcessCoordinationUnitCreated(coordination_unit); |
+ } |
+} |
+ |
+void TabCPUUsageObserver::TabUkmSourceIdChanged( |
+ const CoordinationUnitImpl* tab_coordination_unit) { |
+ const CoordinationUnitID& tab_cu_id = tab_coordination_unit->id(); |
+ DCHECK_EQ(1u, tab_observers_.count(tab_cu_id)); |
+ TabObserver& tab_observer = tab_observers_[tab_cu_id]; |
+ ukm::SourceId source_id; |
+ base::StringToInt64( |
+ tab_coordination_unit->GetProperty(mojom::PropertyType::kTabUkmSourceId) |
+ .GetString(), |
+ &source_id); |
+ tab_observer.UkmSourceIdChanged(source_id); |
+} |
+ |
+void TabCPUUsageObserver::ProcessCPUUsageUpdated( |
+ const CoordinationUnitImpl* process_coordination_unit) { |
+ const CoordinationUnitID& process_cu_id = process_coordination_unit->id(); |
+ DCHECK_EQ(1u, process_observers_.count(process_cu_id)); |
+ ProcessObserver& process_observer = process_observers_[process_cu_id]; |
+ process_observer.CPUUsageUpdated( |
+ process_coordination_unit |
+ ->GetProperty(mojom::PropertyType::kProcessCPUUsage) |
+ .GetDouble()); |
+} |
+ |
+void TabCPUUsageObserver::OnPropertyChanged( |
+ const CoordinationUnitImpl* coordination_unit, |
+ mojom::PropertyType property) { |
+ const CoordinationUnitID& cu_id = coordination_unit->id(); |
+ if (cu_id.type == CoordinationUnitType::kWebContents && |
+ property == mojom::PropertyType::kTabUkmSourceId) { |
+ TabUkmSourceIdChanged(coordination_unit); |
+ } else if (cu_id.type == CoordinationUnitType::kProcess && |
+ property == mojom::PropertyType::kProcessCPUUsage) { |
+ ProcessCPUUsageUpdated(coordination_unit); |
+ } |
+} |
+ |
+void TabCPUUsageObserver::MaybeRecalculateAttributableTabsForProcess( |
+ const CoordinationUnitImpl* process_coordination_unit, |
+ const CoordinationUnitImpl* tab_coordination_unit) { |
+ const CoordinationUnitID& process_cu_id = process_coordination_unit->id(); |
+ if (process_cu_id.type == CoordinationUnitType::kProcess && |
+ tab_coordination_unit->id().type == CoordinationUnitType::kFrame) { |
+ DCHECK_EQ(1u, process_observers_.count(process_cu_id)); |
+ ProcessObserver& process_observer = process_observers_[process_cu_id]; |
+ process_observer.RecalculateAttributableTabs(process_coordination_unit, |
+ tab_coordination_unit); |
+ } |
+} |
+ |
+void TabCPUUsageObserver::OnChildAdded( |
+ const CoordinationUnitImpl* coordination_unit, |
+ const CoordinationUnitImpl* child_coordination_unit) { |
+ MaybeRecalculateAttributableTabsForProcess(coordination_unit, |
+ child_coordination_unit); |
+} |
+ |
+void TabCPUUsageObserver::OnChildRemoved( |
+ const CoordinationUnitImpl* coordination_unit, |
+ const CoordinationUnitImpl* child_coordination_unit) { |
+ MaybeRecalculateAttributableTabsForProcess(coordination_unit, |
+ child_coordination_unit); |
+} |
+ |
+void TabCPUUsageObserver::RemoveProcessObserver( |
+ const CoordinationUnitImpl* process_coordination_unit) { |
+ DCHECK_EQ(1u, process_observers_.count(process_coordination_unit->id())); |
+ process_observers_.erase(process_coordination_unit->id()); |
+} |
+ |
+void TabCPUUsageObserver::RemoveTabObserver( |
+ const CoordinationUnitImpl* tab_coordination_unit) { |
+ DCHECK_EQ(1u, tab_observers_.count(tab_coordination_unit->id())); |
+ tab_observers_.erase(tab_coordination_unit->id()); |
+} |
+ |
+void TabCPUUsageObserver::OnCoordinationUnitWillBeDestroyed( |
+ const CoordinationUnitImpl* coordination_unit) { |
+ if (coordination_unit->id().type == CoordinationUnitType::kWebContents) { |
+ RemoveTabObserver(coordination_unit); |
+ } else if (coordination_unit->id().type == CoordinationUnitType::kProcess) { |
+ RemoveProcessObserver(coordination_unit); |
+ } |
+} |
+ |
+} // namespace resource_coordinator |