1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "third_party/blink/renderer/bindings/core/v8/profiler_trace_builder.h"
6
7 #include "base/time/time.h"
8 #include "third_party/blink/renderer/bindings/core/v8/v8_profiler_frame.h"
9 #include "third_party/blink/renderer/bindings/core/v8/v8_profiler_sample.h"
10 #include "third_party/blink/renderer/bindings/core/v8/v8_profiler_stack.h"
11 #include "third_party/blink/renderer/bindings/core/v8/v8_profiler_trace.h"
12 #include "third_party/blink/renderer/core/timing/performance.h"
13 #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
14 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
15 #include "third_party/blink/renderer/platform/wtf/vector.h"
16 #include "v8/include/v8.h"
17
18 namespace blink {
19
FromProfile(ScriptState * script_state,const v8::CpuProfile * profile,const SecurityOrigin * allowed_origin,base::TimeTicks time_origin)20 ProfilerTrace* ProfilerTraceBuilder::FromProfile(
21 ScriptState* script_state,
22 const v8::CpuProfile* profile,
23 const SecurityOrigin* allowed_origin,
24 base::TimeTicks time_origin) {
25 TRACE_EVENT0("blink", "ProfilerTraceBuilder::FromProfile");
26 ProfilerTraceBuilder* builder = MakeGarbageCollected<ProfilerTraceBuilder>(
27 script_state, allowed_origin, time_origin);
28 if (profile) {
29 for (int i = 0; i < profile->GetSamplesCount(); i++) {
30 const auto* node = profile->GetSample(i);
31 auto timestamp = base::TimeTicks() + base::TimeDelta::FromMicroseconds(
32 profile->GetSampleTimestamp(i));
33 builder->AddSample(node, timestamp);
34 }
35 }
36 return builder->GetTrace();
37 }
38
ProfilerTraceBuilder(ScriptState * script_state,const SecurityOrigin * allowed_origin,base::TimeTicks time_origin)39 ProfilerTraceBuilder::ProfilerTraceBuilder(ScriptState* script_state,
40 const SecurityOrigin* allowed_origin,
41 base::TimeTicks time_origin)
42 : script_state_(script_state),
43 allowed_origin_(allowed_origin),
44 time_origin_(time_origin) {}
45
Trace(Visitor * visitor) const46 void ProfilerTraceBuilder::Trace(Visitor* visitor) const {
47 visitor->Trace(script_state_);
48 visitor->Trace(frames_);
49 visitor->Trace(stacks_);
50 visitor->Trace(samples_);
51 }
52
AddSample(const v8::CpuProfileNode * node,base::TimeTicks timestamp)53 void ProfilerTraceBuilder::AddSample(const v8::CpuProfileNode* node,
54 base::TimeTicks timestamp) {
55 auto* sample = ProfilerSample::Create();
56 auto relative_timestamp = Performance::MonotonicTimeToDOMHighResTimeStamp(
57 time_origin_, timestamp, true);
58
59 sample->setTimestamp(relative_timestamp);
60 if (base::Optional<wtf_size_t> stack_id = GetOrInsertStackId(node))
61 sample->setStackId(*stack_id);
62
63 samples_.push_back(sample);
64 }
65
GetOrInsertStackId(const v8::CpuProfileNode * node)66 base::Optional<wtf_size_t> ProfilerTraceBuilder::GetOrInsertStackId(
67 const v8::CpuProfileNode* node) {
68 if (!node)
69 return base::Optional<wtf_size_t>();
70
71 // Omit frames that don't pass a cross-origin check.
72 // Do this at the stack level (rather than the frame level) to avoid
73 // including skeleton frames without data.
74 KURL resource_url(node->GetScriptResourceNameStr());
75 if (!ShouldIncludeStackFrame(resource_url, node->GetScriptId(),
76 node->GetSourceType(),
77 node->IsScriptSharedCrossOrigin())) {
78 return GetOrInsertStackId(node->GetParent());
79 }
80
81 auto existing_stack_id = node_to_stack_map_.find(node);
82 if (existing_stack_id != node_to_stack_map_.end()) {
83 // If we found a stack entry for this node ID, the subpath to the root
84 // already exists in the trace, and we may coalesce.
85 return existing_stack_id->value;
86 }
87
88 auto* stack = ProfilerStack::Create();
89 wtf_size_t frame_id = GetOrInsertFrameId(node);
90 stack->setFrameId(frame_id);
91 if (base::Optional<int> parent_stack_id =
92 GetOrInsertStackId(node->GetParent()))
93 stack->setParentId(*parent_stack_id);
94
95 wtf_size_t stack_id = stacks_.size();
96 stacks_.push_back(stack);
97 node_to_stack_map_.Set(node, stack_id);
98 return stack_id;
99 }
100
GetOrInsertFrameId(const v8::CpuProfileNode * node)101 wtf_size_t ProfilerTraceBuilder::GetOrInsertFrameId(
102 const v8::CpuProfileNode* node) {
103 auto existing_frame_id = node_to_frame_map_.find(node);
104
105 if (existing_frame_id != node_to_frame_map_.end())
106 return existing_frame_id->value;
107
108 auto* frame = ProfilerFrame::Create();
109 frame->setName(node->GetFunctionNameStr());
110 if (*node->GetScriptResourceNameStr() != '\0') {
111 wtf_size_t resource_id =
112 GetOrInsertResourceId(node->GetScriptResourceNameStr());
113 frame->setResourceId(resource_id);
114 }
115 if (node->GetLineNumber() != v8::CpuProfileNode::kNoLineNumberInfo)
116 frame->setLine(node->GetLineNumber());
117 if (node->GetColumnNumber() != v8::CpuProfileNode::kNoColumnNumberInfo)
118 frame->setColumn(node->GetColumnNumber());
119
120 wtf_size_t frame_id = frames_.size();
121 frames_.push_back(frame);
122 node_to_frame_map_.Set(node, frame_id);
123
124 return frame_id;
125 }
126
GetOrInsertResourceId(const char * resource_name)127 wtf_size_t ProfilerTraceBuilder::GetOrInsertResourceId(
128 const char* resource_name) {
129 // Since V8's CPU profiler already does string interning, pointer equality is
130 // value equality here.
131 auto existing_resource_id = resource_map_.find(resource_name);
132
133 if (existing_resource_id != resource_map_.end())
134 return existing_resource_id->value;
135
136 wtf_size_t resource_id = resources_.size();
137 resources_.push_back(resource_name);
138 resource_map_.Set(resource_name, resource_id);
139
140 return resource_id;
141 }
142
GetTrace() const143 ProfilerTrace* ProfilerTraceBuilder::GetTrace() const {
144 ProfilerTrace* trace = ProfilerTrace::Create();
145 trace->setResources(resources_);
146 trace->setFrames(frames_);
147 trace->setStacks(stacks_);
148 trace->setSamples(samples_);
149 return trace;
150 }
151
ShouldIncludeStackFrame(const KURL & script_url,int script_id,v8::CpuProfileNode::SourceType source_type,bool script_shared_cross_origin)152 bool ProfilerTraceBuilder::ShouldIncludeStackFrame(
153 const KURL& script_url,
154 int script_id,
155 v8::CpuProfileNode::SourceType source_type,
156 bool script_shared_cross_origin) {
157 // Omit V8 metadata frames.
158 if (source_type != v8::CpuProfileNode::kScript &&
159 source_type != v8::CpuProfileNode::kBuiltin &&
160 source_type != v8::CpuProfileNode::kCallback) {
161 return false;
162 }
163
164 // If we couldn't derive script data, only allow builtins and callbacks.
165 if (script_id == v8::UnboundScript::kNoScriptId) {
166 return source_type == v8::CpuProfileNode::kBuiltin ||
167 source_type == v8::CpuProfileNode::kCallback;
168 }
169
170 // If we already tested whether or not this script was cross-origin, return
171 // the cached results.
172 auto it = script_same_origin_cache_.find(script_id);
173 if (it != script_same_origin_cache_.end())
174 return it->value;
175
176 if (!script_url.IsValid())
177 return false;
178
179 auto origin = SecurityOrigin::Create(script_url);
180 // TODO(acomminos): Consider easing this check based on optional headers.
181 bool allowed =
182 script_shared_cross_origin || origin->IsSameOriginWith(allowed_origin_);
183 script_same_origin_cache_.Set(script_id, allowed);
184 return allowed;
185 }
186
187 } // namespace blink
188