| // 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/bindings/core/v8/v8_profiler_frame.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_profiler_sample.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_profiler_stack.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_profiler_trace.h" |
| #include "third_party/blink/renderer/core/timing/performance.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); |
| if (profile) { |
| 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(Visitor* visitor) const { |
| 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->IsSameOriginWith(allowed_origin_); |
| script_same_origin_cache_.Set(script_id, allowed); |
| return allowed; |
| } |
| |
| } // namespace blink |