#include "node_internals.h" #include "node_perf.h" #include namespace node { namespace performance { using v8::Array; using v8::ArrayBuffer; using v8::Context; using v8::Function; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; using v8::HandleScope; using v8::Integer; using v8::Isolate; using v8::Local; using v8::Name; using v8::Object; using v8::ObjectTemplate; using v8::PropertyCallbackInfo; using v8::String; using v8::Value; const uint64_t timeOrigin = PERFORMANCE_NOW(); uint64_t performance_node_start; uint64_t performance_v8_start; uint64_t performance_last_gc_start_mark_ = 0; v8::GCType performance_last_gc_type_ = v8::GCType::kGCTypeAll; void PerformanceEntry::New(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); Isolate* isolate = env->isolate(); Utf8Value name(isolate, args[0]); Utf8Value type(isolate, args[1]); uint64_t now = PERFORMANCE_NOW(); new PerformanceEntry(env, args.This(), *name, *type, now, now); } void PerformanceEntry::NotifyObservers(Environment* env, PerformanceEntry* entry) { uint32_t* observers = env->performance_state()->observers; PerformanceEntryType type = ToPerformanceEntryTypeEnum(entry->type().c_str()); if (observers == nullptr || type == NODE_PERFORMANCE_ENTRY_TYPE_INVALID || !observers[type]) { return; } Local context = env->context(); Isolate* isolate = env->isolate(); Local argv = entry->object(); env->performance_entry_callback()->Call(context, v8::Undefined(isolate), 1, &argv).ToLocalChecked(); } void Mark(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); Local context = env->context(); Isolate* isolate = env->isolate(); Utf8Value name(isolate, args[0]); uint64_t now = PERFORMANCE_NOW(); auto marks = env->performance_marks(); (*marks)[*name] = now; // TODO(jasnell): Once Tracing API is fully implemented, this should // record a trace event also. Local fn = env->performance_entry_template(); Local obj = fn->NewInstance(context).ToLocalChecked(); new PerformanceEntry(env, obj, *name, "mark", now, now); args.GetReturnValue().Set(obj); } inline uint64_t GetPerformanceMark(Environment* env, std::string name) { auto marks = env->performance_marks(); auto res = marks->find(name); return res != marks->end() ? res->second : 0; } void Measure(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); Local context = env->context(); Isolate* isolate = env->isolate(); Utf8Value name(isolate, args[0]); Utf8Value startMark(isolate, args[1]); Utf8Value endMark(isolate, args[2]); double* milestones = env->performance_state()->milestones; uint64_t startTimestamp = timeOrigin; uint64_t start = GetPerformanceMark(env, *startMark); if (start != 0) { startTimestamp = start; } else { PerformanceMilestone milestone = ToPerformanceMilestoneEnum(*startMark); if (milestone != NODE_PERFORMANCE_MILESTONE_INVALID) startTimestamp = milestones[milestone]; } uint64_t endTimestamp = GetPerformanceMark(env, *endMark); if (endTimestamp == 0) { PerformanceMilestone milestone = ToPerformanceMilestoneEnum(*endMark); if (milestone != NODE_PERFORMANCE_MILESTONE_INVALID) endTimestamp = milestones[milestone]; } if (endTimestamp < startTimestamp) endTimestamp = startTimestamp; // TODO(jasnell): Once Tracing API is fully implemented, this should // record a trace event also. Local fn = env->performance_entry_template(); Local obj = fn->NewInstance(context).ToLocalChecked(); new PerformanceEntry(env, obj, *name, "measure", startTimestamp, endTimestamp); args.GetReturnValue().Set(obj); } void GetPerformanceEntryName(const Local prop, const PropertyCallbackInfo& info) { Isolate* isolate = info.GetIsolate(); PerformanceEntry* entry; ASSIGN_OR_RETURN_UNWRAP(&entry, info.Holder()); info.GetReturnValue().Set( String::NewFromUtf8(isolate, entry->name().c_str(), String::kNormalString)); } void GetPerformanceEntryType(const Local prop, const PropertyCallbackInfo& info) { Isolate* isolate = info.GetIsolate(); PerformanceEntry* entry; ASSIGN_OR_RETURN_UNWRAP(&entry, info.Holder()); info.GetReturnValue().Set( String::NewFromUtf8(isolate, entry->type().c_str(), String::kNormalString)); } void GetPerformanceEntryStartTime(const Local prop, const PropertyCallbackInfo& info) { PerformanceEntry* entry; ASSIGN_OR_RETURN_UNWRAP(&entry, info.Holder()); info.GetReturnValue().Set(entry->startTime()); } void GetPerformanceEntryDuration(const Local prop, const PropertyCallbackInfo& info) { PerformanceEntry* entry; ASSIGN_OR_RETURN_UNWRAP(&entry, info.Holder()); info.GetReturnValue().Set(entry->duration()); } void MarkMilestone(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); Local context = env->context(); double* milestones = env->performance_state()->milestones; PerformanceMilestone milestone = static_cast( args[0]->Int32Value(context).ToChecked()); if (milestone != NODE_PERFORMANCE_MILESTONE_INVALID) { milestones[milestone] = PERFORMANCE_NOW(); } } void SetupPerformanceObservers(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CHECK(args[0]->IsFunction()); env->set_performance_entry_callback(args[0].As()); } void PerformanceGCCallback(uv_async_t* handle) { PerformanceEntry::Data* data = static_cast(handle->data); Environment* env = data->env(); Isolate* isolate = env->isolate(); HandleScope scope(isolate); Local context = env->context(); Context::Scope context_scope(context); Local fn; Local obj; PerformanceGCKind kind = static_cast(data->data()); uint32_t* observers = env->performance_state()->observers; if (!observers[NODE_PERFORMANCE_ENTRY_TYPE_GC]) { goto cleanup; } fn = env->performance_entry_template(); obj = fn->NewInstance(context).ToLocalChecked(); obj->Set(context, FIXED_ONE_BYTE_STRING(isolate, "kind"), Integer::New(isolate, kind)).FromJust(); new PerformanceEntry(env, obj, data); cleanup: delete data; auto closeCB = [](uv_handle_t* handle) { delete reinterpret_cast(handle); }; uv_close(reinterpret_cast(handle), closeCB); } void MarkGarbageCollectionStart(Isolate* isolate, v8::GCType type, v8::GCCallbackFlags flags) { performance_last_gc_start_mark_ = PERFORMANCE_NOW(); performance_last_gc_type_ = type; } void MarkGarbageCollectionEnd(Isolate* isolate, v8::GCType type, v8::GCCallbackFlags flags, void* data) { Environment* env = static_cast(data); uv_async_t* async = new uv_async_t(); // coverity[leaked_storage] if (uv_async_init(env->event_loop(), async, PerformanceGCCallback)) return delete async; uv_unref(reinterpret_cast(async)); async->data = new PerformanceEntry::Data(env, "gc", "gc", performance_last_gc_start_mark_, PERFORMANCE_NOW(), type); CHECK_EQ(0, uv_async_send(async)); } inline void SetupGarbageCollectionTracking(Environment* env) { env->isolate()->AddGCPrologueCallback(MarkGarbageCollectionStart); env->isolate()->AddGCEpilogueCallback(MarkGarbageCollectionEnd, static_cast(env)); } inline Local GetName(Local fn) { Local val = fn->GetDebugName(); if (val.IsEmpty() || val->IsUndefined()) { Local boundFunction = fn->GetBoundFunction(); if (!boundFunction.IsEmpty() && !boundFunction->IsUndefined()) { val = GetName(boundFunction.As()); } } return val; } void TimerFunctionCall(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); HandleScope scope(isolate); Environment* env = Environment::GetCurrent(isolate); Local context = env->context(); Local fn = args.Data().As(); size_t count = args.Length(); size_t idx; std::vector> call_args; for (size_t i = 0; i < count; ++i) { call_args.push_back(args[i]); } Utf8Value name(isolate, GetName(fn)); uint64_t start; uint64_t end; v8::TryCatch try_catch(isolate); if (args.IsConstructCall()) { start = PERFORMANCE_NOW(); v8::MaybeLocal ret = fn->NewInstance(context, call_args.size(), call_args.data()); end = PERFORMANCE_NOW(); if (ret.IsEmpty()) { try_catch.ReThrow(); return; } args.GetReturnValue().Set(ret.ToLocalChecked()); } else { start = PERFORMANCE_NOW(); v8::MaybeLocal ret = fn->Call(context, args.This(), call_args.size(), call_args.data()); end = PERFORMANCE_NOW(); if (ret.IsEmpty()) { try_catch.ReThrow(); return; } args.GetReturnValue().Set(ret.ToLocalChecked()); } uint32_t* observers = env->performance_state()->observers; if (!observers[NODE_PERFORMANCE_ENTRY_TYPE_FUNCTION]) return; Local ctor = env->performance_entry_template(); v8::MaybeLocal instance = ctor->NewInstance(context); Local obj = instance.ToLocalChecked(); for (idx = 0; idx < count; idx++) { obj->Set(context, idx, args[idx]).ToChecked(); } new PerformanceEntry(env, obj, *name, "function", start, end); } void Timerify(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); Local context = env->context(); CHECK(args[0]->IsFunction()); CHECK(args[1]->IsNumber()); Local fn = args[0].As(); int length = args[1]->IntegerValue(context).ToChecked(); Local wrap = Function::New(context, TimerFunctionCall, fn, length).ToLocalChecked(); args.GetReturnValue().Set(wrap); } void Init(Local target, Local unused, Local context) { Environment* env = Environment::GetCurrent(context); Isolate* isolate = env->isolate(); performance_state* state = env->performance_state(); auto state_ab = ArrayBuffer::New(isolate, state, sizeof(*state)); #define SET_STATE_TYPEDARRAY(name, type, field) \ target->Set(context, \ FIXED_ONE_BYTE_STRING(isolate, (name)), \ type::New(state_ab, \ offsetof(performance_state, field), \ arraysize(state->field))) \ .FromJust() SET_STATE_TYPEDARRAY("observerCounts", v8::Uint32Array, observers); SET_STATE_TYPEDARRAY("milestones", v8::Float64Array, milestones); #undef SET_STATE_TYPEDARRAY Local performanceEntryString = FIXED_ONE_BYTE_STRING(isolate, "PerformanceEntry"); Local pe = env->NewFunctionTemplate(PerformanceEntry::New); pe->InstanceTemplate()->SetInternalFieldCount(1); pe->SetClassName(performanceEntryString); Local ot = pe->InstanceTemplate(); ot->SetAccessor(env->name_string(), GetPerformanceEntryName); ot->SetAccessor(FIXED_ONE_BYTE_STRING(isolate, "entryType"), GetPerformanceEntryType); ot->SetAccessor(FIXED_ONE_BYTE_STRING(isolate, "startTime"), GetPerformanceEntryStartTime); ot->SetAccessor(FIXED_ONE_BYTE_STRING(isolate, "duration"), GetPerformanceEntryDuration); Local fn = pe->GetFunction(); target->Set(performanceEntryString, fn); env->set_performance_entry_template(fn); env->SetMethod(target, "mark", Mark); env->SetMethod(target, "measure", Measure); env->SetMethod(target, "markMilestone", MarkMilestone); env->SetMethod(target, "setupObservers", SetupPerformanceObservers); env->SetMethod(target, "timerify", Timerify); Local constants = Object::New(isolate); NODE_DEFINE_CONSTANT(constants, NODE_PERFORMANCE_GC_MAJOR); NODE_DEFINE_CONSTANT(constants, NODE_PERFORMANCE_GC_MINOR); NODE_DEFINE_CONSTANT(constants, NODE_PERFORMANCE_GC_INCREMENTAL); NODE_DEFINE_CONSTANT(constants, NODE_PERFORMANCE_GC_WEAKCB); #define V(name, _) \ NODE_DEFINE_HIDDEN_CONSTANT(constants, NODE_PERFORMANCE_ENTRY_TYPE_##name); NODE_PERFORMANCE_ENTRY_TYPES(V) #undef V #define V(name, _) \ NODE_DEFINE_HIDDEN_CONSTANT(constants, NODE_PERFORMANCE_MILESTONE_##name); NODE_PERFORMANCE_MILESTONES(V) #undef V v8::PropertyAttribute attr = static_cast(v8::ReadOnly | v8::DontDelete); target->DefineOwnProperty(context, FIXED_ONE_BYTE_STRING(isolate, "timeOrigin"), v8::Number::New(isolate, timeOrigin / 1e6), attr).ToChecked(); target->DefineOwnProperty(context, env->constants_string(), constants, attr).ToChecked(); SetupGarbageCollectionTracking(env); } } // namespace performance } // namespace node NODE_BUILTIN_MODULE_CONTEXT_AWARE(performance, node::performance::Init)