blob: 40ebca757b3c685650d406f05a6598100ccd99c6 [file] [log] [blame]
// Copyright 2016 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 "third_party/blink/renderer/core/frame/performance_monitor.h"
#include "base/format_macros.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/bindings/core/v8/scheduled_action.h"
#include "third_party/blink/renderer/bindings/core/v8/script_event_listener.h"
#include "third_party/blink/renderer/bindings/core/v8/source_location.h"
#include "third_party/blink/renderer/core/core_probe_sink.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/events/event_listener.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/frame/frame.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/html/parser/html_document_parser.h"
#include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/platform/histogram.h"
#include "third_party/blink/renderer/platform/wtf/time.h"
namespace blink {
namespace {
constexpr auto kLongTaskSubTaskThreshold = TimeDelta::FromMilliseconds(12);
} // namespace
void PerformanceMonitor::BypassLongCompileThresholdOnceForTesting() {
bypass_long_compile_threshold_ = true;
};
// static
base::TimeDelta PerformanceMonitor::Threshold(ExecutionContext* context,
Violation violation) {
PerformanceMonitor* monitor =
PerformanceMonitor::InstrumentingMonitor(context);
return monitor ? monitor->thresholds_[violation] : base::TimeDelta();
}
// static
void PerformanceMonitor::ReportGenericViolation(
ExecutionContext* context,
Violation violation,
const String& text,
base::TimeDelta time,
std::unique_ptr<SourceLocation> location) {
PerformanceMonitor* monitor =
PerformanceMonitor::InstrumentingMonitor(context);
if (!monitor)
return;
monitor->InnerReportGenericViolation(context, violation, text, time,
std::move(location));
}
// static
PerformanceMonitor* PerformanceMonitor::Monitor(
const ExecutionContext* context) {
const auto* document = DynamicTo<Document>(context);
if (!document)
return nullptr;
LocalFrame* frame = document->GetFrame();
if (!frame)
return nullptr;
return frame->GetPerformanceMonitor();
}
// static
PerformanceMonitor* PerformanceMonitor::InstrumentingMonitor(
const ExecutionContext* context) {
PerformanceMonitor* monitor = PerformanceMonitor::Monitor(context);
return monitor && monitor->enabled_ ? monitor : nullptr;
}
PerformanceMonitor::PerformanceMonitor(LocalFrame* local_root)
: local_root_(local_root) {
std::fill(std::begin(thresholds_), std::end(thresholds_), base::TimeDelta());
Thread::Current()->AddTaskTimeObserver(this);
local_root_->GetProbeSink()->addPerformanceMonitor(this);
}
PerformanceMonitor::~PerformanceMonitor() {
DCHECK(!local_root_);
}
void PerformanceMonitor::Subscribe(Violation violation,
base::TimeDelta threshold,
Client* client) {
DCHECK(violation < kAfterLast);
ClientThresholds* client_thresholds = subscriptions_.at(violation);
if (!client_thresholds) {
client_thresholds = MakeGarbageCollected<ClientThresholds>();
subscriptions_.Set(violation, client_thresholds);
}
client_thresholds->Set(client, threshold);
UpdateInstrumentation();
}
void PerformanceMonitor::UnsubscribeAll(Client* client) {
for (const auto& it : subscriptions_)
it.value->erase(client);
UpdateInstrumentation();
}
void PerformanceMonitor::Shutdown() {
if (!local_root_)
return;
subscriptions_.clear();
UpdateInstrumentation();
Thread::Current()->RemoveTaskTimeObserver(this);
local_root_->GetProbeSink()->removePerformanceMonitor(this);
local_root_ = nullptr;
}
void PerformanceMonitor::UpdateInstrumentation() {
std::fill(std::begin(thresholds_), std::end(thresholds_), base::TimeDelta());
for (const auto& it : subscriptions_) {
Violation violation = static_cast<Violation>(it.key);
ClientThresholds* client_thresholds = it.value;
for (const auto& client_threshold : *client_thresholds) {
if (thresholds_[violation].is_zero() ||
thresholds_[violation] > client_threshold.value)
thresholds_[violation] = client_threshold.value;
}
}
enabled_ = std::count(std::begin(thresholds_), std::end(thresholds_),
base::TimeDelta()) < static_cast<int>(kAfterLast);
}
void PerformanceMonitor::WillExecuteScript(ExecutionContext* context) {
// Heuristic for minimal frame context attribution: note the frame context
// for each script execution. When a long task is encountered,
// if there is only one frame context involved, then report it.
// Otherwise don't report frame context.
// NOTE: This heuristic is imperfect and will be improved in V2 API.
// In V2, timing of script execution along with style & layout updates will be
// accounted for detailed and more accurate attribution.
++script_depth_;
UpdateTaskAttribution(context);
}
void PerformanceMonitor::DidExecuteScript() {
--script_depth_;
}
void PerformanceMonitor::UpdateTaskAttribution(ExecutionContext* context) {
// If |context| is not a document, unable to attribute a frame context.
auto* document = DynamicTo<Document>(context);
if (!document)
return;
UpdateTaskShouldBeReported(document->GetFrame());
if (!task_execution_context_)
task_execution_context_ = context;
else if (task_execution_context_ != context)
task_has_multiple_contexts_ = true;
}
void PerformanceMonitor::UpdateTaskShouldBeReported(LocalFrame* frame) {
if (frame && local_root_ == &(frame->LocalFrameRoot()))
task_should_be_reported_ = true;
}
void PerformanceMonitor::Will(const probe::RecalculateStyle& probe) {
UpdateTaskShouldBeReported(probe.document ? probe.document->GetFrame()
: nullptr);
if (enabled_ && !thresholds_[kLongLayout].is_zero() && script_depth_)
probe.CaptureStartTime();
}
void PerformanceMonitor::Did(const probe::RecalculateStyle& probe) {
if (enabled_ && script_depth_ && !thresholds_[kLongLayout].is_zero())
per_task_style_and_layout_time_ += probe.Duration();
}
void PerformanceMonitor::Will(const probe::UpdateLayout& probe) {
UpdateTaskShouldBeReported(probe.document ? probe.document->GetFrame()
: nullptr);
++layout_depth_;
if (!enabled_)
return;
if (layout_depth_ > 1 || !script_depth_ || thresholds_[kLongLayout].is_zero())
return;
probe.CaptureStartTime();
}
void PerformanceMonitor::Did(const probe::UpdateLayout& probe) {
--layout_depth_;
if (!enabled_)
return;
if (!thresholds_[kLongLayout].is_zero() && script_depth_ && !layout_depth_)
per_task_style_and_layout_time_ += probe.Duration();
}
void PerformanceMonitor::Will(const probe::ExecuteScript& probe) {
WillExecuteScript(probe.context);
probe.CaptureStartTime();
}
void PerformanceMonitor::Did(const probe::ExecuteScript& probe) {
DidExecuteScript();
if (!enabled_ || thresholds_[kLongTask].is_zero())
return;
if (probe.Duration() <= kLongTaskSubTaskThreshold)
return;
std::unique_ptr<SubTaskAttribution> sub_task_attribution =
SubTaskAttribution::Create(AtomicString("script-run"),
probe.context->Url().GetString(),
probe.CaptureStartTime(), probe.Duration());
sub_task_attributions_.push_back(std::move(sub_task_attribution));
}
void PerformanceMonitor::Will(const probe::CallFunction& probe) {
WillExecuteScript(probe.context);
if (user_callback_)
probe.CaptureStartTime();
}
void PerformanceMonitor::Did(const probe::CallFunction& probe) {
DidExecuteScript();
if (!enabled_ || !user_callback_)
return;
// Working around Oilpan - probes are STACK_ALLOCATED.
const probe::UserCallback* user_callback =
static_cast<const probe::UserCallback*>(user_callback_);
Violation handler_type =
user_callback->recurring ? kRecurringHandler : kHandler;
base::TimeDelta threshold = thresholds_[handler_type];
base::TimeDelta duration = probe.Duration();
if (threshold.is_zero() || duration < threshold)
return;
String name = user_callback->name ? String(user_callback->name)
: String(user_callback->atomicName);
String text = String::Format("'%s' handler took %" PRId64 "ms",
name.Utf8().data(), duration.InMilliseconds());
InnerReportGenericViolation(probe.context, handler_type, text, duration,
SourceLocation::FromFunction(probe.function));
}
void PerformanceMonitor::Will(const probe::V8Compile& probe) {
UpdateTaskAttribution(probe.context);
if (!enabled_ || thresholds_[kLongTask].is_zero())
return;
v8_compile_start_time_ = probe.CaptureStartTime();
}
void PerformanceMonitor::Did(const probe::V8Compile& probe) {
if (!enabled_ || thresholds_[kLongTask].is_zero())
return;
TimeDelta v8_compile_duration = probe.Duration();
if (bypass_long_compile_threshold_) {
bypass_long_compile_threshold_ = false;
} else {
if (v8_compile_duration <= kLongTaskSubTaskThreshold)
return;
}
std::unique_ptr<SubTaskAttribution> sub_task_attribution =
SubTaskAttribution::Create(
AtomicString("script-compile"),
String::Format("%s(%d, %d)", probe.file_name.Utf8().data(),
probe.line, probe.column),
v8_compile_start_time_, v8_compile_duration);
sub_task_attributions_.push_back(std::move(sub_task_attribution));
}
void PerformanceMonitor::Will(const probe::UserCallback& probe) {
++user_callback_depth_;
UpdateTaskAttribution(probe.context);
if (!enabled_ || user_callback_depth_ != 1 ||
thresholds_[probe.recurring ? kRecurringHandler : kHandler].is_zero())
return;
DCHECK(!user_callback_);
user_callback_ = &probe;
}
void PerformanceMonitor::Did(const probe::UserCallback& probe) {
--user_callback_depth_;
if (!user_callback_depth_)
user_callback_ = nullptr;
DCHECK(user_callback_ != &probe);
}
void PerformanceMonitor::DocumentWriteFetchScript(Document* document) {
if (!enabled_)
return;
String text = "Parser was blocked due to document.write(<script>)";
InnerReportGenericViolation(document, kBlockedParser, text, base::TimeDelta(),
nullptr);
}
void PerformanceMonitor::WillProcessTask(base::TimeTicks start_time) {
// Reset m_taskExecutionContext. We don't clear this in didProcessTask
// as it is needed in ReportTaskTime which occurs after didProcessTask.
task_execution_context_ = nullptr;
task_has_multiple_contexts_ = false;
task_should_be_reported_ = false;
if (!enabled_)
return;
// Reset everything for regular and nested tasks.
script_depth_ = 0;
layout_depth_ = 0;
per_task_style_and_layout_time_ = TimeDelta();
user_callback_ = nullptr;
v8_compile_start_time_ = TimeTicks();
sub_task_attributions_.clear();
}
void PerformanceMonitor::DidProcessTask(base::TimeTicks start_time,
base::TimeTicks end_time) {
if (!enabled_ || !task_should_be_reported_)
return;
base::TimeDelta layout_threshold = thresholds_[kLongLayout];
base::TimeDelta layout_time = per_task_style_and_layout_time_;
if (!layout_threshold.is_zero() && layout_time > layout_threshold) {
ClientThresholds* client_thresholds = subscriptions_.at(kLongLayout);
DCHECK(client_thresholds);
for (const auto& it : *client_thresholds) {
if (it.value < layout_time)
it.key->ReportLongLayout(layout_time);
}
}
base::TimeDelta task_time = end_time - start_time;
if (!thresholds_[kLongTask].is_zero() && task_time > thresholds_[kLongTask]) {
ClientThresholds* client_thresholds = subscriptions_.at(kLongTask);
for (const auto& it : *client_thresholds) {
if (it.value < task_time) {
it.key->ReportLongTask(
start_time, end_time,
task_has_multiple_contexts_ ? nullptr : task_execution_context_,
task_has_multiple_contexts_, sub_task_attributions_);
}
}
}
}
void PerformanceMonitor::InnerReportGenericViolation(
ExecutionContext* context,
Violation violation,
const String& text,
base::TimeDelta time,
std::unique_ptr<SourceLocation> location) {
ClientThresholds* client_thresholds = subscriptions_.at(violation);
if (!client_thresholds)
return;
if (!location)
location = SourceLocation::Capture(context);
for (const auto& it : *client_thresholds) {
if (it.value < time)
it.key->ReportGenericViolation(violation, text, time, location.get());
}
}
void PerformanceMonitor::Trace(blink::Visitor* visitor) {
visitor->Trace(local_root_);
visitor->Trace(task_execution_context_);
visitor->Trace(subscriptions_);
}
} // namespace blink