-
Notifications
You must be signed in to change notification settings - Fork 121
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement Cobalt wrappers for v8::CpuProfiler (#2037)
Implement Cobalt wrappers for v8::CpuProfiler Provides a baseline cobalt-side implementation of the JS Self-Profiling API (https://wicg.github.io/js-self-profiling/). Adds Profiler::Profiler, which wraps a profile handle from a v8::CpuProfiler, and relevant IDL bindings. b/314179829 Co-authored-by: Ahmed Elzeiny <[email protected]> (cherry picked from commit 2285d51)
- Loading branch information
Showing
17 changed files
with
879 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
# Copyright 2023 The Cobalt Authors. All Rights Reserved. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
static_library("js_profiler") { | ||
sources = [ | ||
"profiler.cc", | ||
"profiler.h", | ||
"profiler_trace_builder.cc", | ||
"profiler_trace_builder.h", | ||
"profiler_trace_wrapper.h", | ||
] | ||
|
||
deps = [ | ||
"//cobalt/base", | ||
"//cobalt/browser:generated_bindings", | ||
"//cobalt/browser:generated_types", | ||
"//cobalt/dom", | ||
"//cobalt/script", | ||
"//cobalt/script/v8c:engine", | ||
"//cobalt/web", | ||
"//third_party/chromium/media:media", | ||
"//third_party/v8:cppgc", | ||
] | ||
} | ||
|
||
target(gtest_target_type, "js_profiler_test") { | ||
testonly = true | ||
|
||
sources = [ "js_profiler_test.cc" ] | ||
|
||
deps = [ | ||
":js_profiler", | ||
"//cobalt/dom", | ||
"//cobalt/dom/testing:dom_testing", | ||
"//cobalt/dom/testing:dom_testing", | ||
"//cobalt/script", | ||
"//cobalt/test:run_all_unittests", | ||
"//cobalt/web:dom_exception", | ||
"//testing/gmock", | ||
"//testing/gtest", | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
// Copyright 2023 The Cobalt Authors. All Rights Reserved. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
#include "base/callback.h" | ||
#include "base/memory/ref_counted.h" | ||
#include "cobalt/dom/testing/stub_environment_settings.h" | ||
#include "cobalt/dom/testing/stub_window.h" | ||
#include "cobalt/dom/testing/test_with_javascript.h" | ||
#include "cobalt/js_profiler/profiler.h" | ||
#include "cobalt/js_profiler/profiler_trace_wrapper.h" | ||
#include "cobalt/script/testing/mock_exception_state.h" | ||
#include "cobalt/web/dom_exception.h" | ||
#include "cobalt/web/environment_settings_helper.h" | ||
#include "testing/gtest/include/gtest/gtest.h" | ||
|
||
using ::testing::_; | ||
using ::testing::Return; | ||
using ::testing::StrictMock; | ||
|
||
namespace cobalt { | ||
namespace js_profiler { | ||
|
||
class ProfilerTest : public dom::testing::TestWithJavaScript { | ||
public: | ||
ProfilerTest() {} | ||
|
||
protected: | ||
dom::testing::StubWindow window_; | ||
StrictMock<script::testing::MockExceptionState> exception_state_; | ||
}; | ||
|
||
TEST_F(ProfilerTest, ProfilerStop) { | ||
v8::HandleScope scope(web::get_isolate(window_.environment_settings())); | ||
ProfilerInitOptions init_options; | ||
init_options.set_sample_interval(10); | ||
init_options.set_max_buffer_size(1000); | ||
|
||
scoped_refptr<Profiler> profiler_(new Profiler( | ||
window_.environment_settings(), init_options, &exception_state_)); | ||
|
||
auto promise = profiler_->Stop(window_.environment_settings()); | ||
EXPECT_EQ(profiler_->stopped(), true); | ||
EXPECT_TRUE(promise->State() == cobalt::script::PromiseState::kPending); | ||
base::RunLoop().RunUntilIdle(); | ||
EXPECT_TRUE(promise->State() == cobalt::script::PromiseState::kFulfilled); | ||
} | ||
|
||
TEST_F(ProfilerTest, ProfilerAlreadyStopped) { | ||
v8::HandleScope scope(web::get_isolate(window_.environment_settings())); | ||
ProfilerInitOptions init_options; | ||
init_options.set_sample_interval(10); | ||
init_options.set_max_buffer_size(0); | ||
|
||
scoped_refptr<Profiler> profiler_(new Profiler( | ||
window_.environment_settings(), init_options, &exception_state_)); | ||
|
||
auto promise = profiler_->Stop(window_.environment_settings()); | ||
EXPECT_EQ(profiler_->stopped(), true); | ||
EXPECT_TRUE(promise->State() == cobalt::script::PromiseState::kPending); | ||
base::RunLoop().RunUntilIdle(); | ||
EXPECT_TRUE(promise->State() == cobalt::script::PromiseState::kFulfilled); | ||
auto promise2 = profiler_->Stop(window_.environment_settings()); | ||
EXPECT_TRUE(promise2->State() == cobalt::script::PromiseState::kRejected); | ||
} | ||
|
||
TEST_F(ProfilerTest, ProfilerZeroSampleInterval) { | ||
v8::HandleScope scope(web::get_isolate(window_.environment_settings())); | ||
ProfilerInitOptions init_options; | ||
init_options.set_sample_interval(0); | ||
init_options.set_max_buffer_size(0); | ||
|
||
scoped_refptr<Profiler> profiler_(new Profiler( | ||
window_.environment_settings(), init_options, &exception_state_)); | ||
EXPECT_EQ(profiler_->sample_interval(), 10); | ||
auto promise = profiler_->Stop(window_.environment_settings()); | ||
EXPECT_EQ(profiler_->stopped(), true); | ||
EXPECT_TRUE(promise->State() == cobalt::script::PromiseState::kPending); | ||
base::RunLoop().RunUntilIdle(); | ||
EXPECT_TRUE(promise->State() == cobalt::script::PromiseState::kFulfilled); | ||
} | ||
|
||
TEST_F(ProfilerTest, ProfilerOutRangeSampleInterval) { | ||
v8::HandleScope scope(web::get_isolate(window_.environment_settings())); | ||
ProfilerInitOptions init_options; | ||
init_options.set_sample_interval(-1); | ||
init_options.set_max_buffer_size(0); | ||
|
||
scoped_refptr<Profiler> profiler_(new Profiler( | ||
window_.environment_settings(), init_options, &exception_state_)); | ||
EXPECT_EQ(profiler_->sample_interval(), 10); | ||
auto promise = profiler_->Stop(window_.environment_settings()); | ||
EXPECT_EQ(profiler_->stopped(), true); | ||
EXPECT_TRUE(promise->State() == cobalt::script::PromiseState::kPending); | ||
base::RunLoop().RunUntilIdle(); | ||
EXPECT_TRUE(promise->State() == cobalt::script::PromiseState::kFulfilled); | ||
} | ||
|
||
TEST_F(ProfilerTest, ProfilerJSCode) { | ||
std::string result; | ||
EXPECT_TRUE(EvaluateScript("Profiler", &result)); | ||
EXPECT_EQ(result, "function Profiler() { [native code] }"); | ||
} | ||
} // namespace js_profiler | ||
} // namespace cobalt |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
// Copyright 2023 The Cobalt Authors. All Rights Reserved. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
#include "cobalt/js_profiler/profiler.h" | ||
|
||
#include <iostream> | ||
#include <limits> | ||
#include <memory> | ||
#include <string> | ||
#include <utility> | ||
|
||
#include "cobalt/base/polymorphic_downcast.h" | ||
#include "cobalt/js_profiler/profiler_trace_builder.h" | ||
#include "cobalt/js_profiler/profiler_trace_wrapper.h" | ||
#include "cobalt/web/cache_utils.h" | ||
#include "cobalt/web/context.h" | ||
#include "cobalt/web/dom_exception.h" | ||
#include "cobalt/web/environment_settings.h" | ||
#include "cobalt/web/environment_settings_helper.h" | ||
|
||
namespace { | ||
v8::Local<v8::String> toV8String(v8::Isolate* isolate, | ||
const std::string& string) { | ||
if (string.empty()) return v8::String::Empty(isolate); | ||
return v8::String::NewFromUtf8(isolate, string.c_str(), | ||
v8::NewStringType::kNormal, string.length()) | ||
.ToLocalChecked(); | ||
} | ||
} // namespace | ||
|
||
namespace cobalt { | ||
namespace js_profiler { | ||
|
||
volatile uint32_t s_lastProfileId = 0; | ||
|
||
static constexpr int kBaseSampleIntervalMs = 10; | ||
|
||
Profiler::Profiler(script::EnvironmentSettings* settings, | ||
ProfilerInitOptions options, | ||
script::ExceptionState* exception_state) | ||
: cobalt::web::EventTarget(settings), | ||
stopped_(false), | ||
time_origin_{base::TimeTicks::Now()} { | ||
profiler_id_ = nextProfileId(); | ||
|
||
const base::TimeDelta sample_interval = | ||
base::Milliseconds(options.sample_interval()); | ||
|
||
int64_t sample_interval_us = sample_interval.InMicroseconds(); | ||
|
||
if (sample_interval_us < 0 || | ||
sample_interval_us > std::numeric_limits<int>::max()) { | ||
sample_interval_us = 0; | ||
} | ||
|
||
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); | ||
} | ||
sample_interval_ = effective_sample_interval_ms; | ||
|
||
auto isolate = web::get_isolate(settings); | ||
|
||
auto status = ImplProfilingStart( | ||
profiler_id_, | ||
v8::CpuProfilingOptions(v8::kLeafNodeLineNumbers, | ||
options.max_buffer_size(), sample_interval_us), | ||
settings); | ||
|
||
if (status == v8::CpuProfilingStatus::kAlreadyStarted) { | ||
web::DOMException::Raise(web::DOMException::kInvalidStateErr, | ||
"Profiler Already started", exception_state); | ||
} else if (status == v8::CpuProfilingStatus::kErrorTooManyProfilers) { | ||
web::DOMException::Raise(web::DOMException::kInvalidStateErr, | ||
"Too Many Profilers", exception_state); | ||
} | ||
} | ||
|
||
Profiler::~Profiler() { | ||
if (cpu_profiler_) { | ||
cpu_profiler_->Dispose(); | ||
cpu_profiler_ = nullptr; | ||
} | ||
} | ||
|
||
v8::CpuProfilingStatus Profiler::ImplProfilingStart( | ||
std::string profiler_id, v8::CpuProfilingOptions options, | ||
script::EnvironmentSettings* settings) { | ||
auto isolate = web::get_isolate(settings); | ||
cpu_profiler_ = v8::CpuProfiler::New(isolate); | ||
cpu_profiler_->SetSamplingInterval(kBaseSampleIntervalMs * | ||
base::Time::kMicrosecondsPerMillisecond); | ||
return cpu_profiler_->StartProfiling( | ||
toV8String(isolate, profiler_id), options, | ||
std::make_unique<ProfilerMaxSamplesDelegate>(this)); | ||
} | ||
|
||
std::string Profiler::nextProfileId() { | ||
s_lastProfileId++; | ||
return "cobalt::profiler[" + std::to_string(s_lastProfileId) + "]"; | ||
} | ||
|
||
void Profiler::PerformStop( | ||
script::EnvironmentSettings* environment_settings, | ||
std::unique_ptr<script::ValuePromiseWrappable::Reference> promise_reference, | ||
base::TimeTicks time_origin, std::string profiler_id) { | ||
auto isolate = web::get_isolate(environment_settings); | ||
auto profile = | ||
cpu_profiler_->StopProfiling(toV8String(isolate, profiler_id_)); | ||
auto trace = ProfilerTraceBuilder::FromProfile(profile, time_origin_); | ||
scoped_refptr<ProfilerTraceWrapper> result(new ProfilerTraceWrapper(trace)); | ||
cpu_profiler_->Dispose(); | ||
cpu_profiler_ = nullptr; | ||
promise_reference->value().Resolve(result); | ||
} | ||
|
||
Profiler::ProfilerTracePromise Profiler::Stop( | ||
script::EnvironmentSettings* environment_settings) { | ||
script::HandlePromiseWrappable promise = | ||
web::get_script_value_factory(environment_settings) | ||
->CreateInterfacePromise<scoped_refptr<ProfilerTraceWrapper>>(); | ||
if (!stopped()) { | ||
stopped_ = true; | ||
auto* global_wrappable = web::get_global_wrappable(environment_settings); | ||
auto* context = web::get_context(environment_settings); | ||
std::unique_ptr<script::ValuePromiseWrappable::Reference> promise_reference( | ||
new script::ValuePromiseWrappable::Reference(global_wrappable, | ||
promise)); | ||
|
||
context->message_loop()->task_runner()->PostTask( | ||
FROM_HERE, | ||
base::BindOnce(&Profiler::PerformStop, base::Unretained(this), | ||
environment_settings, std::move(promise_reference), | ||
std::move(time_origin_), std::move(profiler_id_))); | ||
} else { | ||
promise->Reject(new web::DOMException(web::DOMException::kInvalidStateErr, | ||
"Profiler already stopped.")); | ||
} | ||
return promise; | ||
} | ||
|
||
} // namespace js_profiler | ||
} // namespace cobalt |
Oops, something went wrong.