blob: 97c372ba87878aeb7ff79edc2556de93dc763def [file] [log] [blame]
// Copyright 2019 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/timing/profiler_group.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "third_party/blink/public/platform/task_type.h"
#include "third_party/blink/renderer/bindings/core/v8/profiler_trace_builder.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_profiler_init_options.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_profiler_trace.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/timing/profiler.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
#include "v8/include/v8-profiler.h"
#include "v8/include/v8.h"
namespace blink {
namespace {
#if defined(OS_WIN)
// On Windows, assume we have the coarsest possible timer.
static constexpr int kBaseSampleIntervalMs =
base::Time::kMinLowResolutionThresholdMs;
#else
// Default to a 10ms base sampling interval on other platforms.
// TODO(acomminos): Reevaluate based on empirical overhead.
static constexpr int kBaseSampleIntervalMs = 10;
#endif // defined(OS_WIN)
} // namespace
ProfilerGroup* ProfilerGroup::From(v8::Isolate* isolate) {
auto* isolate_data = V8PerIsolateData::From(isolate);
auto* profiler_group =
reinterpret_cast<ProfilerGroup*>(isolate_data->ProfilerGroup());
if (!profiler_group) {
profiler_group = MakeGarbageCollected<ProfilerGroup>(isolate);
isolate_data->SetProfilerGroup(profiler_group);
}
return profiler_group;
}
base::TimeDelta ProfilerGroup::GetBaseSampleInterval() {
return base::TimeDelta::FromMilliseconds(kBaseSampleIntervalMs);
}
ProfilerGroup::ProfilerGroup(v8::Isolate* isolate)
: isolate_(isolate),
cpu_profiler_(nullptr),
next_profiler_id_(0),
num_active_profilers_(0) {}
Profiler* ProfilerGroup::CreateProfiler(ScriptState* script_state,
const ProfilerInitOptions& init_options,
base::TimeTicks time_origin,
ExceptionState& exception_state) {
DCHECK(RuntimeEnabledFeatures::ExperimentalJSProfilerEnabled(
ExecutionContext::From(script_state)));
DCHECK_EQ(script_state->GetIsolate(), isolate_);
DCHECK(init_options.hasSampleInterval());
const base::TimeDelta sample_interval =
base::TimeDelta::FromMillisecondsD(init_options.sampleInterval());
const int64_t sample_interval_us = sample_interval.InMicroseconds();
if (sample_interval_us < 0 ||
sample_interval_us > std::numeric_limits<int>::max()) {
exception_state.ThrowRangeError("Invalid sample interval");
return nullptr;
}
if (!cpu_profiler_)
InitV8Profiler();
DCHECK(cpu_profiler_);
String profiler_id = NextProfilerId();
v8::CpuProfilingOptions options(v8::kLeafNodeLineNumbers,
init_options.hasMaxBufferSize()
? init_options.maxBufferSize()
: v8::CpuProfilingOptions::kNoSampleLimit,
static_cast<int>(sample_interval_us));
v8::CpuProfilingStatus status =
cpu_profiler_->StartProfiling(V8String(isolate_, profiler_id), options);
switch (status) {
case v8::CpuProfilingStatus::kErrorTooManyProfilers: {
exception_state.ThrowTypeError(
"Reached maximum concurrent amount of profilers");
return nullptr;
}
case v8::CpuProfilingStatus::kAlreadyStarted: {
// Since we increment the profiler id for every invocation of
// StartProfiling, we do not expect to hit kAlreadyStarted status
DCHECK(false);
return nullptr;
}
case v8::CpuProfilingStatus::kStarted: {
// Limit non-crossorigin script frames to the origin that started the
// profiler.
auto* execution_context = ExecutionContext::From(script_state);
scoped_refptr<const SecurityOrigin> source_origin(
execution_context->GetSecurityOrigin());
// The V8 CPU profiler ticks in multiples of the base sampling interval.
// This effectively means that we gather samples at the multiple of the
// base sampling interval that's greater than or equal to the requested
// interval.
int effective_sample_interval_ms =
static_cast<int>(sample_interval.InMilliseconds());
if (effective_sample_interval_ms % kBaseSampleIntervalMs != 0 ||
effective_sample_interval_ms == 0) {
effective_sample_interval_ms +=
(kBaseSampleIntervalMs -
effective_sample_interval_ms % kBaseSampleIntervalMs);
}
auto* profiler = MakeGarbageCollected<Profiler>(
this, script_state, profiler_id, effective_sample_interval_ms,
source_origin, time_origin);
profilers_.insert(profiler);
num_active_profilers_++;
return profiler;
}
}
}
ProfilerGroup::~ProfilerGroup() {
// v8::CpuProfiler should have been torn down by WillBeDestroyed.
DCHECK(!cpu_profiler_);
}
void ProfilerGroup::WillBeDestroyed() {
while (!profilers_.IsEmpty()) {
Profiler* profiler = profilers_.begin()->Get();
DCHECK(profiler);
CancelProfiler(profiler);
profiler->RemovedFromProfilerGroup();
DCHECK(profiler->stopped());
DCHECK(!profilers_.Contains(profiler));
}
StopDetachedProfilers();
if (cpu_profiler_)
TeardownV8Profiler();
}
void ProfilerGroup::Trace(Visitor* visitor) const {
visitor->Trace(profilers_);
V8PerIsolateData::GarbageCollectedData::Trace(visitor);
}
void ProfilerGroup::InitV8Profiler() {
DCHECK(!cpu_profiler_);
DCHECK_EQ(num_active_profilers_, 0);
cpu_profiler_ = v8::CpuProfiler::New(isolate_, v8::kStandardNaming);
#if defined(OS_WIN)
// Avoid busy-waiting on Windows, clamping us to the system clock interrupt
// interval in the worst case.
cpu_profiler_->SetUsePreciseSampling(false);
#endif // defined(OS_WIN)
cpu_profiler_->SetSamplingInterval(kBaseSampleIntervalMs *
base::Time::kMicrosecondsPerMillisecond);
}
void ProfilerGroup::TeardownV8Profiler() {
DCHECK(cpu_profiler_);
DCHECK_EQ(num_active_profilers_, 0);
cpu_profiler_->Dispose();
cpu_profiler_ = nullptr;
}
void ProfilerGroup::StopProfiler(ScriptState* script_state,
Profiler* profiler,
ScriptPromiseResolver* resolver) {
DCHECK(cpu_profiler_);
DCHECK(!profiler->stopped());
v8::Local<v8::String> profiler_id =
V8String(isolate_, profiler->ProfilerId());
auto* profile = cpu_profiler_->StopProfiling(profiler_id);
auto* trace = ProfilerTraceBuilder::FromProfile(
script_state, profile, profiler->SourceOrigin(), profiler->TimeOrigin());
resolver->Resolve(trace);
if (profile)
profile->Delete();
profilers_.erase(profiler);
if (--num_active_profilers_ == 0)
TeardownV8Profiler();
}
void ProfilerGroup::CancelProfiler(Profiler* profiler) {
DCHECK(cpu_profiler_);
DCHECK(!profiler->stopped());
profilers_.erase(profiler);
CancelProfilerImpl(profiler->ProfilerId());
}
void ProfilerGroup::CancelProfilerAsync(ScriptState* script_state,
Profiler* profiler) {
DCHECK(IsMainThread());
DCHECK(cpu_profiler_);
DCHECK(!profiler->stopped());
profilers_.erase(profiler);
// register the profiler to be cleaned up in case its associated context
// gets destroyed before the cleanup task is executed.
detached_profiler_ids_.push_back(profiler->ProfilerId());
// Since it's possible for the profiler to get destructed along with its
// associated context, dispatch a task to cleanup context-independent isolate
// resources (rather than use the context's task runner).
ThreadScheduler::Current()->V8TaskRunner()->PostTask(
FROM_HERE, WTF::Bind(&ProfilerGroup::StopDetachedProfiler,
WrapPersistent(this), profiler->ProfilerId()));
}
void ProfilerGroup::StopDetachedProfiler(String profiler_id) {
DCHECK(IsMainThread());
// we use a vector instead of a map because the expected number of profiler
// is expected to be very small
auto* it = std::find(detached_profiler_ids_.begin(),
detached_profiler_ids_.end(), profiler_id);
if (it == detached_profiler_ids_.end()) {
// Profiler already stopped
return;
}
CancelProfilerImpl(profiler_id);
detached_profiler_ids_.erase(it);
}
void ProfilerGroup::StopDetachedProfilers() {
DCHECK(IsMainThread());
for (auto& detached_profiler_id : detached_profiler_ids_) {
CancelProfilerImpl(detached_profiler_id);
}
detached_profiler_ids_.clear();
}
void ProfilerGroup::CancelProfilerImpl(String profiler_id) {
if (!cpu_profiler_)
return;
v8::HandleScope scope(isolate_);
v8::Local<v8::String> v8_profiler_id = V8String(isolate_, profiler_id);
auto* profile = cpu_profiler_->StopProfiling(v8_profiler_id);
profile->Delete();
if (--num_active_profilers_ == 0)
TeardownV8Profiler();
}
String ProfilerGroup::NextProfilerId() {
auto id = String::Format("blink::Profiler[%d]", next_profiler_id_);
++next_profiler_id_;
return id;
}
} // namespace blink