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