Commit 04afa7b9 authored by Andrew Comminos's avatar Andrew Comminos Committed by Commit Bot

Implement blink::Profiler trace generation

Given a v8::CpuProfile from a blink::Profiler, constructs a trace object
with appropriate cross-origin resource filtering and substack caching.

Bug: 956688
Change-Id: Ib41877f18efee83b75cf7be549b4141943e6ba55
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1629334Reviewed-by: default avatarNicolás Peña Moreno <npm@chromium.org>
Reviewed-by: default avatarKentaro Hara <haraken@chromium.org>
Reviewed-by: default avatarYuki Shiino <yukishiino@chromium.org>
Commit-Queue: Andrew Comminos <acomminos@fb.com>
Cr-Commit-Position: refs/heads/master@{#666552}
parent 0471e477
......@@ -59,6 +59,8 @@ bindings_core_v8_files =
"core/v8/module_record.h",
"core/v8/native_value_traits.h",
"core/v8/native_value_traits_impl.h",
"core/v8/profiler_trace_builder.cc",
"core/v8/profiler_trace_builder.h",
"core/v8/referrer_script_info.cc",
"core/v8/referrer_script_info.h",
"core/v8/rejected_promises.cc",
......
// 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/bindings/core/v8/profiler_trace_builder.h"
#include "base/time/time.h"
#include "third_party/blink/renderer/core/timing/performance.h"
#include "third_party/blink/renderer/core/timing/profiler_frame.h"
#include "third_party/blink/renderer/core/timing/profiler_sample.h"
#include "third_party/blink/renderer/core/timing/profiler_stack.h"
#include "third_party/blink/renderer/core/timing/profiler_trace.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
#include "v8/include/v8.h"
namespace blink {
ProfilerTrace* ProfilerTraceBuilder::FromProfile(
ScriptState* script_state,
const v8::CpuProfile* profile,
const SecurityOrigin* allowed_origin,
base::TimeTicks time_origin) {
TRACE_EVENT0("blink", "ProfilerTraceBuilder::FromProfile");
ProfilerTraceBuilder* builder = MakeGarbageCollected<ProfilerTraceBuilder>(
script_state, allowed_origin, time_origin);
for (int i = 0; i < profile->GetSamplesCount(); i++) {
const auto* node = profile->GetSample(i);
auto timestamp = base::TimeTicks() + base::TimeDelta::FromMicroseconds(
profile->GetSampleTimestamp(i));
builder->AddSample(node, timestamp);
}
return builder->GetTrace();
}
ProfilerTraceBuilder::ProfilerTraceBuilder(ScriptState* script_state,
const SecurityOrigin* allowed_origin,
base::TimeTicks time_origin)
: script_state_(script_state),
allowed_origin_(allowed_origin),
time_origin_(time_origin) {}
void ProfilerTraceBuilder::Trace(blink::Visitor* visitor) {
visitor->Trace(script_state_);
visitor->Trace(frames_);
visitor->Trace(stacks_);
visitor->Trace(samples_);
}
void ProfilerTraceBuilder::AddSample(const v8::CpuProfileNode* node,
base::TimeTicks timestamp) {
auto* sample = ProfilerSample::Create();
auto relative_timestamp = Performance::MonotonicTimeToDOMHighResTimeStamp(
time_origin_, timestamp, true);
sample->setTimestamp(relative_timestamp);
if (base::Optional<wtf_size_t> stack_id = GetOrInsertStackId(node))
sample->setStackId(*stack_id);
samples_.push_back(sample);
}
base::Optional<wtf_size_t> ProfilerTraceBuilder::GetOrInsertStackId(
const v8::CpuProfileNode* node) {
if (!node)
return base::Optional<wtf_size_t>();
// Omit frames that don't pass a cross-origin check.
// Do this at the stack level (rather than the frame level) to avoid
// including skeleton frames without data.
KURL resource_url(node->GetScriptResourceNameStr());
if (!ShouldIncludeStackFrame(resource_url, node->GetScriptId(),
node->GetSourceType(),
node->IsScriptSharedCrossOrigin())) {
return GetOrInsertStackId(node->GetParent());
}
auto existing_stack_id = node_to_stack_map_.find(node);
if (existing_stack_id != node_to_stack_map_.end()) {
// If we found a stack entry for this node ID, the subpath to the root
// already exists in the trace, and we may coalesce.
return existing_stack_id->value;
}
auto* stack = ProfilerStack::Create();
wtf_size_t frame_id = GetOrInsertFrameId(node);
stack->setFrameId(frame_id);
if (base::Optional<int> parent_stack_id =
GetOrInsertStackId(node->GetParent()))
stack->setParentId(*parent_stack_id);
wtf_size_t stack_id = stacks_.size();
stacks_.push_back(stack);
node_to_stack_map_.Set(node, stack_id);
return stack_id;
}
wtf_size_t ProfilerTraceBuilder::GetOrInsertFrameId(
const v8::CpuProfileNode* node) {
auto existing_frame_id = node_to_frame_map_.find(node);
if (existing_frame_id != node_to_frame_map_.end())
return existing_frame_id->value;
auto* frame = ProfilerFrame::Create();
frame->setName(node->GetFunctionNameStr());
if (*node->GetScriptResourceNameStr() != '\0') {
wtf_size_t resource_id =
GetOrInsertResourceId(node->GetScriptResourceNameStr());
frame->setResourceId(resource_id);
}
if (node->GetLineNumber() != v8::CpuProfileNode::kNoLineNumberInfo)
frame->setLine(node->GetLineNumber());
if (node->GetColumnNumber() != v8::CpuProfileNode::kNoColumnNumberInfo)
frame->setColumn(node->GetColumnNumber());
wtf_size_t frame_id = frames_.size();
frames_.push_back(frame);
node_to_frame_map_.Set(node, frame_id);
return frame_id;
}
wtf_size_t ProfilerTraceBuilder::GetOrInsertResourceId(
const char* resource_name) {
// Since V8's CPU profiler already does string interning, pointer equality is
// value equality here.
auto existing_resource_id = resource_map_.find(resource_name);
if (existing_resource_id != resource_map_.end())
return existing_resource_id->value;
wtf_size_t resource_id = resources_.size();
resources_.push_back(resource_name);
resource_map_.Set(resource_name, resource_id);
return resource_id;
}
ProfilerTrace* ProfilerTraceBuilder::GetTrace() const {
ProfilerTrace* trace = ProfilerTrace::Create();
trace->setResources(resources_);
trace->setFrames(frames_);
trace->setStacks(stacks_);
trace->setSamples(samples_);
return trace;
}
bool ProfilerTraceBuilder::ShouldIncludeStackFrame(
const KURL& script_url,
int script_id,
v8::CpuProfileNode::SourceType source_type,
bool script_shared_cross_origin) {
// Omit V8 metadata frames.
if (source_type != v8::CpuProfileNode::kScript &&
source_type != v8::CpuProfileNode::kBuiltin &&
source_type != v8::CpuProfileNode::kCallback) {
return false;
}
// If we couldn't derive script data, only allow builtins and callbacks.
if (script_id == v8::UnboundScript::kNoScriptId) {
return source_type == v8::CpuProfileNode::kBuiltin ||
source_type == v8::CpuProfileNode::kCallback;
}
// If we already tested whether or not this script was cross-origin, return
// the cached results.
auto it = script_same_origin_cache_.find(script_id);
if (it != script_same_origin_cache_.end())
return it->value;
if (!script_url.IsValid())
return false;
auto origin = SecurityOrigin::Create(script_url);
// TODO(acomminos): Consider easing this check based on optional headers.
bool allowed = script_shared_cross_origin ||
origin->IsSameSchemeHostPort(allowed_origin_);
script_same_origin_cache_.Set(script_id, allowed);
return allowed;
}
} // namespace blink
// 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.
#ifndef THIRD_PARTY_BLINK_RENDERER_BINDINGS_CORE_V8_PROFILER_TRACE_BUILDER_H_
#define THIRD_PARTY_BLINK_RENDERER_BINDINGS_CORE_V8_PROFILER_TRACE_BUILDER_H_
#include "base/macros.h"
#include "base/optional.h"
#include "base/time/time.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/heap/heap_allocator.h"
#include "third_party/blink/renderer/platform/heap/member.h"
#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
#include "third_party/blink/renderer/platform/wtf/forward.h"
#include "third_party/blink/renderer/platform/wtf/hash_map.h"
#include "third_party/blink/renderer/platform/wtf/text/string_hash.h"
#include "third_party/blink/renderer/platform/wtf/wtf_size_t.h"
#include "v8/include/v8-profiler.h"
namespace blink {
class ProfilerFrame;
class ProfilerSample;
class ProfilerStack;
class ProfilerTrace;
class ScriptState;
// A hash uniquely identified by the substack associated with the node.
struct ProfilerNodeStackHash {
STATIC_ONLY(ProfilerNodeStackHash);
static bool Equal(const v8::CpuProfileNode* a, const v8::CpuProfileNode* b) {
return a->GetNodeId() == b->GetNodeId();
}
static unsigned GetHash(const v8::CpuProfileNode* node) {
return node->GetNodeId();
}
static const bool safe_to_compare_to_empty_or_deleted = false;
};
// A hash uniquely identified by the stack frame associated with the node.
struct ProfilerNodeFrameHash {
STATIC_ONLY(ProfilerNodeFrameHash);
static bool Equal(const v8::CpuProfileNode* a, const v8::CpuProfileNode* b) {
return a->GetFunctionName() == b->GetFunctionName() &&
a->GetScriptResourceName() == b->GetScriptResourceName() &&
a->GetLineNumber() == b->GetLineNumber() &&
a->GetColumnNumber() == b->GetColumnNumber();
}
static unsigned GetHash(const v8::CpuProfileNode* node) {
return StringHash::GetHash(node->GetFunctionNameStr()) ^
StringHash::GetHash(node->GetScriptResourceNameStr()) ^
DefaultHash<unsigned>::Hash().GetHash(node->GetLineNumber()) ^
DefaultHash<unsigned>::Hash().GetHash(node->GetColumnNumber());
}
static const bool safe_to_compare_to_empty_or_deleted = false;
};
// Produces a structurally compressed trace from a v8::CpuProfile relative to a
// time origin, and omits frames from cross-origin scripts that do not
// participate in CORS.
//
// The trace format is described at:
// https://wicg.github.io/js-self-profiling/#the-profilertrace-dictionary
class ProfilerTraceBuilder
: public GarbageCollectedFinalized<ProfilerTraceBuilder> {
public:
static ProfilerTrace* FromProfile(ScriptState*,
const v8::CpuProfile* profile,
const SecurityOrigin* allowed_origin,
base::TimeTicks time_origin);
explicit ProfilerTraceBuilder(ScriptState*,
const SecurityOrigin* allowed_origin,
base::TimeTicks time_origin);
void Trace(blink::Visitor*);
private:
// Adds a stack sample from V8 to the trace, performing necessary filtering
// and coalescing.
void AddSample(const v8::CpuProfileNode* node, base::TimeTicks timestamp);
// Obtains the stack ID of the substack with the given node as its leaf,
// performing origin-based filtering.
base::Optional<wtf_size_t> GetOrInsertStackId(const v8::CpuProfileNode* node);
// Obtains the frame ID of the stack frame represented by the given node.
wtf_size_t GetOrInsertFrameId(const v8::CpuProfileNode* node);
// Obtains the resource ID for the given resource name.
wtf_size_t GetOrInsertResourceId(const char* resource_name);
ProfilerTrace* GetTrace() const;
// Discards metadata frames and performs an origin check on the given stack
// frame, returning true if it either has the same origin as the profiler, or
// if it should be shared cross origin.
bool ShouldIncludeStackFrame(const KURL& script_url,
int script_id,
v8::CpuProfileNode::SourceType source_type,
bool script_shared_cross_origin);
Member<ScriptState> script_state_;
const SecurityOrigin* allowed_origin_;
const base::TimeTicks time_origin_;
Vector<String> resources_;
HeapVector<Member<ProfilerFrame>> frames_;
HeapVector<Member<ProfilerStack>> stacks_;
HeapVector<Member<ProfilerSample>> samples_;
// Maps V8-managed resource strings to their indices in the resources table.
HashMap<const char*, wtf_size_t> resource_map_;
HashMap<const v8::CpuProfileNode*, wtf_size_t, ProfilerNodeStackHash>
node_to_stack_map_;
HashMap<const v8::CpuProfileNode*, wtf_size_t, ProfilerNodeFrameHash>
node_to_frame_map_;
// A mapping from a V8 internal script ID to whether or not it passes the
// same-origin policy for the ScriptState that the trace belongs to.
HashMap<int, bool> script_same_origin_cache_;
DISALLOW_COPY_AND_ASSIGN(ProfilerTraceBuilder);
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_BINDINGS_CORE_V8_PROFILER_TRACE_BUILDER_H_
......@@ -696,7 +696,11 @@ core_dictionary_idl_files =
"timing/performance_mark_options.idl",
"timing/performance_measure_options.idl",
"timing/performance_observer_init.idl",
"timing/profiler_frame.idl",
"timing/profiler_init_options.idl",
"timing/profiler_sample.idl",
"timing/profiler_stack.idl",
"timing/profiler_trace.idl",
"trustedtypes/trusted_type_policy_options.idl",
"workers/worker_options.idl",
"workers/worklet_options.idl",
......
......@@ -4,8 +4,6 @@
// https://wicg.github.io/js-self-profiling/
typedef any ProfilerTrace;
[Exposed=(Window,Worker), RuntimeEnabled=ExperimentalJSProfiler]
interface Profiler {
readonly attribute DOMHighResTimeStamp sampleInterval;
......
// 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.
// https://wicg.github.io/js-self-profiling/#the-profilerframe-dictionary
dictionary ProfilerFrame {
required DOMString name;
unsigned long long resourceId;
unsigned long long line;
unsigned long long column;
};
......@@ -6,9 +6,11 @@
#include "base/time/time.h"
#include "build/build_config.h"
#include "third_party/blink/renderer/bindings/core/v8/profiler_trace_builder.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/core/timing/profiler_init_options.h"
#include "third_party/blink/renderer/core/timing/profiler_trace.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"
......@@ -123,8 +125,10 @@ void ProfilerGroup::StopProfiler(ScriptState* script_state,
v8::Local<v8::String> profiler_id =
V8String(isolate_, profiler->ProfilerId());
auto* profile = cpu_profiler_->StopProfiling(profiler_id);
// TODO(acomminos): Process v8::CpuProfile into JS Self-Profiling trace format
resolver->Resolve();
auto* trace = ProfilerTraceBuilder::FromProfile(
script_state, profile, profiler->SourceOrigin(), profiler->TimeOrigin());
resolver->Resolve(trace);
profile->Delete();
}
......
// 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.
// https://wicg.github.io/js-self-profiling/#the-profilersample-dictionary
dictionary ProfilerSample {
required DOMHighResTimeStamp timestamp;
unsigned long long stackId;
};
// 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.
// https://wicg.github.io/js-self-profiling/#the-profilerstack-dictionary
dictionary ProfilerStack {
unsigned long long parentId;
required unsigned long long frameId;
};
// 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.
// https://wicg.github.io/js-self-profiling/#the-profilertrace-dictionary
dictionary ProfilerTrace {
required sequence<DOMString> resources;
required sequence<ProfilerFrame> frames;
required sequence<ProfilerStack> stacks;
required sequence<ProfilerSample> samples;
};
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