diff --git a/shell/browser/javascript_environment.cc b/shell/browser/javascript_environment.cc index f67ce3705283..6969b44867e9 100644 --- a/shell/browser/javascript_environment.cc +++ b/shell/browser/javascript_environment.cc @@ -5,13 +5,15 @@ #include "shell/browser/javascript_environment.h" #include - #include +#include +#include #include "base/command_line.h" #include "base/task/current_thread.h" #include "base/task/thread_pool/initialization_util.h" #include "base/threading/thread_task_runner_handle.h" +#include "base/trace_event/trace_event.h" #include "content/public/common/content_switches.h" #include "gin/array_buffer.h" #include "gin/v8_initializer.h" @@ -24,6 +26,43 @@ namespace { v8::Isolate* g_isolate; } +namespace gin { + +class ConvertableToTraceFormatWrapper final + : public base::trace_event::ConvertableToTraceFormat { + public: + explicit ConvertableToTraceFormatWrapper( + std::unique_ptr inner) + : inner_(std::move(inner)) {} + ~ConvertableToTraceFormatWrapper() override = default; + void AppendAsTraceFormat(std::string* out) const final { + inner_->AppendAsTraceFormat(out); + } + + private: + std::unique_ptr inner_; + + DISALLOW_COPY_AND_ASSIGN(ConvertableToTraceFormatWrapper); +}; + +} // namespace gin + +// Allow std::unique_ptr to be a valid +// initialization value for trace macros. +template <> +struct base::trace_event::TraceValue::Helper< + std::unique_ptr> { + static constexpr unsigned char kType = TRACE_VALUE_TYPE_CONVERTABLE; + static inline void SetValue( + TraceValue* v, + std::unique_ptr value) { + // NOTE: |as_convertable| is an owning pointer, so using new here + // is acceptable. + v->as_convertable = + new gin::ConvertableToTraceFormatWrapper(std::move(value)); + } +}; + namespace electron { JavascriptEnvironment::JavascriptEnvironment(uv_loop_t* event_loop) @@ -52,6 +91,146 @@ JavascriptEnvironment::~JavascriptEnvironment() { g_isolate = nullptr; } +class EnabledStateObserverImpl final + : public base::trace_event::TraceLog::EnabledStateObserver { + public: + EnabledStateObserverImpl() { + base::trace_event::TraceLog::GetInstance()->AddEnabledStateObserver(this); + } + + ~EnabledStateObserverImpl() override { + base::trace_event::TraceLog::GetInstance()->RemoveEnabledStateObserver( + this); + } + + void OnTraceLogEnabled() final { + base::AutoLock lock(mutex_); + for (auto* o : observers_) { + o->OnTraceEnabled(); + } + } + + void OnTraceLogDisabled() final { + base::AutoLock lock(mutex_); + for (auto* o : observers_) { + o->OnTraceDisabled(); + } + } + + void AddObserver(v8::TracingController::TraceStateObserver* observer) { + { + base::AutoLock lock(mutex_); + DCHECK(!observers_.count(observer)); + observers_.insert(observer); + } + + // Fire the observer if recording is already in progress. + if (base::trace_event::TraceLog::GetInstance()->IsEnabled()) + observer->OnTraceEnabled(); + } + + void RemoveObserver(v8::TracingController::TraceStateObserver* observer) { + base::AutoLock lock(mutex_); + DCHECK_EQ(observers_.count(observer), 1lu); + observers_.erase(observer); + } + + private: + base::Lock mutex_; + std::unordered_set observers_; + + DISALLOW_COPY_AND_ASSIGN(EnabledStateObserverImpl); +}; + +base::LazyInstance::Leaky g_trace_state_dispatcher = + LAZY_INSTANCE_INITIALIZER; + +class TracingControllerImpl : public node::tracing::TracingController { + public: + TracingControllerImpl() = default; + ~TracingControllerImpl() override = default; + + // TracingController implementation. + const uint8_t* GetCategoryGroupEnabled(const char* name) override { + return TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED(name); + } + uint64_t AddTraceEvent( + char phase, + const uint8_t* category_enabled_flag, + const char* name, + const char* scope, + uint64_t id, + uint64_t bind_id, + int32_t num_args, + const char** arg_names, + const uint8_t* arg_types, + const uint64_t* arg_values, + std::unique_ptr* arg_convertables, + unsigned int flags) override { + base::trace_event::TraceArguments args( + num_args, arg_names, arg_types, + reinterpret_cast( // NOLINT(runtime/int) + arg_values), + arg_convertables); + DCHECK_LE(num_args, 2); + base::trace_event::TraceEventHandle handle = + TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_BIND_ID( + phase, category_enabled_flag, name, scope, id, bind_id, &args, + flags); + uint64_t result; + memcpy(&result, &handle, sizeof(result)); + return result; + } + uint64_t AddTraceEventWithTimestamp( + char phase, + const uint8_t* category_enabled_flag, + const char* name, + const char* scope, + uint64_t id, + uint64_t bind_id, + int32_t num_args, + const char** arg_names, + const uint8_t* arg_types, + const uint64_t* arg_values, + std::unique_ptr* arg_convertables, + unsigned int flags, + int64_t timestampMicroseconds) override { + base::trace_event::TraceArguments args( + num_args, arg_names, arg_types, + reinterpret_cast( // NOLINT(runtime/int) + arg_values), + arg_convertables); + DCHECK_LE(num_args, 2); + base::TimeTicks timestamp = + base::TimeTicks() + + base::TimeDelta::FromMicroseconds(timestampMicroseconds); + base::trace_event::TraceEventHandle handle = + TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_THREAD_ID_AND_TIMESTAMP( + phase, category_enabled_flag, name, scope, id, bind_id, + TRACE_EVENT_API_CURRENT_THREAD_ID, timestamp, &args, flags); + uint64_t result; + memcpy(&result, &handle, sizeof(result)); + return result; + } + void UpdateTraceEventDuration(const uint8_t* category_enabled_flag, + const char* name, + uint64_t handle) override { + base::trace_event::TraceEventHandle traceEventHandle; + memcpy(&traceEventHandle, &handle, sizeof(handle)); + TRACE_EVENT_API_UPDATE_TRACE_EVENT_DURATION(category_enabled_flag, name, + traceEventHandle); + } + void AddTraceStateObserver(TraceStateObserver* observer) override { + g_trace_state_dispatcher.Get().AddObserver(observer); + } + void RemoveTraceStateObserver(TraceStateObserver* observer) override { + g_trace_state_dispatcher.Get().RemoveObserver(observer); + } + + private: + DISALLOW_COPY_AND_ASSIGN(TracingControllerImpl); +}; + v8::Isolate* JavascriptEnvironment::Initialize(uv_loop_t* event_loop) { auto* cmd = base::CommandLine::ForCurrentProcess(); @@ -63,7 +242,7 @@ v8::Isolate* JavascriptEnvironment::Initialize(uv_loop_t* event_loop) { // The V8Platform of gin relies on Chromium's task schedule, which has not // been started at this point, so we have to rely on Node's V8Platform. auto* tracing_agent = node::CreateAgent(); - auto* tracing_controller = tracing_agent->GetTracingController(); + auto* tracing_controller = new TracingControllerImpl(); node::tracing::TraceEventHelper::SetAgent(tracing_agent); platform_ = node::CreatePlatform( base::RecommendedMaxNumberOfThreadsInThreadGroup(3, 8, 0.1, 0), diff --git a/spec-main/api-content-tracing-spec.ts b/spec-main/api-content-tracing-spec.ts index 9d3f43e12166..3f2f258b09ca 100644 --- a/spec-main/api-content-tracing-spec.ts +++ b/spec-main/api-content-tracing-spec.ts @@ -120,4 +120,27 @@ ifdescribe(!(process.platform !== 'win32' && ['arm', 'arm64'].includes(process.a expect(resultFilePath).to.be.a('string').that.is.not.empty('result path'); }); }); + + describe('captured events', () => { + it('include V8 samples from the main process', async () => { + await contentTracing.startRecording({ + categoryFilter: 'disabled-by-default-v8.cpu_profiler', + traceOptions: 'record-until-full' + }); + { + const start = +new Date(); + let n = 0; + const f = () => {}; + while (+new Date() - start < 200 || n < 500) { + await delay(0); + f(); + n++; + } + } + const path = await contentTracing.stopRecording(); + const data = fs.readFileSync(path, 'utf8'); + const parsed = JSON.parse(data); + expect(parsed.traceEvents.some((x: any) => x.cat === 'disabled-by-default-v8.cpu_profiler' && x.name === 'ProfileChunk')).to.be.true(); + }); + }); });