Commit 0521282f authored by Nicolas Dubus's avatar Nicolas Dubus Committed by Chromium LUCI CQ

[blink] Throw exception when profiler->StartProfiling() returns error status

 - cpu_profiler_->StartProfiling() now returns a status enum, of either kStarted, kAlreadyStarted, or kErrorTooManyProfilers.
This change throws an exception and returns a nullptr when either kAlreadyStarted or kErrorTooManyProfilers is returned

R=acomminos@fb.com, npm@chromium.org, petermarshall@chromium.org, ulan@chromium.org

Change-Id: Ie9c6df3c836119064c30fffc54ee7f21f34d9eb1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2549465Reviewed-by: default avatarNicolás Peña Moreno <npm@chromium.org>
Reviewed-by: default avatarUlan Degenbaev <ulan@chromium.org>
Reviewed-by: default avatarPeter Marshall <petermarshall@chromium.org>
Commit-Queue: Nicolas Dubus <nicodubus@fb.com>
Cr-Commit-Position: refs/heads/master@{#833852}
parent be24d572
...@@ -90,34 +90,50 @@ Profiler* ProfilerGroup::CreateProfiler(ScriptState* script_state, ...@@ -90,34 +90,50 @@ Profiler* ProfilerGroup::CreateProfiler(ScriptState* script_state,
: v8::CpuProfilingOptions::kNoSampleLimit, : v8::CpuProfilingOptions::kNoSampleLimit,
static_cast<int>(sample_interval_us)); static_cast<int>(sample_interval_us));
cpu_profiler_->StartProfiling(V8String(isolate_, profiler_id), options); v8::CpuProfilingStatus status =
cpu_profiler_->StartProfiling(V8String(isolate_, profiler_id), options);
// Limit non-crossorigin script frames to the origin that started the
// profiler. switch (status) {
auto* execution_context = ExecutionContext::From(script_state); case v8::CpuProfilingStatus::kErrorTooManyProfilers: {
scoped_refptr<const SecurityOrigin> source_origin( exception_state.ThrowTypeError(
execution_context->GetSecurityOrigin()); "Reached maximum concurrent amount of profilers");
return nullptr;
// 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 case v8::CpuProfilingStatus::kAlreadyStarted: {
// sampling interval that's greater than or equal to the requested interval. // Since we increment the profiler id for every invocation of
int effective_sample_interval_ms = // StartProfiling, we do not expect to hit kAlreadyStarted status
static_cast<int>(sample_interval.InMilliseconds()); DCHECK(false);
if (effective_sample_interval_ms % kBaseSampleIntervalMs != 0 || return nullptr;
effective_sample_interval_ms == 0) { }
effective_sample_interval_ms += case v8::CpuProfilingStatus::kStarted: {
(kBaseSampleIntervalMs - // Limit non-crossorigin script frames to the origin that started the
effective_sample_interval_ms % kBaseSampleIntervalMs); // 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;
}
} }
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() { ProfilerGroup::~ProfilerGroup() {
......
...@@ -53,7 +53,7 @@ class CORE_EXPORT ProfilerGroup ...@@ -53,7 +53,7 @@ class CORE_EXPORT ProfilerGroup
// Cancels a profiler, discarding its associated trace. // Cancels a profiler, discarding its associated trace.
void CancelProfiler(Profiler*); void CancelProfiler(Profiler*);
// Asynchronously cancels a profiler. Invoked on Profiler descrution. // Asynchronously cancels a profiler. Invoked on Profiler destruction.
void CancelProfilerAsync(ScriptState*, Profiler*); void CancelProfilerAsync(ScriptState*, Profiler*);
// Internal implementation of cancel. // Internal implementation of cancel.
void CancelProfilerImpl(String profiler_id); void CancelProfilerImpl(String profiler_id);
......
...@@ -16,25 +16,10 @@ namespace blink { ...@@ -16,25 +16,10 @@ namespace blink {
namespace { namespace {
static constexpr int kLargeProfilerCount = 128; static constexpr int kLargeProfilerCount = 128;
static constexpr int kMaxConcurrentProfilerCount = 100;
} // namespace } // namespace
// Tests that a leaked profiler doesn't crash the isolate on heap teardown.
TEST(ProfilerGroupTest, LeakProfiler) {
V8TestingScope scope;
ProfilerGroup* profiler_group = ProfilerGroup::From(scope.GetIsolate());
ProfilerInitOptions* init_options = ProfilerInitOptions::Create();
init_options->setSampleInterval(0);
init_options->setMaxBufferSize(0);
Profiler* profiler = profiler_group->CreateProfiler(
scope.GetScriptState(), *init_options, base::TimeTicks(),
scope.GetExceptionState());
EXPECT_FALSE(profiler->stopped());
}
TEST(ProfilerGroupTest, StopProfiler) { TEST(ProfilerGroupTest, StopProfiler) {
V8TestingScope scope; V8TestingScope scope;
...@@ -83,6 +68,9 @@ TEST(ProfilerGroupTest, CreateProfiler) { ...@@ -83,6 +68,9 @@ TEST(ProfilerGroupTest, CreateProfiler) {
EXPECT_FALSE(profiler->stopped()); EXPECT_FALSE(profiler->stopped());
EXPECT_FALSE(scope.GetExceptionState().HadException()); EXPECT_FALSE(scope.GetExceptionState().HadException());
// clean up
profiler->stop(scope.GetScriptState());
} }
TEST(ProfilerGroupTest, ClampedSamplingIntervalZero) { TEST(ProfilerGroupTest, ClampedSamplingIntervalZero) {
...@@ -102,6 +90,9 @@ TEST(ProfilerGroupTest, ClampedSamplingIntervalZero) { ...@@ -102,6 +90,9 @@ TEST(ProfilerGroupTest, ClampedSamplingIntervalZero) {
// interval. // interval.
EXPECT_EQ(profiler->sampleInterval(), EXPECT_EQ(profiler->sampleInterval(),
ProfilerGroup::GetBaseSampleInterval().InMilliseconds()); ProfilerGroup::GetBaseSampleInterval().InMilliseconds());
// clean up
profiler->stop(scope.GetScriptState());
} }
TEST(ProfilerGroupTest, ClampedSamplingIntervalNext) { TEST(ProfilerGroupTest, ClampedSamplingIntervalNext) {
...@@ -123,6 +114,42 @@ TEST(ProfilerGroupTest, ClampedSamplingIntervalNext) { ...@@ -123,6 +114,42 @@ TEST(ProfilerGroupTest, ClampedSamplingIntervalNext) {
// interval. // interval.
EXPECT_EQ(profiler->sampleInterval(), EXPECT_EQ(profiler->sampleInterval(),
(ProfilerGroup::GetBaseSampleInterval() * 2).InMilliseconds()); (ProfilerGroup::GetBaseSampleInterval() * 2).InMilliseconds());
// clean up
profiler->stop(scope.GetScriptState());
}
TEST(ProfilerGroupTest, V8ProfileLimitThrowsExceptionWhenMaxConcurrentReached) {
V8TestingScope scope;
HeapVector<Member<Profiler>> profilers;
ProfilerGroup* profiler_group = ProfilerGroup::From(scope.GetIsolate());
ProfilerInitOptions* init_options = ProfilerInitOptions::Create();
for (auto i = 0; i < kMaxConcurrentProfilerCount; i++) {
init_options->setSampleInterval(i);
profilers.push_back(profiler_group->CreateProfiler(
scope.GetScriptState(), *init_options, base::TimeTicks(),
scope.GetExceptionState()));
EXPECT_FALSE(scope.GetExceptionState().HadException());
}
// check kErrorTooManyProfilers
ProfilerGroup* extra_profiler_group = ProfilerGroup::From(scope.GetIsolate());
ProfilerInitOptions* extra_init_options = ProfilerInitOptions::Create();
extra_init_options->setSampleInterval(100);
for (auto i = kMaxConcurrentProfilerCount; i < kLargeProfilerCount; i++) {
extra_profiler_group->CreateProfiler(scope.GetScriptState(),
*extra_init_options, base::TimeTicks(),
scope.GetExceptionState());
EXPECT_TRUE(scope.GetExceptionState().HadException());
EXPECT_EQ(scope.GetExceptionState().Message(),
"Reached maximum concurrent amount of profilers");
}
for (auto profiler : profilers) {
profiler->stop(scope.GetScriptState());
}
} }
TEST(ProfilerGroupTest, NegativeSamplingInterval) { TEST(ProfilerGroupTest, NegativeSamplingInterval) {
...@@ -152,30 +179,6 @@ TEST(ProfilerGroupTest, OverflowSamplingInterval) { ...@@ -152,30 +179,6 @@ TEST(ProfilerGroupTest, OverflowSamplingInterval) {
EXPECT_TRUE(scope.GetExceptionState().HadException()); EXPECT_TRUE(scope.GetExceptionState().HadException());
} }
// Tests behaviour when exceeding the maximum number of concurrent profiles
// supported by the V8 profiling API (https://crbug.com/1052341).
TEST(ProfilerGroupTest, V8ProfileLimit) {
V8TestingScope scope;
ProfilerGroup* profiler_group = ProfilerGroup::From(scope.GetIsolate());
ProfilerInitOptions* init_options = ProfilerInitOptions::Create();
init_options->setSampleInterval(0);
HeapVector<Member<Profiler>> profilers;
for (auto i = 0; i < kLargeProfilerCount; i++) {
// TODO(acomminos): The V8 public API should likely be changed to expose
// exceeding the profile limit during creation. This would enable
// instantiation of profiles to cause a promise rejection instead.
profilers.push_back(profiler_group->CreateProfiler(
scope.GetScriptState(), *init_options, base::TimeTicks(),
scope.GetExceptionState()));
}
for (auto profiler : profilers) {
profiler->stop(scope.GetScriptState());
}
}
TEST(ProfilerGroupTest, Bug1119865) { TEST(ProfilerGroupTest, Bug1119865) {
class ExpectNoCallFunction : public ScriptFunction { class ExpectNoCallFunction : public ScriptFunction {
public: public:
...@@ -208,6 +211,27 @@ TEST(ProfilerGroupTest, Bug1119865) { ...@@ -208,6 +211,27 @@ TEST(ProfilerGroupTest, Bug1119865) {
profiler->stop(scope.GetScriptState()).Then(function); profiler->stop(scope.GetScriptState()).Then(function);
} }
/*
* LEAK TESTS - SHOULD RUN LAST
*/
// Tests that a leaked profiler doesn't crash the isolate on heap teardown.
// These should run last
TEST(ProfilerGroupTest, LeakProfiler) {
V8TestingScope scope;
ProfilerGroup* profiler_group = ProfilerGroup::From(scope.GetIsolate());
ProfilerInitOptions* init_options = ProfilerInitOptions::Create();
init_options->setSampleInterval(0);
init_options->setMaxBufferSize(0);
Profiler* profiler = profiler_group->CreateProfiler(
scope.GetScriptState(), *init_options, base::TimeTicks(),
scope.GetExceptionState());
EXPECT_FALSE(profiler->stopped());
}
// Tests that a leaked profiler doesn't crash when disposed alongside its // Tests that a leaked profiler doesn't crash when disposed alongside its
// context. // context.
TEST(ProfilerGroupTest, LeakProfilerWithContext) { TEST(ProfilerGroupTest, LeakProfilerWithContext) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment