[ukm] Add framework for collecting event-based metrics

Add a framework for collecting event-based metrics like UKMs in V8
that is independent of the actual implementation.

Design doc: https://docs.google.com/document/d/1vCZQCh4B05isqwJOwTPv7WqcnVp4KJITMgsHSBg35ZI/

R=ulan@chromium.org

Bug: chromium:1101749
Change-Id: If3a5b954d1f0bcee4e06a03467b651feae378a5f
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2288231
Commit-Queue: Emanuel Ziegler <ecmziegler@chromium.org>
Reviewed-by: Ross McIlroy <rmcilroy@chromium.org>
Reviewed-by: Ulan Degenbaev <ulan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#69098}
This commit is contained in:
Emanuel Ziegler 2020-07-28 13:11:06 +02:00 committed by Commit Bot
parent 1250fd59aa
commit 367da30543
10 changed files with 583 additions and 2 deletions

View File

@ -2221,6 +2221,7 @@ v8_source_set("v8_base_without_compiler") {
"include/v8-inspector-protocol.h",
"include/v8-inspector.h",
"include/v8-internal.h",
"include/v8-metrics.h",
"include/v8-platform.h",
"include/v8-profiler.h",
"include/v8-util.h",
@ -2721,6 +2722,8 @@ v8_source_set("v8_base_without_compiler") {
"src/logging/log-utils.h",
"src/logging/log.cc",
"src/logging/log.h",
"src/logging/metrics.cc",
"src/logging/metrics.h",
"src/logging/off-thread-logger.h",
"src/logging/tracing-flags.cc",
"src/logging/tracing-flags.h",

105
include/v8-metrics.h Normal file
View File

@ -0,0 +1,105 @@
// 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.
#ifndef V8_METRICS_H_
#define V8_METRICS_H_
#include "v8.h" // NOLINT(build/include_directory)
namespace v8 {
namespace metrics {
struct WasmModuleDecoded {
bool async = false;
bool streamed = false;
bool success = false;
size_t module_size_in_bytes = 0;
size_t function_count = 0;
int64_t wall_clock_time_in_us = 0;
};
struct WasmModuleCompiled {
bool async = false;
bool streamed = false;
bool cached = false;
bool deserialized = false;
bool lazy = false;
bool success = false;
size_t code_size_in_bytes = 0;
size_t liftoff_bailout_count = 0;
int64_t wall_clock_time_in_us = 0;
};
struct WasmModuleInstantiated {
bool async = false;
bool success = false;
size_t imported_function_count = 0;
int64_t wall_clock_time_in_us = 0;
};
struct WasmModuleTieredUp {
bool lazy = false;
size_t code_size_in_bytes = 0;
int64_t wall_clock_time_in_us = 0;
};
struct WasmModulesPerIsolate {
size_t count = 0;
};
#define V8_MAIN_THREAD_METRICS_EVENTS(V) \
V(WasmModuleDecoded) \
V(WasmModuleCompiled) \
V(WasmModuleInstantiated) \
V(WasmModuleTieredUp)
#define V8_THREAD_SAFE_METRICS_EVENTS(V) V(WasmModulesPerIsolate)
/**
* This class serves as a base class for recording event-based metrics in V8.
* There a two kinds of metrics, those which are expected to be thread-safe and
* whose implementation is required to fulfill this requirement and those whose
* implementation does not have that requirement and only needs to be
* executable on the main thread. If such an event is triggered from a
* background thread, it will be delayed and executed by the foreground task
* runner.
*
* The thread-safe events are listed in the V8_THREAD_SAFE_METRICS_EVENTS
* macro above while the main thread event are listed in
* V8_MAIN_THREAD_METRICS_EVENTS above. For the former, a virtual method
* AddMainThreadEvent(const E& event, v8::Context::Token token) will be
* generated and for the latter AddThreadSafeEvent(const E& event).
*
* Thread-safe events are not allowed to access the context and therefore do
* not carry a context token with them. These tokens can be generated from
* contexts using GetToken() and the token will be valid as long as the isolate
* and the context live. It is not guaranteed that the token will still resolve
* to a valid context using v8::Context::GetByToken() at the time the metric is
* recorded. In this case, an empty handle will be returned.
*
* The embedder is expected to call v8::Isolate::SetMetricsRecorder()
* providing its implementation and have the virtual methods overwritten
* for the events it cares about.
*/
class Recorder {
public:
virtual ~Recorder() = default;
#define ADD_MAIN_THREAD_EVENT(E) \
virtual void AddMainThreadEvent(const E& event, v8::Context::Token token) {}
V8_MAIN_THREAD_METRICS_EVENTS(ADD_MAIN_THREAD_EVENT)
#undef ADD_MAIN_THREAD_EVENT
#define ADD_THREAD_SAFE_EVENT(E) \
virtual void AddThreadSafeEvent(const E& event) {}
V8_THREAD_SAFE_METRICS_EVENTS(ADD_THREAD_SAFE_EVENT)
#undef ADD_THREAD_SAFE_EVENT
virtual void NotifyIsolateDisposal() {}
};
} // namespace metrics
} // namespace v8
#endif // V8_METRICS_H_

View File

@ -149,6 +149,10 @@ class StreamingDecoder;
} // namespace internal
namespace metrics {
class Recorder;
} // namespace metrics
namespace debug {
class ConsoleCallArguments;
} // namespace debug
@ -9139,6 +9143,18 @@ class V8_EXPORT Isolate {
void SetCreateHistogramFunction(CreateHistogramCallback);
void SetAddHistogramSampleFunction(AddHistogramSampleCallback);
/**
* Enables the host application to provide a mechanism for recording
* event based metrics. In order to use this interface
* include/v8-metrics.h
* needs to be included and the recorder needs to be derived from the
* Recorder base class defined there.
* This method can only be called once per isolate and must happen during
* isolate initialization before background threads are spawned.
*/
void SetMetricsRecorder(
const std::shared_ptr<metrics::Recorder>& metrics_recorder);
/**
* Enables the host application to provide a mechanism for recording a
* predefined set of data as crash keys to be used in postmortem debugging in
@ -10495,6 +10511,33 @@ class V8_EXPORT Context {
const BackupIncumbentScope* prev_ = nullptr;
};
// A unique token for a context in this Isolate.
// It is guaranteed to not be reused throughout the lifetime of the Isolate.
class Token {
public:
Token() : token_(kEmptyToken) {}
bool IsEmpty() const { return token_ == kEmptyToken; }
static const Token Empty() { return Token{kEmptyToken}; }
bool operator==(const Token& other) const { return token_ == other.token_; }
bool operator!=(const Token& other) const { return token_ != other.token_; }
private:
friend class Context;
friend class internal::Isolate;
explicit Token(uintptr_t token) : token_(token) {}
static constexpr uintptr_t kEmptyToken = 0;
uintptr_t token_;
};
// Return the context with the given token or an empty handle if the context
// was already garbage collected.
static MaybeLocal<Context> GetByToken(Isolate* isolate, Token token);
v8::Context::Token GetToken();
private:
friend class Value;
friend class Script;

View File

@ -58,6 +58,7 @@
#include "src/json/json-parser.h"
#include "src/json/json-stringifier.h"
#include "src/logging/counters.h"
#include "src/logging/metrics.h"
#include "src/logging/tracing-flags.h"
#include "src/numbers/conversions-inl.h"
#include "src/objects/api-callbacks.h"
@ -6149,6 +6150,19 @@ void Context::SetContinuationPreservedEmbedderData(Local<Value> data) {
*i::Handle<i::HeapObject>::cast(Utils::OpenHandle(*data)));
}
MaybeLocal<Context> Context::GetByToken(Isolate* isolate,
Context::Token token) {
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
return i_isolate->GetContextFromToken(token);
}
v8::Context::Token Context::GetToken() {
i::Handle<i::Context> context = Utils::OpenHandle(this);
i::Isolate* isolate = context->GetIsolate();
return isolate->GetOrRegisterContextToken(
handle(context->native_context(), isolate));
}
namespace {
i::Address* GetSerializedDataFromFixedArray(i::Isolate* isolate,
i::FixedArray list, size_t index) {
@ -8783,6 +8797,12 @@ void Isolate::SetAddHistogramSampleFunction(
->SetAddHistogramSampleFunction(callback);
}
void Isolate::SetMetricsRecorder(
const std::shared_ptr<metrics::Recorder>& metrics_recorder) {
reinterpret_cast<i::Isolate*>(this)->metrics_recorder()->SetRecorder(
metrics_recorder);
}
void Isolate::SetAddCrashKeyCallback(AddCrashKeyCallback callback) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(this);
isolate->SetAddCrashKeyCallback(callback);

View File

@ -55,6 +55,7 @@
#include "src/libsampler/sampler.h"
#include "src/logging/counters.h"
#include "src/logging/log.h"
#include "src/logging/metrics.h"
#include "src/numbers/hash-seed-inl.h"
#include "src/objects/backing-store.h"
#include "src/objects/elements.h"
@ -3037,6 +3038,8 @@ void Isolate::Deinit() {
heap_profiler()->StopSamplingHeapProfiler();
}
metrics_recorder_->NotifyIsolateDisposal();
#if defined(V8_OS_WIN64)
if (win64_unwindinfo::CanRegisterUnwindInfoForNonABICompliantCodeRange() &&
heap()->memory_allocator() && RequiresCodeRange()) {
@ -3511,6 +3514,8 @@ bool Isolate::Init(ReadOnlyDeserializer* read_only_deserializer,
// Enable logging before setting up the heap
logger_->SetUp(this);
metrics_recorder_ = std::make_shared<metrics::Recorder>(this);
{ // NOLINT
// Ensure that the thread has a valid stack guard. The v8::Locker object
// will ensure this too, but we don't have to use lockers if we are only
@ -4678,6 +4683,42 @@ bool Isolate::RequiresCodeRange() const {
return kPlatformRequiresCodeRange && !jitless_;
}
v8::Context::Token Isolate::GetOrRegisterContextToken(
Handle<NativeContext> context) {
if (serializer_enabled_) return v8::Context::Token::Empty();
i::Object token = context->context_token();
if (token.IsNullOrUndefined()) {
CHECK_LT(last_context_token_, i::Smi::kMaxValue);
context->set_context_token(i::Smi::FromIntptr(++last_context_token_));
v8::HandleScope handle_scope(reinterpret_cast<v8::Isolate*>(this));
auto result = context_token_map_.emplace(
std::piecewise_construct, std::forward_as_tuple(last_context_token_),
std::forward_as_tuple(reinterpret_cast<v8::Isolate*>(this),
ToApiHandle<v8::Context>(context)));
result.first->second.SetWeak(reinterpret_cast<void*>(last_context_token_),
RemoveContextTokenCallback,
v8::WeakCallbackType::kParameter);
return v8::Context::Token(last_context_token_);
} else {
DCHECK(token.IsSmi());
return v8::Context::Token(static_cast<uintptr_t>(i::Smi::ToInt(token)));
}
}
MaybeLocal<v8::Context> Isolate::GetContextFromToken(v8::Context::Token token) {
auto result = context_token_map_.find(token.token_);
if (result == context_token_map_.end() || result->second.IsEmpty())
return MaybeLocal<v8::Context>();
return result->second.Get(reinterpret_cast<v8::Isolate*>(this));
}
void Isolate::RemoveContextTokenCallback(
const v8::WeakCallbackInfo<void>& data) {
Isolate* isolate = reinterpret_cast<Isolate*>(data.GetIsolate());
uintptr_t token = reinterpret_cast<uintptr_t>(data.GetParameter());
isolate->context_token_map_.erase(token);
}
// |chunk| is either a Page or an executable LargePage.
void Isolate::RemoveCodeMemoryChunk(MemoryChunk* chunk) {
// We only keep track of individual code pages/allocations if we are on arm32,

View File

@ -124,6 +124,10 @@ namespace win64_unwindinfo {
class BuiltinUnwindInfo;
}
namespace metrics {
class Recorder;
} // namespace metrics
#define RETURN_FAILURE_IF_SCHEDULED_EXCEPTION(isolate) \
do { \
Isolate* __isolate__ = (isolate); \
@ -926,6 +930,9 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory {
DCHECK_NOT_NULL(async_counters_.get());
return async_counters_;
}
const std::shared_ptr<metrics::Recorder>& metrics_recorder() {
return metrics_recorder_;
}
RuntimeProfiler* runtime_profiler() { return runtime_profiler_; }
CompilationCache* compilation_cache() { return compilation_cache_; }
Logger* logger() {
@ -1542,6 +1549,9 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory {
static Address load_from_stack_count_address(const char* function_name);
static Address store_to_stack_count_address(const char* function_name);
v8::Context::Token GetOrRegisterContextToken(Handle<NativeContext> context);
MaybeLocal<v8::Context> GetContextFromToken(v8::Context::Token token);
private:
explicit Isolate(std::unique_ptr<IsolateAllocator> isolate_allocator);
~Isolate();
@ -1554,6 +1564,9 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory {
void InitializeCodeRanges();
void AddCodeMemoryRange(MemoryRange range);
static void RemoveContextTokenCallback(
const v8::WeakCallbackInfo<void>& data);
class ThreadDataTable {
public:
ThreadDataTable() = default;
@ -1813,6 +1826,13 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory {
v8::Isolate::UseCounterCallback use_counter_callback_ = nullptr;
std::shared_ptr<metrics::Recorder> metrics_recorder_;
uintptr_t last_context_token_ = 0;
std::unordered_map<
uintptr_t,
Persistent<v8::Context, v8::CopyablePersistentTraits<v8::Context>>>
context_token_map_;
std::vector<Object> startup_object_cache_;
// Used during builtins compilation to build the builtins constants table,

63
src/logging/metrics.cc Normal file
View File

@ -0,0 +1,63 @@
// 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 "src/logging/metrics.h"
#include "include/v8-platform.h"
namespace v8 {
namespace internal {
namespace metrics {
class Recorder::Task : public v8::Task {
public:
explicit Task(const std::shared_ptr<Recorder>& recorder)
: recorder_(recorder) {}
void Run() override {
std::queue<std::unique_ptr<Recorder::DelayedEventBase>> delayed_events;
{
base::MutexGuard lock_scope(&recorder_->lock_);
delayed_events.swap(recorder_->delayed_events_);
}
while (!delayed_events.empty()) {
delayed_events.front()->Run(recorder_);
delayed_events.pop();
}
}
private:
std::shared_ptr<Recorder> recorder_;
};
Recorder::Recorder(Isolate* isolate)
: foreground_task_runner_(V8::GetCurrentPlatform()->GetForegroundTaskRunner(
reinterpret_cast<v8::Isolate*>(isolate))),
embedder_recorder_(nullptr) {}
void Recorder::SetRecorder(
const std::shared_ptr<v8::metrics::Recorder>& embedder_recorder) {
CHECK_NULL(embedder_recorder_);
embedder_recorder_ = embedder_recorder;
}
void Recorder::NotifyIsolateDisposal() {
if (embedder_recorder_) {
embedder_recorder_->NotifyIsolateDisposal();
}
}
void Recorder::Delay(std::unique_ptr<Recorder::DelayedEventBase>&& event) {
base::MutexGuard lock_scope(&lock_);
bool was_empty = delayed_events_.empty();
delayed_events_.push(std::move(event));
if (was_empty) {
foreground_task_runner_->PostDelayedTask(
std::make_unique<Task>(shared_from_this()), 1.0);
}
}
} // namespace metrics
} // namespace internal
} // namespace v8

104
src/logging/metrics.h Normal file
View File

@ -0,0 +1,104 @@
// 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.
#ifndef V8_LOGGING_METRICS_H_
#define V8_LOGGING_METRICS_H_
#include <memory>
#include <queue>
#include "include/v8-metrics.h"
#include "src/base/platform/mutex.h"
#include "src/base/platform/time.h"
#include "src/init/v8.h"
namespace v8 {
class TaskRunner;
namespace internal {
namespace metrics {
class Recorder : public std::enable_shared_from_this<Recorder> {
public:
explicit V8_EXPORT_PRIVATE Recorder(Isolate* isolate);
V8_EXPORT_PRIVATE void SetRecorder(
const std::shared_ptr<v8::metrics::Recorder>& embedder_recorder);
V8_EXPORT_PRIVATE void NotifyIsolateDisposal();
template <class T>
void AddMainThreadEvent(const T& event, v8::Context::Token token) {
if (embedder_recorder_)
embedder_recorder_->AddMainThreadEvent(event, token);
}
template <class T>
void DelayMainThreadEvent(const T& event, v8::Context::Token token) {
if (!embedder_recorder_) return;
Delay(std::make_unique<DelayedEvent<T>>(event, token));
}
template <class T>
void AddThreadSafeEvent(const T& event) {
if (embedder_recorder_) embedder_recorder_->AddThreadSafeEvent(event);
}
private:
class DelayedEventBase {
public:
virtual ~DelayedEventBase() = default;
virtual void Run(const std::shared_ptr<Recorder>& recorder) = 0;
};
template <class T>
class DelayedEvent : public DelayedEventBase {
public:
DelayedEvent(const T& event, v8::Context::Token token)
: event_(event), token_(token) {}
void Run(const std::shared_ptr<Recorder>& recorder) override {
recorder->AddMainThreadEvent(event_, token_);
}
protected:
T event_;
v8::Context::Token token_;
};
class Task;
V8_EXPORT_PRIVATE void Delay(
std::unique_ptr<Recorder::DelayedEventBase>&& event);
base::Mutex lock_;
std::shared_ptr<v8::TaskRunner> foreground_task_runner_;
std::shared_ptr<v8::metrics::Recorder> embedder_recorder_;
std::queue<std::unique_ptr<DelayedEventBase>> delayed_events_;
};
template <class T, int64_t (base::TimeDelta::*precision)() const =
&base::TimeDelta::InMicroseconds>
class TimedScope {
public:
TimedScope(T* event, int64_t T::*time)
: event_(event), time_(time), start_time_(base::TimeTicks::Now()) {}
~TimedScope() {
base::TimeDelta duration = base::TimeTicks::Now() - start_time_;
event_->*time_ = (duration.*precision)();
}
private:
T* event_;
int64_t T::*time_;
base::TimeTicks start_time_;
};
} // namespace metrics
} // namespace internal
} // namespace v8
#endif // V8_LOGGING_METRICS_H_

View File

@ -91,6 +91,7 @@ enum ContextLookupFlags {
V(CALL_ASYNC_MODULE_REJECTED, JSFunction, call_async_module_rejected) \
V(CALLSITE_FUNCTION_INDEX, JSFunction, callsite_function) \
V(CONTEXT_EXTENSION_FUNCTION_INDEX, JSFunction, context_extension_function) \
V(CONTEXT_TOKEN, Object, context_token) \
V(DATA_PROPERTY_DESCRIPTOR_MAP_INDEX, Map, data_property_descriptor_map) \
V(DATA_VIEW_FUN_INDEX, JSFunction, data_view_fun) \
V(DATE_FUNCTION_INDEX, JSFunction, date_function) \

View File

@ -25,14 +25,14 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "test/cctest/test-api.h"
#include <climits>
#include <csignal>
#include <map>
#include <memory>
#include <string>
#include "test/cctest/test-api.h"
#if V8_OS_POSIX
#include <unistd.h> // NOLINT
#endif
@ -53,6 +53,7 @@
#include "src/heap/heap-inl.h"
#include "src/heap/incremental-marking.h"
#include "src/heap/local-allocator.h"
#include "src/logging/metrics.h"
#include "src/objects/feedback-vector-inl.h"
#include "src/objects/feedback-vector.h"
#include "src/objects/hash-table-inl.h"
@ -27884,3 +27885,183 @@ TEST(FastApiCalls) {
// TODO(mslekova): Add tests for FTI that requires access check.
#endif // V8_LITE_MODE
}
THREADED_TEST(GetContextByToken) {
using v8::Context;
using v8::Local;
using v8::MaybeLocal;
// Set up isolate and context.
v8::Isolate* iso = CcTest::isolate();
Context::Token original_token;
std::vector<Context::Token> tokens;
{
v8::HandleScope scope(iso);
Local<Context> context = Context::New(iso);
// Ensure that we get a valid token.
original_token = context->GetToken();
CHECK(!original_token.IsEmpty());
// Request many tokens to ensure correct growth behavior.
for (size_t count = 0; count < 50; ++count) {
Local<Context> temp_context = Context::New(iso);
tokens.push_back(temp_context->GetToken());
}
for (const Context::Token& token : tokens) {
CHECK(!Context::GetByToken(iso, token).IsEmpty());
}
// Ensure that we can get the context from the token.
MaybeLocal<Context> retrieved_context =
Context::GetByToken(iso, original_token);
CHECK_EQ(context, retrieved_context.ToLocalChecked());
// Ensure that an empty token returns an empty handle.
retrieved_context = Context::GetByToken(iso, Context::Token::Empty());
CHECK(retrieved_context.IsEmpty());
// Ensure that repeated token accesses return the same token.
Context::Token new_token = context->GetToken();
CHECK_EQ(original_token, new_token);
}
// Invalidate the context and therefore the token.
CcTest::PreciseCollectAllGarbage();
// Ensure that a stale token returns an empty handle.
{
v8::HandleScope scope(iso);
MaybeLocal<Context> retrieved_context =
Context::GetByToken(iso, original_token);
CHECK(retrieved_context.IsEmpty());
for (const Context::Token& token : tokens) {
CHECK(Context::GetByToken(iso, token).IsEmpty());
}
}
}
namespace {
class MetricsRecorder : public v8::metrics::Recorder {
public:
v8::Isolate* isolate_;
size_t count_ = 0;
size_t module_count_ = 0;
int64_t time_in_us_ = -1;
explicit MetricsRecorder(v8::Isolate* isolate) : isolate_(isolate) {}
void AddMainThreadEvent(const v8::metrics::WasmModuleDecoded& event,
Context::Token token) override {
if (Context::GetByToken(isolate_, token).IsEmpty()) return;
++count_;
time_in_us_ = event.wall_clock_time_in_us;
}
void AddThreadSafeEvent(
const v8::metrics::WasmModulesPerIsolate& event) override {
++count_;
module_count_ = event.count;
}
};
} // namespace
TEST(TriggerMainThreadMetricsEvent) {
using v8::Context;
using v8::Local;
using v8::MaybeLocal;
// Set up isolate and context.
v8::Isolate* iso = CcTest::isolate();
i::Isolate* i_iso = reinterpret_cast<i::Isolate*>(iso);
CHECK(i_iso->metrics_recorder());
v8::metrics::WasmModuleDecoded event;
Context::Token token;
std::shared_ptr<MetricsRecorder> recorder =
std::make_shared<MetricsRecorder>(iso);
iso->SetMetricsRecorder(recorder);
{
v8::HandleScope scope(iso);
Local<Context> context = Context::New(iso);
token = context->GetToken();
// Check that event submission works.
{
i::metrics::TimedScope<v8::metrics::WasmModuleDecoded> timed_scope(
&event, &v8::metrics::WasmModuleDecoded::wall_clock_time_in_us);
v8::base::OS::Sleep(v8::base::TimeDelta::FromMilliseconds(100));
}
i_iso->metrics_recorder()->AddMainThreadEvent(event, token);
CHECK_EQ(recorder->count_, 1); // Increased.
CHECK_GT(recorder->time_in_us_, 100);
}
CcTest::PreciseCollectAllGarbage();
// Check that event submission doesn't break even if the token is invalid.
i_iso->metrics_recorder()->AddMainThreadEvent(event, token);
CHECK_EQ(recorder->count_, 1); // Unchanged.
}
TEST(TriggerDelayedMainThreadMetricsEvent) {
using v8::Context;
using v8::Local;
using v8::MaybeLocal;
// Set up isolate and context.
v8::Isolate* iso = CcTest::isolate();
i::Isolate* i_iso = reinterpret_cast<i::Isolate*>(iso);
CHECK(i_iso->metrics_recorder());
v8::metrics::WasmModuleDecoded event;
Context::Token token;
std::shared_ptr<MetricsRecorder> recorder =
std::make_shared<MetricsRecorder>(iso);
iso->SetMetricsRecorder(recorder);
{
v8::HandleScope scope(iso);
Local<Context> context = Context::New(iso);
token = context->GetToken();
// Check that event submission works.
{
i::metrics::TimedScope<v8::metrics::WasmModuleDecoded> timed_scope(
&event, &v8::metrics::WasmModuleDecoded::wall_clock_time_in_us);
v8::base::OS::Sleep(v8::base::TimeDelta::FromMilliseconds(100));
}
i_iso->metrics_recorder()->DelayMainThreadEvent(event, token);
CHECK_EQ(recorder->count_, 0); // Unchanged.
CHECK_EQ(recorder->time_in_us_, -1); // Unchanged.
v8::base::OS::Sleep(v8::base::TimeDelta::FromMilliseconds(1100));
v8::platform::PumpMessageLoop(v8::internal::V8::GetCurrentPlatform(), iso);
CHECK_EQ(recorder->count_, 1); // Increased.
CHECK_GT(recorder->time_in_us_, 100);
}
CcTest::PreciseCollectAllGarbage();
// Check that event submission doesn't break even if the token is invalid.
i_iso->metrics_recorder()->DelayMainThreadEvent(event, token);
v8::base::OS::Sleep(v8::base::TimeDelta::FromMilliseconds(1100));
v8::platform::PumpMessageLoop(v8::internal::V8::GetCurrentPlatform(), iso);
CHECK_EQ(recorder->count_, 1); // Unchanged.
}
TEST(TriggerThreadSafeMetricsEvent) {
// Set up isolate and context.
v8::Isolate* iso = CcTest::isolate();
i::Isolate* i_iso = reinterpret_cast<i::Isolate*>(iso);
CHECK(i_iso->metrics_recorder());
v8::metrics::WasmModulesPerIsolate event;
std::shared_ptr<MetricsRecorder> recorder =
std::make_shared<MetricsRecorder>(iso);
iso->SetMetricsRecorder(recorder);
// Check that event submission works.
event.count = 42;
i_iso->metrics_recorder()->AddThreadSafeEvent(event);
CHECK_EQ(recorder->count_, 1); // Increased.
CHECK_EQ(recorder->module_count_, 42);
}