v8/test/cctest/wasm/test-wasm-metrics.cc
Emanuel Ziegler 905aafab8b [wasm] Add CPU time metrics (reland)
This is a reland of dcdaf42fa8. It adds
CPU time metrics to the WasmModuleDecoded (except for streaming),
WasmModuleCompiled and WasmModuleTieredUp events. This can later be used
to provide this information as UKMs or UMAs.

Bug: v8:11611
Change-Id: I813fc8de36d1445c6a887abf496ec10e1a803815
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2953296
Reviewed-by: Adam Klein <adamk@chromium.org>
Reviewed-by: Clemens Backes <clemensb@chromium.org>
Commit-Queue: Emanuel Ziegler <ecmziegler@chromium.org>
Cr-Commit-Position: refs/heads/master@{#75715}
2021-07-14 08:01:41 +00:00

377 lines
14 KiB
C++

// Copyright 2020 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.
#include <memory>
#include "include/libplatform/libplatform.h"
#include "include/v8-metrics.h"
#include "src/api/api-inl.h"
#include "src/base/platform/time.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-module-builder.h"
#include "test/cctest/cctest.h"
#include "test/common/wasm/flag-utils.h"
#include "test/common/wasm/test-signatures.h"
#include "test/common/wasm/wasm-macro-gen.h"
#include "test/common/wasm/wasm-module-runner.h"
namespace v8 {
namespace internal {
namespace wasm {
namespace {
class MockPlatform final : public TestPlatform {
public:
MockPlatform() : task_runner_(std::make_shared<MockTaskRunner>()) {
// Now that it's completely constructed, make this the current platform.
i::V8::SetPlatformForTesting(this);
}
~MockPlatform() override {
for (auto* job_handle : job_handles_) job_handle->ResetPlatform();
}
std::unique_ptr<v8::JobHandle> PostJob(
v8::TaskPriority priority,
std::unique_ptr<v8::JobTask> job_task) override {
auto orig_job_handle = v8::platform::NewDefaultJobHandle(
this, priority, std::move(job_task), 1);
auto job_handle =
std::make_unique<MockJobHandle>(std::move(orig_job_handle), this);
job_handles_.insert(job_handle.get());
return job_handle;
}
std::shared_ptr<TaskRunner> GetForegroundTaskRunner(
v8::Isolate* isolate) override {
return task_runner_;
}
void CallOnWorkerThread(std::unique_ptr<v8::Task> task) override {
task_runner_->PostTask(std::move(task));
}
bool IdleTasksEnabled(v8::Isolate* isolate) override { return false; }
void ExecuteTasks() {
task_runner_->ExecuteTasks();
}
private:
class MockTaskRunner final : public TaskRunner {
public:
void PostTask(std::unique_ptr<v8::Task> task) override {
base::MutexGuard lock_scope(&tasks_lock_);
tasks_.push(std::move(task));
}
void PostNonNestableTask(std::unique_ptr<Task> task) override {
PostTask(std::move(task));
}
void PostDelayedTask(std::unique_ptr<Task> task,
double delay_in_seconds) override {
PostTask(std::move(task));
}
void PostNonNestableDelayedTask(std::unique_ptr<Task> task,
double delay_in_seconds) override {
PostTask(std::move(task));
}
void PostIdleTask(std::unique_ptr<IdleTask> task) override {
UNREACHABLE();
}
bool IdleTasksEnabled() override { return false; }
bool NonNestableTasksEnabled() const override { return true; }
bool NonNestableDelayedTasksEnabled() const override { return true; }
void ExecuteTasks() {
std::queue<std::unique_ptr<v8::Task>> tasks;
while (true) {
{
base::MutexGuard lock_scope(&tasks_lock_);
tasks.swap(tasks_);
}
if (tasks.empty()) break;
while (!tasks.empty()) {
std::unique_ptr<Task> task = std::move(tasks.front());
tasks.pop();
task->Run();
}
}
}
private:
base::Mutex tasks_lock_;
// We do not execute tasks concurrently, so we only need one list of tasks.
std::queue<std::unique_ptr<v8::Task>> tasks_;
};
class MockJobHandle : public JobHandle {
public:
explicit MockJobHandle(std::unique_ptr<JobHandle> orig_handle,
MockPlatform* platform)
: orig_handle_(std::move(orig_handle)), platform_(platform) {}
~MockJobHandle() {
if (platform_) platform_->job_handles_.erase(this);
}
void ResetPlatform() { platform_ = nullptr; }
void NotifyConcurrencyIncrease() override {
orig_handle_->NotifyConcurrencyIncrease();
}
void Join() override { orig_handle_->Join(); }
void Cancel() override { orig_handle_->Cancel(); }
void CancelAndDetach() override { orig_handle_->CancelAndDetach(); }
bool IsValid() override { return orig_handle_->IsValid(); }
bool IsActive() override { return orig_handle_->IsActive(); }
private:
std::unique_ptr<JobHandle> orig_handle_;
MockPlatform* platform_;
};
std::shared_ptr<MockTaskRunner> task_runner_;
std::unordered_set<MockJobHandle*> job_handles_;
};
enum class CompilationStatus {
kPending,
kFinished,
kFailed,
};
class TestInstantiateResolver : public InstantiationResultResolver {
public:
TestInstantiateResolver(Isolate* isolate, CompilationStatus* status,
std::string* error_message)
: isolate_(isolate), status_(status), error_message_(error_message) {}
void OnInstantiationSucceeded(
i::Handle<i::WasmInstanceObject> instance) override {
*status_ = CompilationStatus::kFinished;
}
void OnInstantiationFailed(i::Handle<i::Object> error_reason) override {
*status_ = CompilationStatus::kFailed;
Handle<String> str =
Object::ToString(isolate_, error_reason).ToHandleChecked();
error_message_->assign(str->ToCString().get());
}
private:
Isolate* isolate_;
CompilationStatus* const status_;
std::string* const error_message_;
};
class TestCompileResolver : public CompilationResultResolver {
public:
TestCompileResolver(CompilationStatus* status, std::string* error_message,
Isolate* isolate,
std::shared_ptr<NativeModule>* native_module)
: status_(status),
error_message_(error_message),
isolate_(isolate),
native_module_(native_module) {}
void OnCompilationSucceeded(i::Handle<i::WasmModuleObject> module) override {
if (!module.is_null()) {
*native_module_ = module->shared_native_module();
GetWasmEngine()->AsyncInstantiate(
isolate_,
std::make_unique<TestInstantiateResolver>(isolate_, status_,
error_message_),
module, MaybeHandle<JSReceiver>());
}
}
void OnCompilationFailed(i::Handle<i::Object> error_reason) override {
*status_ = CompilationStatus::kFailed;
Handle<String> str =
Object::ToString(CcTest::i_isolate(), error_reason).ToHandleChecked();
error_message_->assign(str->ToCString().get());
}
private:
CompilationStatus* const status_;
std::string* const error_message_;
Isolate* isolate_;
std::shared_ptr<NativeModule>* const native_module_;
};
} // namespace
#define RUN_COMPILE(name) \
MockPlatform mock_platform; \
CHECK_EQ(V8::GetCurrentPlatform(), &mock_platform); \
v8::Isolate::CreateParams create_params; \
create_params.array_buffer_allocator = CcTest::array_buffer_allocator(); \
v8::Isolate* isolate = v8::Isolate::New(create_params); \
{ \
v8::HandleScope handle_scope(isolate); \
v8::Local<v8::Context> context = v8::Context::New(isolate); \
v8::Context::Scope context_scope(context); \
Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate); \
testing::SetupIsolateForWasmModule(i_isolate); \
RunCompile_##name(&mock_platform, i_isolate); \
} \
isolate->Dispose();
#define COMPILE_TEST(name) \
void RunCompile_##name(MockPlatform*, i::Isolate*); \
UNINITIALIZED_TEST(Sync##name) { \
i::FlagScope<bool> sync_scope(&i::FLAG_wasm_async_compilation, false); \
RUN_COMPILE(name); \
} \
\
UNINITIALIZED_TEST(Async##name) { RUN_COMPILE(name); } \
\
UNINITIALIZED_TEST(Streaming##name) { \
i::FlagScope<bool> streaming_scope(&i::FLAG_wasm_test_streaming, true); \
RUN_COMPILE(name); \
} \
void RunCompile_##name(MockPlatform* platform, i::Isolate* isolate)
class MetricsRecorder : public v8::metrics::Recorder {
public:
std::vector<v8::metrics::WasmModuleDecoded> module_decoded_;
std::vector<v8::metrics::WasmModuleCompiled> module_compiled_;
std::vector<v8::metrics::WasmModuleInstantiated> module_instantiated_;
std::vector<v8::metrics::WasmModuleTieredUp> module_tiered_up_;
void AddMainThreadEvent(const v8::metrics::WasmModuleDecoded& event,
v8::metrics::Recorder::ContextId id) override {
CHECK(!id.IsEmpty());
module_decoded_.emplace_back(event);
}
void AddMainThreadEvent(const v8::metrics::WasmModuleCompiled& event,
v8::metrics::Recorder::ContextId id) override {
CHECK(!id.IsEmpty());
module_compiled_.emplace_back(event);
}
void AddMainThreadEvent(const v8::metrics::WasmModuleInstantiated& event,
v8::metrics::Recorder::ContextId id) override {
CHECK(!id.IsEmpty());
module_instantiated_.emplace_back(event);
}
void AddMainThreadEvent(const v8::metrics::WasmModuleTieredUp& event,
v8::metrics::Recorder::ContextId id) override {
CHECK(!id.IsEmpty());
module_tiered_up_.emplace_back(event);
}
};
COMPILE_TEST(TestEventMetrics) {
std::shared_ptr<MetricsRecorder> recorder =
std::make_shared<MetricsRecorder>();
reinterpret_cast<v8::Isolate*>(isolate)->SetMetricsRecorder(recorder);
if (v8::base::ThreadTicks::IsSupported()) {
v8::base::ThreadTicks::WaitUntilInitialized();
}
TestSignatures sigs;
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
WasmModuleBuilder* builder = zone.New<WasmModuleBuilder>(&zone);
WasmFunctionBuilder* f = builder->AddFunction(sigs.i_v());
f->builder()->AddExport(base::CStrVector("main"), f);
byte code[] = {WASM_I32V_2(0)};
f->EmitCode(code, sizeof(code));
f->Emit(kExprEnd);
ZoneBuffer buffer(&zone);
builder->WriteTo(&buffer);
auto enabled_features = WasmFeatures::FromIsolate(isolate);
CompilationStatus status = CompilationStatus::kPending;
std::string error_message;
std::shared_ptr<NativeModule> native_module;
GetWasmEngine()->AsyncCompile(
isolate, enabled_features,
std::make_shared<TestCompileResolver>(&status, &error_message, isolate,
&native_module),
ModuleWireBytes(buffer.begin(), buffer.end()), true,
"CompileAndInstantiateWasmModuleForTesting");
// Finish compilation tasks.
while (status == CompilationStatus::kPending) {
platform->ExecuteTasks();
}
platform->ExecuteTasks(); // Complete pending tasks beyond compilation.
CHECK_EQ(CompilationStatus::kFinished, status);
CHECK_EQ(1, recorder->module_decoded_.size());
CHECK(recorder->module_decoded_.back().success);
CHECK_EQ(i::FLAG_wasm_async_compilation,
recorder->module_decoded_.back().async);
CHECK_EQ(i::FLAG_wasm_test_streaming,
recorder->module_decoded_.back().streamed);
CHECK_EQ(buffer.size(),
recorder->module_decoded_.back().module_size_in_bytes);
CHECK_EQ(1, recorder->module_decoded_.back().function_count);
CHECK_LE(0, recorder->module_decoded_.back().wall_clock_duration_in_us);
CHECK_IMPLIES(
v8::base::ThreadTicks::IsSupported() && !i::FLAG_wasm_test_streaming,
recorder->module_decoded_.back().cpu_duration_in_us > 0);
CHECK_EQ(1, recorder->module_compiled_.size());
CHECK(recorder->module_compiled_.back().success);
CHECK_EQ(i::FLAG_wasm_async_compilation,
recorder->module_compiled_.back().async);
CHECK_EQ(i::FLAG_wasm_test_streaming,
recorder->module_compiled_.back().streamed);
CHECK(!recorder->module_compiled_.back().cached);
CHECK(!recorder->module_compiled_.back().deserialized);
CHECK(!recorder->module_compiled_.back().lazy);
CHECK_LT(0, recorder->module_compiled_.back().code_size_in_bytes);
// We currently cannot ensure that no code is attributed to Liftoff after the
// WasmModuleCompiled event has been emitted. We therefore only assume the
// liftoff_code_size() to be an upper limit for the reported size.
CHECK_GE(native_module->liftoff_code_size(),
recorder->module_compiled_.back().code_size_in_bytes);
CHECK_GE(native_module->generated_code_size(),
recorder->module_compiled_.back().code_size_in_bytes);
CHECK_LE(0, recorder->module_compiled_.back().wall_clock_duration_in_us);
CHECK_EQ(native_module->baseline_compilation_cpu_duration(),
recorder->module_compiled_.back().cpu_duration_in_us);
CHECK_IMPLIES(
v8::base::ThreadTicks::IsSupported() && !i::FLAG_wasm_test_streaming,
recorder->module_compiled_.back().cpu_duration_in_us > 0);
CHECK_EQ(1, recorder->module_instantiated_.size());
CHECK(recorder->module_instantiated_.back().success);
// We currently don't support true async instantiation.
CHECK(!recorder->module_instantiated_.back().async);
CHECK_EQ(0, recorder->module_instantiated_.back().imported_function_count);
CHECK_LE(0, recorder->module_instantiated_.back().wall_clock_duration_in_us);
CHECK_EQ(1, recorder->module_tiered_up_.size());
CHECK(!recorder->module_tiered_up_.back().lazy);
CHECK_LT(0, recorder->module_tiered_up_.back().code_size_in_bytes);
CHECK_GE(native_module->turbofan_code_size(),
recorder->module_tiered_up_.back().code_size_in_bytes);
CHECK_GE(native_module->generated_code_size(),
recorder->module_tiered_up_.back().code_size_in_bytes);
CHECK_GE(native_module->committed_code_space(),
recorder->module_tiered_up_.back().code_size_in_bytes);
CHECK_LE(0, recorder->module_tiered_up_.back().wall_clock_duration_in_us);
CHECK_EQ(native_module->tier_up_cpu_duration(),
recorder->module_tiered_up_.back().cpu_duration_in_us);
CHECK_IMPLIES(
v8::base::ThreadTicks::IsSupported() && i::FLAG_wasm_tier_up &&
i::FLAG_liftoff &&
recorder->module_compiled_.back().liftoff_bailout_count == 0,
recorder->module_tiered_up_.back().cpu_duration_in_us > 0);
}
} // namespace wasm
} // namespace internal
} // namespace v8