v8/test/cctest/test-sampler-api.cc

230 lines
7.1 KiB
C++
Raw Normal View History

// Copyright 2014 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Tests the sampling API in include/v8.h
#include <map>
#include <string>
Reland "[include] Split out v8.h" This is a reland of d1b27019d3bf86360ea838c317f8505fac6d3a7e Fixes include: Adding missing file to bazel build Forward-declaring classing before friend-classing them to fix win/gcc Add missing v8-isolate.h include for vtune builds Original change's description: > [include] Split out v8.h > > This moves every single class/function out of include/v8.h into a > separate header in include/, which v8.h then includes so that > externally nothing appears to have changed. > > Every include of v8.h from inside v8 has been changed to a more > fine-grained include. > > Previously inline functions defined at the bottom of v8.h would call > private non-inline functions in the V8 class. Since that class is now > in v8-initialization.h and is rarely included (as that would create > dependency cycles), this is not possible and so those methods have been > moved out of the V8 class into the namespace v8::api_internal. > > None of the previous files in include/ now #include v8.h, which means > if embedders were relying on this transitive dependency then it will > give compile failures. > > v8-inspector.h does depend on v8-scripts.h for the time being to ensure > that Chrome continue to compile but that change will be reverted once > those transitive #includes in chrome are changed to include it directly. > > Full design: > https://docs.google.com/document/d/1rTD--I8hCAr-Rho1WTumZzFKaDpEp0IJ8ejZtk4nJdA/edit?usp=sharing > > Bug: v8:11965 > Change-Id: I53b84b29581632710edc80eb11f819c2097a2877 > Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3097448 > Reviewed-by: Yang Guo <yangguo@chromium.org> > Reviewed-by: Camillo Bruni <cbruni@chromium.org> > Reviewed-by: Jakob Kummerow <jkummerow@chromium.org> > Reviewed-by: Leszek Swirski <leszeks@chromium.org> > Reviewed-by: Michael Lippautz <mlippautz@chromium.org> > Commit-Queue: Dan Elphick <delphick@chromium.org> > Cr-Commit-Position: refs/heads/main@{#76424} Cq-Include-Trybots: luci.v8.try:v8_linux_vtunejit Bug: v8:11965 Change-Id: I99f5d3a73bf8fe25b650adfaf9567dc4e44a09e6 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3113629 Reviewed-by: Leszek Swirski <leszeks@chromium.org> Reviewed-by: Camillo Bruni <cbruni@chromium.org> Reviewed-by: Michael Lippautz <mlippautz@chromium.org> Reviewed-by: Jakob Kummerow <jkummerow@chromium.org> Reviewed-by: Simon Zünd <szuend@chromium.org> Commit-Queue: Dan Elphick <delphick@chromium.org> Cr-Commit-Position: refs/heads/main@{#76460}
2021-08-23 13:01:06 +00:00
#include "include/v8-isolate.h"
#include "include/v8-local-handle.h"
#include "include/v8-template.h"
#include "include/v8-unwinder.h"
#include "src/flags/flags.h"
#include "test/cctest/cctest.h"
namespace {
class Sample {
public:
enum { kFramesLimit = 255 };
Sample() = default;
using const_iterator = const void* const*;
const_iterator begin() const { return data_.begin(); }
const_iterator end() const { return &data_[data_.length()]; }
int size() const { return data_.length(); }
v8::base::Vector<void*>& data() { return data_; }
private:
v8::base::EmbeddedVector<void*, kFramesLimit> data_;
};
class SamplingTestHelper {
public:
struct CodeEventEntry {
std::string name;
const void* code_start;
size_t code_len;
};
using CodeEntries = std::map<const void*, CodeEventEntry>;
explicit SamplingTestHelper(const std::string& test_function)
: sample_is_taken_(false), isolate_(CcTest::isolate()) {
CHECK(!instance_);
instance_ = this;
v8::HandleScope scope(isolate_);
v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate_);
global->Set(isolate_, "CollectSample",
v8::FunctionTemplate::New(isolate_, CollectSample));
LocalContext env(isolate_, nullptr, global);
isolate_->SetJitCodeEventHandler(v8::kJitCodeEventEnumExisting,
JitCodeEventHandler);
CompileRun(v8_str(test_function.c_str()));
}
~SamplingTestHelper() {
isolate_->SetJitCodeEventHandler(v8::kJitCodeEventDefault, nullptr);
instance_ = nullptr;
}
Sample& sample() { return sample_; }
const CodeEventEntry* FindEventEntry(const void* address) {
CodeEntries::const_iterator it = code_entries_.upper_bound(address);
if (it == code_entries_.begin()) return nullptr;
const CodeEventEntry& entry = (--it)->second;
const void* code_end =
static_cast<const uint8_t*>(entry.code_start) + entry.code_len;
return address < code_end ? &entry : nullptr;
}
private:
static void CollectSample(const v8::FunctionCallbackInfo<v8::Value>& args) {
instance_->DoCollectSample();
}
static void JitCodeEventHandler(const v8::JitCodeEvent* event) {
instance_->DoJitCodeEventHandler(event);
}
// The JavaScript calls this function when on full stack depth.
void DoCollectSample() {
v8::RegisterState state;
#if defined(USE_SIMULATOR)
SimulatorHelper simulator_helper;
if (!simulator_helper.Init(isolate_)) return;
simulator_helper.FillRegisters(&state);
#else
state.pc = nullptr;
state.fp = &state;
state.sp = &state;
#endif
v8::SampleInfo info;
isolate_->GetStackSample(state, sample_.data().begin(),
static_cast<size_t>(sample_.size()), &info);
size_t frames_count = info.frames_count;
CHECK_LE(frames_count, static_cast<size_t>(sample_.size()));
sample_.data().Truncate(static_cast<int>(frames_count));
sample_is_taken_ = true;
}
void DoJitCodeEventHandler(const v8::JitCodeEvent* event) {
if (sample_is_taken_) return;
switch (event->type) {
case v8::JitCodeEvent::CODE_ADDED: {
CodeEventEntry entry;
entry.name = std::string(event->name.str, event->name.len);
entry.code_start = event->code_start;
entry.code_len = event->code_len;
code_entries_.insert(std::make_pair(entry.code_start, entry));
break;
}
case v8::JitCodeEvent::CODE_MOVED: {
CodeEntries::iterator it = code_entries_.find(event->code_start);
CHECK(it != code_entries_.end());
code_entries_.erase(it);
CodeEventEntry entry;
entry.name = std::string(event->name.str, event->name.len);
entry.code_start = event->new_code_start;
entry.code_len = event->code_len;
code_entries_.insert(std::make_pair(entry.code_start, entry));
break;
}
case v8::JitCodeEvent::CODE_REMOVED:
code_entries_.erase(event->code_start);
break;
default:
break;
}
}
Sample sample_;
bool sample_is_taken_;
v8::Isolate* isolate_;
CodeEntries code_entries_;
static SamplingTestHelper* instance_;
};
SamplingTestHelper* SamplingTestHelper::instance_;
} // namespace
// A JavaScript function which takes stack depth
// (minimum value 2) as an argument.
// When at the bottom of the recursion,
// the JavaScript code calls into C++ test code,
// waiting for the sampler to take a sample.
static const char* test_function =
"function func(depth) {"
" if (depth == 2) CollectSample();"
" else return func(depth - 1);"
"}";
TEST(StackDepthIsConsistent) {
SamplingTestHelper helper(std::string(test_function) + "func(8);");
CHECK_EQ(8, helper.sample().size());
}
TEST(StackDepthDoesNotExceedMaxValue) {
SamplingTestHelper helper(std::string(test_function) + "func(300);");
CHECK_EQ(Sample::kFramesLimit, helper.sample().size());
}
static const char* test_function_call_builtin =
"function func(depth) {"
" if (depth == 2) CollectSample();"
" else return [0].forEach(function recurse() { func(depth - 1) });"
"}";
TEST(BuiltinsInSamples) {
SamplingTestHelper helper(std::string(test_function_call_builtin) +
"func(10);");
Sample& sample = helper.sample();
CHECK_EQ(26, sample.size());
for (int i = 0; i < 20; i++) {
const SamplingTestHelper::CodeEventEntry* entry;
entry = helper.FindEventEntry(sample.begin()[i]);
switch (i % 3) {
case 0:
CHECK(std::string::npos != entry->name.find("func"));
break;
case 1:
CHECK(std::string::npos != entry->name.find("recurse"));
break;
case 2:
CHECK(std::string::npos != entry->name.find("ArrayForEach"));
break;
}
}
}
// The captured sample should have three pc values.
// They should fall in the range where the compiled code resides.
// The expected stack is:
// bottom of stack [{anon script}, outer, inner] top of stack
// ^ ^ ^
// sample.stack indices 2 1 0
TEST(StackFramesConsistent) {
i::FLAG_allow_natives_syntax = true;
const char* test_script =
"function test_sampler_api_inner() {"
" CollectSample();"
" return 0;"
"}"
"function test_sampler_api_outer() {"
" return test_sampler_api_inner();"
"}"
"%NeverOptimizeFunction(test_sampler_api_inner);"
"%NeverOptimizeFunction(test_sampler_api_outer);"
"test_sampler_api_outer();";
SamplingTestHelper helper(test_script);
Sample& sample = helper.sample();
CHECK_EQ(3, sample.size());
const SamplingTestHelper::CodeEventEntry* entry;
entry = helper.FindEventEntry(sample.begin()[0]);
CHECK(entry);
CHECK(std::string::npos != entry->name.find("test_sampler_api_inner"));
entry = helper.FindEventEntry(sample.begin()[1]);
CHECK(entry);
CHECK(std::string::npos != entry->name.find("test_sampler_api_outer"));
}