blob: 544bb2c7e141528487d4affda22d51d2e036a938 [file] [log] [blame]
// Copyright 2018 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 "chrome/browser/resource_coordinator/tab_memory_metrics_reporter.h"
#include <cstdint>
#include <memory>
#include "base/process/process.h"
#include "base/time/time.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/metrics/process_memory_metrics_emitter.h"
#include "chrome/browser/resource_coordinator/tab_load_tracker.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
namespace resource_coordinator {
// static
TabMemoryMetricsReporter* TabMemoryMetricsReporter::Get() {
static base::NoDestructor<TabMemoryMetricsReporter>
tab_memory_metrics_reporter;
return tab_memory_metrics_reporter.get();
}
TabMemoryMetricsReporter::TabMemoryMetricsReporter() = default;
TabMemoryMetricsReporter::~TabMemoryMetricsReporter() = default;
TabMemoryMetricsReporter::TabMemoryMetricsReporter(
const base::TickClock* tick_clock)
: update_timer_(tick_clock) {}
void TabMemoryMetricsReporter::StartReporting(TabLoadTracker* tracker) {
if (reporting_started_)
return;
tracker->AddObserver(this);
reporting_started_ = true;
}
void TabMemoryMetricsReporter::OnStartTracking(
content::WebContents* web_contents,
TabLoadTracker::LoadingState loading_state) {
if (loading_state != TabLoadTracker::LoadingState::LOADED)
return;
MonitorWebContents(web_contents);
}
void TabMemoryMetricsReporter::OnLoadingStateChange(
content::WebContents* web_contents,
TabLoadTracker::LoadingState old_loading_state,
LoadingState new_loading_state) {
if (new_loading_state != TabLoadTracker::LoadingState::LOADED)
return;
MonitorWebContents(web_contents);
}
void TabMemoryMetricsReporter::RemoveWebContentsDataFromMonitoredListIfExists(
content::WebContents* web_contents) {
for (auto it = monitored_contents_.begin(); it != monitored_contents_.end();
++it) {
if (it->web_contents == web_contents) {
monitored_contents_.erase(it);
break;
}
}
}
void TabMemoryMetricsReporter::MonitorWebContents(
content::WebContents* web_contents) {
base::TimeTicks current_time = NowTicks();
WebContentsData data;
data.page_loaded_time = current_time;
data.next_emit_time = data.page_loaded_time + base::TimeDelta::FromMinutes(1);
data.state = NO_METRICS_EMITTED;
data.web_contents = web_contents;
RemoveWebContentsDataFromMonitoredListIfExists(web_contents);
monitored_contents_.insert(data);
if (monitored_contents_.cbegin()->web_contents != web_contents)
return;
RestartTimerIfNeeded(current_time);
}
void TabMemoryMetricsReporter::OnStopTracking(
content::WebContents* web_contents,
TabLoadTracker::LoadingState loading_state) {
bool should_update_timer =
!monitored_contents_.empty() &&
monitored_contents_.cbegin()->web_contents == web_contents;
RemoveWebContentsDataFromMonitoredListIfExists(web_contents);
if (!should_update_timer)
return;
RestartTimerIfNeeded(NowTicks());
}
void TabMemoryMetricsReporter::UpdateTimerCallback() {
base::TimeTicks current_time = NowTicks();
// A list of renderers whose memory dumps were emitted.
std::forward_list<WebContentsData> renderers_memory_dumped;
// Extract all WebContentsData whose next_emit_time have expired,
// and emit metrics for them.
auto it = monitored_contents_.begin();
while (it != monitored_contents_.end() &&
it->next_emit_time <= current_time) {
if (EmitMemoryMetricsAfterPageLoaded(*it))
renderers_memory_dumped.push_front(*it);
it = monitored_contents_.erase(it);
}
// Advance the state of each item that just emitted metrics.
// If they aren't done, put them back into the monitored list
// with the time of their next event.
while (!renderers_memory_dumped.empty()) {
WebContentsData& data = renderers_memory_dumped.front();
data.state = NextStateOfEmitMemoryDumpAfterPageLoaded(
current_time - data.page_loaded_time);
if (data.state < EMITTED_ALL_METRICS) {
data.next_emit_time =
data.page_loaded_time + NextEmitTimeAfterPageLoaded(data.state);
DCHECK(data.next_emit_time > current_time);
monitored_contents_.insert(data);
}
renderers_memory_dumped.pop_front();
}
RestartTimerIfNeeded(current_time);
}
void TabMemoryMetricsReporter::RestartTimerIfNeeded(
base::TimeTicks current_time) {
update_timer_.Stop();
if (monitored_contents_.empty())
return;
base::TimeDelta timeout =
monitored_contents_.cbegin()->next_emit_time - current_time;
update_timer_.Start(FROM_HERE, timeout, this,
&TabMemoryMetricsReporter::UpdateTimerCallback);
}
bool TabMemoryMetricsReporter::EmitMemoryMetricsAfterPageLoaded(
const TabMemoryMetricsReporter::WebContentsData& content_data) {
content::RenderFrameHost* render_frame_host =
content_data.web_contents->GetMainFrame();
if (!render_frame_host)
return false;
const base::Process& process = render_frame_host->GetProcess()->GetProcess();
if (!process.IsValid())
return false;
// To record only this tab's process memory metrics, we will create
// ProcessMemoryMetricsEmitter with pid.
scoped_refptr<ProcessMemoryMetricsEmitter> emitter(
new ProcessMemoryMetricsEmitter(process.Pid()));
emitter->FetchAndEmitProcessMemoryMetrics();
return true;
}
base::TimeDelta TabMemoryMetricsReporter::NextEmitTimeAfterPageLoaded(
TabMemoryMetricsReporter::ReportState state) {
static constexpr base::TimeDelta next_emit_time_after_page_loaded[] = {
base::TimeDelta::FromMinutes(1), base::TimeDelta::FromMinutes(5),
base::TimeDelta::FromMinutes(10), base::TimeDelta::FromMinutes(15)};
DCHECK(NO_METRICS_EMITTED <= state && state < EMITTED_ALL_METRICS);
return next_emit_time_after_page_loaded[state];
}
TabMemoryMetricsReporter::ReportState
TabMemoryMetricsReporter::NextStateOfEmitMemoryDumpAfterPageLoaded(
base::TimeDelta time_passed) {
if (time_passed >= base::TimeDelta::FromMinutes(15))
return EMITTED_ALL_METRICS;
if (time_passed >= base::TimeDelta::FromMinutes(10))
return EMITTED_10MIN_METRIC;
if (time_passed >= base::TimeDelta::FromMinutes(5))
return EMITTED_5MIN_METRIC;
if (time_passed >= base::TimeDelta::FromMinutes(1))
return EMITTED_1MIN_METRIC;
return NO_METRICS_EMITTED;
}
bool TabMemoryMetricsReporter::WebContentsDataComparator::operator()(
const WebContentsData& a,
const WebContentsData& b) {
return a.next_emit_time < b.next_emit_time;
}
} // namespace resource_coordinator