[d8] Add performance.mark and performance.measure

Add simple implementations of performance.mark/performance.measure --
these aren't fully to spec, and in particular don't have the right base
class or prototype, but they're similar enough for simple use.

Additionally, log trace events for performance.measure, similar to
Chromium -- this allows us to annotate traces collected with d8's
--enable-tracing.

Change-Id: Ib4d7104ba94a261493c57334b2008956e4d89dd1
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3918092
Reviewed-by: Camillo Bruni <cbruni@chromium.org>
Commit-Queue: Leszek Swirski <leszeks@chromium.org>
Auto-Submit: Leszek Swirski <leszeks@chromium.org>
Cr-Commit-Position: refs/heads/main@{#83478}
This commit is contained in:
Leszek Swirski 2022-09-28 16:18:28 +02:00 committed by V8 LUCI CQ
parent 65dd2f8e61
commit c4772b58aa
3 changed files with 170 additions and 4 deletions

View File

@ -1748,17 +1748,149 @@ int PerIsolateData::RealmIndexOrThrow(
return index;
}
// performance.now() returns a time stamp as double, measured in milliseconds.
// GetTimestamp() returns a time stamp as double, measured in milliseconds.
// When v8_flags.verify_predictable mode is enabled it returns result of
// v8::Platform::MonotonicallyIncreasingTime().
void Shell::PerformanceNow(const v8::FunctionCallbackInfo<v8::Value>& args) {
double Shell::GetTimestamp() {
if (i::v8_flags.verify_predictable) {
args.GetReturnValue().Set(g_platform->MonotonicallyIncreasingTime());
return g_platform->MonotonicallyIncreasingTime();
} else {
base::TimeDelta delta = base::TimeTicks::Now() - kInitialTicks;
args.GetReturnValue().Set(delta.InMillisecondsF());
return delta.InMillisecondsF();
}
}
int64_t Shell::GetTracingTimestampFromPerformanceTimestamp(
double performance_timestamp) {
// Don't use this in --verify-predictable mode, predictable timestamps don't
// work well with tracing.
DCHECK(!i::v8_flags.verify_predictable);
base::TimeDelta delta =
base::TimeDelta::FromMillisecondsD(performance_timestamp);
// See TracingController::CurrentTimestampMicroseconds().
return (delta + kInitialTicks).ToInternalValue();
}
// performance.now() returns GetTimestamp().
void Shell::PerformanceNow(const v8::FunctionCallbackInfo<v8::Value>& args) {
args.GetReturnValue().Set(GetTimestamp());
}
// performance.mark() records and returns a PerformanceEntry with the current
// timestamp.
void Shell::PerformanceMark(const v8::FunctionCallbackInfo<v8::Value>& args) {
Isolate* isolate = args.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
if (args.Length() < 1 || !args[0]->IsString()) {
args.GetIsolate()->ThrowError("Invalid 'name' argument");
return;
}
Local<String> name = args[0].As<String>();
double timestamp = GetTimestamp();
Local<Object> performance_entry = Object::New(isolate);
performance_entry
->DefineOwnProperty(context,
String::NewFromUtf8Literal(isolate, "entryType"),
String::NewFromUtf8Literal(isolate, "mark"), ReadOnly)
.Check();
performance_entry
->DefineOwnProperty(context, String::NewFromUtf8Literal(isolate, "name"),
name, ReadOnly)
.Check();
performance_entry
->DefineOwnProperty(context,
String::NewFromUtf8Literal(isolate, "startTime"),
Number::New(isolate, timestamp), ReadOnly)
.Check();
performance_entry
->DefineOwnProperty(context,
String::NewFromUtf8Literal(isolate, "duration"),
Integer::New(isolate, 0), ReadOnly)
.Check();
args.GetReturnValue().Set(performance_entry);
}
// performance.measure() records and returns a PerformanceEntry with a duration
// since a given mark, or since zero.
void Shell::PerformanceMeasure(
const v8::FunctionCallbackInfo<v8::Value>& args) {
Isolate* isolate = args.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
if (args.Length() < 1 || !args[0]->IsString()) {
args.GetIsolate()->ThrowError("Invalid 'name' argument");
return;
}
v8::Local<String> name = args[0].As<String>();
double start_timestamp = 0;
if (args.Length() >= 2) {
Local<Value> start_mark = args[1].As<Value>();
if (!start_mark->IsObject()) {
args.GetIsolate()->ThrowError(
"Invalid 'startMark' argument: Not an Object");
return;
}
Local<Value> start_time_field;
if (!start_mark.As<Object>()
->Get(context, String::NewFromUtf8Literal(isolate, "startTime"))
.ToLocal(&start_time_field)) {
return;
}
if (!start_time_field->IsNumber()) {
args.GetIsolate()->ThrowError(
"Invalid 'startMark' argument: No numeric 'startTime' field");
return;
}
start_timestamp = start_time_field.As<Number>()->Value();
}
if (args.Length() > 2) {
args.GetIsolate()->ThrowError("Too many arguments");
return;
}
double end_timestamp = GetTimestamp();
if (options.trace_enabled) {
size_t hash = base::hash_combine(name->GetIdentityHash(), start_timestamp,
end_timestamp);
String::Utf8Value utf8(isolate, name);
TRACE_EVENT_COPY_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP1(
"v8", *utf8, static_cast<uint64_t>(hash),
GetTracingTimestampFromPerformanceTimestamp(start_timestamp),
"startTime", start_timestamp);
TRACE_EVENT_COPY_NESTABLE_ASYNC_END_WITH_TIMESTAMP0(
"v8", *utf8, static_cast<uint64_t>(hash),
GetTracingTimestampFromPerformanceTimestamp(end_timestamp));
}
Local<Object> performance_entry = Object::New(isolate);
performance_entry
->DefineOwnProperty(
context, String::NewFromUtf8Literal(isolate, "entryType"),
String::NewFromUtf8Literal(isolate, "measure"), ReadOnly)
.Check();
performance_entry
->DefineOwnProperty(context, String::NewFromUtf8Literal(isolate, "name"),
name, ReadOnly)
.Check();
performance_entry
->DefineOwnProperty(context,
String::NewFromUtf8Literal(isolate, "startTime"),
Number::New(isolate, start_timestamp), ReadOnly)
.Check();
performance_entry
->DefineOwnProperty(
context, String::NewFromUtf8Literal(isolate, "duration"),
Number::New(isolate, end_timestamp - start_timestamp), ReadOnly)
.Check();
args.GetReturnValue().Set(performance_entry);
}
// performance.measureMemory() implements JavaScript Memory API proposal.
// See https://github.com/ulan/javascript-agent-memory/blob/master/explainer.md.
@ -3272,6 +3404,10 @@ Local<ObjectTemplate> Shell::CreatePerformanceTemplate(Isolate* isolate) {
Local<ObjectTemplate> performance_template = ObjectTemplate::New(isolate);
performance_template->Set(isolate, "now",
FunctionTemplate::New(isolate, PerformanceNow));
performance_template->Set(isolate, "mark",
FunctionTemplate::New(isolate, PerformanceMark));
performance_template->Set(isolate, "measure",
FunctionTemplate::New(isolate, PerformanceMeasure));
performance_template->Set(
isolate, "measureMemory",
FunctionTemplate::New(isolate, PerformanceMeasureMemory));

View File

@ -540,7 +540,14 @@ class Shell : public i::AllStatic {
static void AddHistogramSample(void* histogram, int sample);
static void MapCounters(v8::Isolate* isolate, const char* name);
static double GetTimestamp();
static int64_t GetTracingTimestampFromPerformanceTimestamp(
double performance_timestamp);
static void PerformanceNow(const v8::FunctionCallbackInfo<v8::Value>& args);
static void PerformanceMark(const v8::FunctionCallbackInfo<v8::Value>& args);
static void PerformanceMeasure(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void PerformanceMeasureMemory(
const v8::FunctionCallbackInfo<v8::Value>& args);

View File

@ -0,0 +1,23 @@
// Copyright 2022 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.
const mark = performance.mark("a mark");
assertEquals("mark", mark.entryType);
assertEquals("a mark", mark.name);
assertTrue(typeof mark.startTime == "number");
assertEquals(0, mark.duration);
const measure = performance.measure("a measure")
assertEquals("measure", measure.entryType);
assertEquals("a measure", measure.name);
assertEquals(0, measure.startTime);
assertTrue(typeof mark.duration == "number");
assertTrue(mark.startTime <= measure.duration);
const range_measure = performance.measure("a range measure", mark)
assertEquals("measure", range_measure.entryType);
assertEquals("a range measure", range_measure.name);
assertEquals(mark.startTime, range_measure.startTime);
assertTrue(typeof range_measure.duration == "number");
assertTrue(0 <= range_measure.duration);