blob: 5f8c06d05b551ad5d28ed8f7870796df57e19381 [file] [log] [blame]
// Copyright 2023 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/js_profiler/profiler_trace_builder.h"
#include "base/time/time.h"
#include "cobalt/dom/performance.h"
#include "cobalt/js_profiler/profiler_frame.h"
#include "cobalt/js_profiler/profiler_sample.h"
#include "cobalt/js_profiler/profiler_stack.h"
#include "cobalt/js_profiler/profiler_trace.h"
#include "v8/include/v8.h"
namespace cobalt {
namespace js_profiler {
ProfilerTrace ProfilerTraceBuilder::FromProfile(const v8::CpuProfile* profile,
base::TimeTicks time_origin) {
ProfilerTraceBuilder builder(time_origin);
if (profile) {
for (int i = 0; i < profile->GetSamplesCount(); i++) {
const auto* node = profile->GetSample(i);
auto timestamp = base::TimeTicks() +
base::Microseconds(profile->GetSampleTimestamp(i));
builder.AddSample(node, timestamp);
}
}
return builder.GetTrace();
}
ProfilerTraceBuilder::ProfilerTraceBuilder(base::TimeTicks time_origin)
: time_origin_(time_origin) {}
void ProfilerTraceBuilder::AddSample(const v8::CpuProfileNode* node,
base::TimeTicks timestamp) {
ProfilerSample sample;
auto relative_timestamp =
dom::Performance::MonotonicTimeToDOMHighResTimeStamp(time_origin_,
timestamp);
sample.set_timestamp(relative_timestamp);
absl::optional<uint64_t> stack_id = GetOrInsertStackId(node);
if (stack_id.has_value()) sample.set_stack_id(stack_id.value());
samples_.push_back(sample);
}
absl::optional<uint64_t> ProfilerTraceBuilder::GetOrInsertStackId(
const v8::CpuProfileNode* node) {
if (!node) return absl::nullopt;
if (!ShouldIncludeStackFrame(node))
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->second;
}
ProfilerStack stack;
uint64_t frame_id = GetOrInsertFrameId(node);
stack.set_frame_id(frame_id);
absl::optional<int> parent_stack_id = GetOrInsertStackId(node->GetParent());
if (parent_stack_id.has_value()) stack.set_parent_id(parent_stack_id.value());
uint64_t stack_id = stacks_.size();
stacks_.push_back(stack);
node_to_stack_map_[node] = stack_id;
return stack_id;
}
uint64_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->second;
ProfilerFrame frame;
std::string function_name(node->GetFunctionNameStr());
frame.set_name(function_name);
if (*node->GetScriptResourceNameStr() != '\0') {
uint64_t resource_id =
GetOrInsertResourceId(node->GetScriptResourceNameStr());
frame.set_resource_id(resource_id);
}
if (node->GetLineNumber() != v8::CpuProfileNode::kNoLineNumberInfo)
frame.set_line(node->GetLineNumber());
if (node->GetColumnNumber() != v8::CpuProfileNode::kNoColumnNumberInfo)
frame.set_column(node->GetColumnNumber());
uint64_t frame_id = frames_.size();
frames_.push_back(frame);
node_to_frame_map_[node] = frame_id;
return frame_id;
}
uint64_t ProfilerTraceBuilder::GetOrInsertResourceId(
const char* resource_name) {
auto existing_resource_id = resource_indices_.find(resource_name);
if (existing_resource_id != resource_indices_.end())
return existing_resource_id->second;
uint64_t resource_id = resources_.size();
resources_.push_back(resource_name);
resource_indices_[resource_name] = resource_id;
return resource_id;
}
ProfilerTrace ProfilerTraceBuilder::GetTrace() const {
ProfilerTrace trace;
trace.set_resources(resources_);
trace.set_frames(frames_);
trace.set_stacks(stacks_);
trace.set_samples(samples_);
return trace;
}
bool ProfilerTraceBuilder::ShouldIncludeStackFrame(
const v8::CpuProfileNode* node) {
DCHECK(node);
// Omit V8 metadata frames.
const v8::CpuProfileNode::SourceType source_type = node->GetSourceType();
if (source_type != v8::CpuProfileNode::kScript &&
source_type != v8::CpuProfileNode::kBuiltin &&
source_type != v8::CpuProfileNode::kCallback) {
return false;
}
// Attempt to attribute each stack frame to a script.
// - For JS functions, this is their own script.
// - For builtins, this is the first attributable caller script.
const v8::CpuProfileNode* resource_node = node;
if (source_type != v8::CpuProfileNode::kScript) {
while (resource_node &&
resource_node->GetScriptId() == v8::UnboundScript::kNoScriptId) {
resource_node = resource_node->GetParent();
}
}
if (!resource_node) return false;
int script_id = resource_node->GetScriptId();
// 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->second;
// insert in pair script_same_origin_cache_ (script_id, true)
script_same_origin_cache_[script_id] = true;
return true;
}
} // namespace js_profiler
} // namespace cobalt