Commit 8d4572a2 authored by James M Snell's avatar James M Snell Committed by Commit Bot

[builtins] Add %IsTraceCategoryEnabled and %Trace builtins

Adds the builtin Trace and IsTraceCategoryEnabled functions
exposed via extra bindings. These are intended to use by
embedders to allow basic trace event support from JavaScript.

```js
isTraceCategoryEnabled('v8.some-category')

trace('e'.charCodeAt(0), 'v8.some-category',
      'Foo', 0, { abc: 'xyz'})
```

Bug: v8:7851
Change-Id: I7bfb9bb059efdf87d92a56a0aae326650730c250
Reviewed-on: https://chromium-review.googlesource.com/1103294
Commit-Queue: Yang Guo <yangguo@chromium.org>
Reviewed-by: 's avatarYang Guo <yangguo@chromium.org>
Reviewed-by: 's avatarFadi Meawad <fmeawad@chromium.org>
Reviewed-by: 's avatarCamillo Bruni <cbruni@chromium.org>
Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#54121}
parent ca675a08
......@@ -86,6 +86,7 @@ Jan de Mooij <jandemooij@gmail.com>
Jan Krems <jan.krems@gmail.com>
Jay Freeman <saurik@saurik.com>
James Pike <g00gle@chilon.net>
James M Snell <jasnell@gmail.com>
Jianghua Yang <jianghua.yjh@alibaba-inc.com>
Joel Stanley <joel@jms.id.au>
Johan Bergström <johan@bergstroem.nu>
......
......@@ -1574,6 +1574,7 @@ v8_source_set("v8_base") {
"src/builtins/builtins-sharedarraybuffer.cc",
"src/builtins/builtins-string.cc",
"src/builtins/builtins-symbol.cc",
"src/builtins/builtins-trace.cc",
"src/builtins/builtins-typed-array.cc",
"src/builtins/builtins-utils.h",
"src/builtins/builtins.cc",
......
......@@ -5139,6 +5139,15 @@ bool Genesis::InstallExtraNatives() {
Handle<JSObject> extras_binding =
factory()->NewJSObject(isolate()->object_function());
// binding.isTraceCategoryEnabled(category)
SimpleInstallFunction(isolate(), extras_binding, "isTraceCategoryEnabled",
Builtins::kIsTraceCategoryEnabled, 1, true);
// binding.trace(phase, category, name, id, data)
SimpleInstallFunction(isolate(), extras_binding, "trace", Builtins::kTrace, 5,
true);
native_context()->set_extras_binding_object(*extras_binding);
for (int i = ExtraNatives::GetDebuggerCount();
......
......@@ -1306,7 +1306,11 @@ namespace internal {
ASM(CallApiGetter) \
ASM(DoubleToI) \
TFC(GetProperty, GetProperty, 1) \
ASM(MathPowInternal)
ASM(MathPowInternal) \
\
/* Trace */ \
CPP(IsTraceCategoryEnabled) \
CPP(Trace)
#ifdef V8_INTL_SUPPORT
#define BUILTIN_LIST(CPP, API, TFJ, TFC, TFS, TFH, ASM) \
......
// Copyright 2018 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/api.h"
#include "src/builtins/builtins-utils.h"
#include "src/builtins/builtins.h"
#include "src/counters.h"
#include "src/json-stringifier.h"
#include "src/objects-inl.h"
namespace v8 {
namespace internal {
namespace {
using v8::tracing::TracedValue;
#define MAX_STACK_LENGTH 100
class MaybeUtf8 {
public:
explicit MaybeUtf8(Isolate* isolate, Handle<String> string) : buf_(data_) {
string = String::Flatten(isolate, string);
int len;
if (string->IsOneByteRepresentation()) {
// Technically this allows unescaped latin1 characters but the trace
// events mechanism currently does the same and the current consuming
// tools are tolerant of it. A more correct approach here would be to
// escape non-ascii characters but this is easier and faster.
len = string->length();
AllocateSufficientSpace(len);
if (len > 0) {
// Why copy? Well, the trace event mechanism requires null-terminated
// strings, the bytes we get from SeqOneByteString are not. buf_ is
// guaranteed to be null terminated.
memcpy(buf_, Handle<SeqOneByteString>::cast(string)->GetChars(), len);
}
} else {
Local<v8::String> local = Utils::ToLocal(string);
len = local->Utf8Length();
AllocateSufficientSpace(len);
if (len > 0) {
local->WriteUtf8(reinterpret_cast<char*>(buf_));
}
}
buf_[len] = 0;
}
const char* operator*() const { return reinterpret_cast<const char*>(buf_); }
private:
void AllocateSufficientSpace(int len) {
if (len + 1 > MAX_STACK_LENGTH) {
allocated_.reset(new uint8_t[len + 1]);
buf_ = allocated_.get();
}
}
// In the most common cases, the buffer here will be stack allocated.
// A heap allocation will only occur if the data is more than MAX_STACK_LENGTH
// Given that this is used primarily for trace event categories and names,
// the MAX_STACK_LENGTH should be more than enough.
uint8_t* buf_;
uint8_t data_[MAX_STACK_LENGTH];
std::unique_ptr<uint8_t> allocated_;
};
class JsonTraceValue : public ConvertableToTraceFormat {
public:
explicit JsonTraceValue(Isolate* isolate, Handle<String> object) {
// object is a JSON string serialized using JSON.stringify() from within
// the BUILTIN(Trace) method. This may (likely) contain UTF8 values so
// to grab the appropriate buffer data we have to serialize it out. We
// hold on to the bits until the AppendAsTraceFormat method is called.
MaybeUtf8 data(isolate, object);
data_ = *data;
}
void AppendAsTraceFormat(std::string* out) const override { *out += data_; }
private:
std::string data_;
};
const uint8_t* GetCategoryGroupEnabled(Isolate* isolate,
Handle<String> string) {
MaybeUtf8 category(isolate, string);
return TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED(*category);
}
#undef MAX_STACK_LENGTH
} // namespace
// Builins::kIsTraceCategoryEnabled(category) : bool
BUILTIN(IsTraceCategoryEnabled) {
HandleScope scope(isolate);
Handle<Object> category = args.atOrUndefined(isolate, 1);
if (!category->IsString()) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kTraceEventCategoryError));
}
return isolate->heap()->ToBoolean(
*GetCategoryGroupEnabled(isolate, Handle<String>::cast(category)));
}
// Builtins::kTrace(phase, category, name, id, data) : bool
BUILTIN(Trace) {
HandleScope handle_scope(isolate);
Handle<Object> phase_arg = args.atOrUndefined(isolate, 1);
Handle<Object> category = args.atOrUndefined(isolate, 2);
Handle<Object> name_arg = args.atOrUndefined(isolate, 3);
Handle<Object> id_arg = args.atOrUndefined(isolate, 4);
Handle<Object> data_arg = args.atOrUndefined(isolate, 5);
const uint8_t* category_group_enabled =
GetCategoryGroupEnabled(isolate, Handle<String>::cast(category));
// Exit early if the category group is not enabled.
if (!*category_group_enabled) {
return isolate->heap()->false_value();
}
if (!phase_arg->IsNumber()) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kTraceEventPhaseError));
}
if (!category->IsString()) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kTraceEventCategoryError));
}
if (!name_arg->IsString()) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kTraceEventNameError));
}
uint32_t flags = TRACE_EVENT_FLAG_COPY;
int32_t id = 0;
if (!id_arg->IsNullOrUndefined(isolate)) {
if (!id_arg->IsNumber()) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kTraceEventIDError));
}
flags |= TRACE_EVENT_FLAG_HAS_ID;
id = DoubleToInt32(id_arg->Number());
}
Handle<String> name_str = Handle<String>::cast(name_arg);
if (name_str->length() == 0) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kTraceEventNameLengthError));
}
MaybeUtf8 name(isolate, name_str);
// We support passing one additional trace event argument with the
// name "data". Any JSON serializable value may be passed.
static const char* arg_name = "data";
int32_t num_args = 0;
uint8_t arg_type;
uint64_t arg_value;
if (!data_arg->IsUndefined(isolate)) {
// Serializes the data argument as a JSON string, which is then
// copied into an object. This eliminates duplicated code but
// could have perf costs. It is also subject to all the same
// limitations as JSON.stringify() as it relates to circular
// references and value limitations (e.g. BigInt is not supported).
JsonStringifier stringifier(isolate);
Handle<Object> result;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, result,
stringifier.Stringify(data_arg, isolate->factory()->undefined_value(),
isolate->factory()->undefined_value()));
std::unique_ptr<JsonTraceValue> traced_value;
traced_value.reset(
new JsonTraceValue(isolate, Handle<String>::cast(result)));
tracing::SetTraceValue(std::move(traced_value), &arg_type, &arg_value);
num_args++;
}
TRACE_EVENT_API_ADD_TRACE_EVENT(
static_cast<char>(DoubleToInt32(phase_arg->Number())),
category_group_enabled, *name, tracing::kGlobalScope, id, tracing::kNoId,
num_args, &arg_name, &arg_type, &arg_value, flags);
return isolate->heap()->true_value();
}
} // namespace internal
} // namespace v8
......@@ -568,6 +568,9 @@ DebugInfo::SideEffectState BuiltinGetSideEffectState(Builtins::Name id) {
case Builtins::kArrayMap:
case Builtins::kArrayReduce:
case Builtins::kArrayReduceRight:
// Trace builtins.
case Builtins::kIsTraceCategoryEnabled:
case Builtins::kTrace:
// TypedArray builtins.
case Builtins::kTypedArrayConstructor:
case Builtins::kTypedArrayPrototypeBuffer:
......
......@@ -754,7 +754,14 @@ class ErrorUtils : public AllStatic {
T(DataCloneDeserializationError, "Unable to deserialize cloned data.") \
T(DataCloneDeserializationVersionError, \
"Unable to deserialize cloned data due to invalid or unsupported " \
"version.")
"version.") \
/* Builtins-Trace Errors */ \
T(TraceEventCategoryError, "Trace event category must be a string.") \
T(TraceEventNameError, "Trace event name must be a string.") \
T(TraceEventNameLengthError, \
"Trace event name must not be an empty string.") \
T(TraceEventPhaseError, "Trace event phase must be a number.") \
T(TraceEventIDError, "Trace event id must be a number.")
class MessageTemplate {
public:
......
......@@ -75,7 +75,7 @@ class MockTracingController : public v8::TracingController {
const char* name, uint64_t handle) override {}
const uint8_t* GetCategoryGroupEnabled(const char* name) override {
if (strcmp(name, "v8-cat")) {
if (strncmp(name, "v8-cat", 6)) {
static uint8_t no = 0;
return &no;
} else {
......@@ -282,3 +282,133 @@ TEST(TestEventWithTimestamp) {
CHECK_EQ(20683, GET_TRACE_OBJECT(3)->timestamp);
CHECK_EQ(32832, GET_TRACE_OBJECT(4)->timestamp);
}
TEST(BuiltinsIsTraceCategoryEnabled) {
MockTracingPlatform platform;
v8::Isolate* isolate = CcTest::isolate();
v8::HandleScope handle_scope(isolate);
LocalContext env;
v8::Local<v8::Object> binding = env->GetExtrasBindingObject();
CHECK(!binding.IsEmpty());
auto undefined = v8::Undefined(isolate);
auto isTraceCategoryEnabled =
binding->Get(env.local(), v8_str("isTraceCategoryEnabled"))
.ToLocalChecked()
.As<v8::Function>();
{
// Test with an enabled category
v8::Local<v8::Value> argv[] = {v8_str("v8-cat")};
auto result = isTraceCategoryEnabled->Call(env.local(), undefined, 1, argv)
.ToLocalChecked()
.As<v8::Boolean>();
CHECK(result->BooleanValue());
}
{
// Test with a disabled category
v8::Local<v8::Value> argv[] = {v8_str("cat")};
auto result = isTraceCategoryEnabled->Call(env.local(), undefined, 1, argv)
.ToLocalChecked()
.As<v8::Boolean>();
CHECK(!result->BooleanValue());
}
{
// Test with an enabled utf8 category
v8::Local<v8::Value> argv[] = {v8_str("v8-cat\u20ac")};
auto result = isTraceCategoryEnabled->Call(env.local(), undefined, 1, argv)
.ToLocalChecked()
.As<v8::Boolean>();
CHECK(result->BooleanValue());
}
}
TEST(BuiltinsTrace) {
MockTracingPlatform platform;
v8::Isolate* isolate = CcTest::isolate();
v8::HandleScope handle_scope(isolate);
LocalContext env;
v8::Local<v8::Object> binding = env->GetExtrasBindingObject();
CHECK(!binding.IsEmpty());
auto undefined = v8::Undefined(isolate);
auto trace = binding->Get(env.local(), v8_str("trace"))
.ToLocalChecked()
.As<v8::Function>();
// Test with disabled category
{
v8::Local<v8::String> category = v8_str("cat");
v8::Local<v8::String> name = v8_str("name");
v8::Local<v8::Value> argv[] = {
v8::Integer::New(isolate, 'b'), // phase
category, name, v8::Integer::New(isolate, 0), // id
undefined // data
};
auto result = trace->Call(env.local(), undefined, 5, argv)
.ToLocalChecked()
.As<v8::Boolean>();
CHECK(!result->BooleanValue());
CHECK_EQ(0, GET_TRACE_OBJECTS_LIST->size());
}
// Test with enabled category
{
v8::Local<v8::String> category = v8_str("v8-cat");
v8::Local<v8::String> name = v8_str("name");
v8::Local<v8::Context> context = isolate->GetCurrentContext();
v8::Local<v8::Object> data = v8::Object::New(isolate);
data->Set(context, v8_str("foo"), v8_str("bar")).FromJust();
v8::Local<v8::Value> argv[] = {
v8::Integer::New(isolate, 'b'), // phase
category, name, v8::Integer::New(isolate, 123), // id
data // data arg
};
auto result = trace->Call(env.local(), undefined, 5, argv)
.ToLocalChecked()
.As<v8::Boolean>();
CHECK(result->BooleanValue());
CHECK_EQ(1, GET_TRACE_OBJECTS_LIST->size());
CHECK_EQ(123, GET_TRACE_OBJECT(0)->id);
CHECK_EQ('b', GET_TRACE_OBJECT(0)->phase);
CHECK_EQ("name", GET_TRACE_OBJECT(0)->name);
CHECK_EQ(1, GET_TRACE_OBJECT(0)->num_args);
}
// Test with enabled utf8 category
{
v8::Local<v8::String> category = v8_str("v8-cat\u20ac");
v8::Local<v8::String> name = v8_str("name\u20ac");
v8::Local<v8::Context> context = isolate->GetCurrentContext();
v8::Local<v8::Object> data = v8::Object::New(isolate);
data->Set(context, v8_str("foo"), v8_str("bar")).FromJust();
v8::Local<v8::Value> argv[] = {
v8::Integer::New(isolate, 'b'), // phase
category, name, v8::Integer::New(isolate, 123), // id
data // data arg
};
auto result = trace->Call(env.local(), undefined, 5, argv)
.ToLocalChecked()
.As<v8::Boolean>();
CHECK(result->BooleanValue());
CHECK_EQ(2, GET_TRACE_OBJECTS_LIST->size());
CHECK_EQ(123, GET_TRACE_OBJECT(1)->id);
CHECK_EQ('b', GET_TRACE_OBJECT(1)->phase);
CHECK_EQ("name\u20ac", GET_TRACE_OBJECT(1)->name);
CHECK_EQ(1, GET_TRACE_OBJECT(1)->num_args);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment