| #include "node_perf.h" |
| #include "aliased_buffer.h" |
| #include "env-inl.h" |
| #include "histogram-inl.h" |
| #include "memory_tracker-inl.h" |
| #include "node_buffer.h" |
| #include "node_external_reference.h" |
| #include "node_internals.h" |
| #include "node_process-inl.h" |
| #include "util-inl.h" |
| |
| #include <cinttypes> |
| |
| namespace node { |
| namespace performance { |
| |
| using v8::Context; |
| using v8::DontDelete; |
| using v8::Function; |
| using v8::FunctionCallbackInfo; |
| using v8::FunctionTemplate; |
| using v8::GCCallbackFlags; |
| using v8::GCType; |
| using v8::Int32; |
| using v8::Integer; |
| using v8::Isolate; |
| using v8::Local; |
| using v8::MaybeLocal; |
| using v8::Number; |
| using v8::Object; |
| using v8::PropertyAttribute; |
| using v8::ReadOnly; |
| using v8::String; |
| using v8::Value; |
| |
| // Microseconds in a millisecond, as a float. |
| #define MICROS_PER_MILLIS 1e3 |
| // Nanoseconds in a millisecond, as a float. |
| #define NANOS_PER_MILLIS 1e6 |
| |
| uint64_t performance_v8_start; |
| |
| PerformanceState::PerformanceState(Isolate* isolate, |
| const PerformanceState::SerializeInfo* info) |
| : root(isolate, |
| sizeof(performance_state_internal), |
| MAYBE_FIELD_PTR(info, root)), |
| milestones(isolate, |
| offsetof(performance_state_internal, milestones), |
| NODE_PERFORMANCE_MILESTONE_INVALID, |
| root, |
| MAYBE_FIELD_PTR(info, milestones)), |
| observers(isolate, |
| offsetof(performance_state_internal, observers), |
| NODE_PERFORMANCE_ENTRY_TYPE_INVALID, |
| root, |
| MAYBE_FIELD_PTR(info, observers)) { |
| if (info == nullptr) { |
| for (size_t i = 0; i < milestones.Length(); i++) milestones[i] = -1.; |
| } |
| } |
| |
| PerformanceState::SerializeInfo PerformanceState::Serialize( |
| v8::Local<v8::Context> context, v8::SnapshotCreator* creator) { |
| SerializeInfo info{root.Serialize(context, creator), |
| milestones.Serialize(context, creator), |
| observers.Serialize(context, creator)}; |
| return info; |
| } |
| |
| void PerformanceState::Deserialize(v8::Local<v8::Context> context) { |
| root.Deserialize(context); |
| // This is just done to set up the pointers, we will actually reset |
| // all the milestones after deserialization. |
| milestones.Deserialize(context); |
| observers.Deserialize(context); |
| } |
| |
| std::ostream& operator<<(std::ostream& o, |
| const PerformanceState::SerializeInfo& i) { |
| o << "{\n" |
| << " " << i.root << ", // root\n" |
| << " " << i.milestones << ", // milestones\n" |
| << " " << i.observers << ", // observers\n" |
| << "}"; |
| return o; |
| } |
| |
| void PerformanceState::Mark(PerformanceMilestone milestone, uint64_t ts) { |
| this->milestones[milestone] = static_cast<double>(ts); |
| TRACE_EVENT_INSTANT_WITH_TIMESTAMP0( |
| TRACING_CATEGORY_NODE1(bootstrap), |
| GetPerformanceMilestoneName(milestone), |
| TRACE_EVENT_SCOPE_THREAD, ts / 1000); |
| } |
| |
| // Allows specific Node.js lifecycle milestones to be set from JavaScript |
| void MarkMilestone(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| PerformanceMilestone milestone = |
| static_cast<PerformanceMilestone>(args[0].As<Int32>()->Value()); |
| if (milestone != NODE_PERFORMANCE_MILESTONE_INVALID) |
| env->performance_state()->Mark(milestone); |
| } |
| |
| void SetupPerformanceObservers(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| CHECK(args[0]->IsFunction()); |
| env->set_performance_entry_callback(args[0].As<Function>()); |
| } |
| |
| // Marks the start of a GC cycle |
| void MarkGarbageCollectionStart( |
| Isolate* isolate, |
| GCType type, |
| GCCallbackFlags flags, |
| void* data) { |
| Environment* env = static_cast<Environment*>(data); |
| // Prevent gc callback from reentering with different type |
| // See https://github.com/nodejs/node/issues/44046 |
| if (env->performance_state()->current_gc_type != 0) { |
| return; |
| } |
| env->performance_state()->performance_last_gc_start_mark = PERFORMANCE_NOW(); |
| env->performance_state()->current_gc_type = type; |
| } |
| |
| MaybeLocal<Object> GCPerformanceEntryTraits::GetDetails( |
| Environment* env, |
| const GCPerformanceEntry& entry) { |
| Local<Object> obj = Object::New(env->isolate()); |
| |
| if (!obj->Set( |
| env->context(), |
| env->kind_string(), |
| Integer::NewFromUnsigned( |
| env->isolate(), |
| entry.details.kind)).IsJust()) { |
| return MaybeLocal<Object>(); |
| } |
| |
| if (!obj->Set( |
| env->context(), |
| env->flags_string(), |
| Integer::NewFromUnsigned( |
| env->isolate(), |
| entry.details.flags)).IsJust()) { |
| return MaybeLocal<Object>(); |
| } |
| |
| return obj; |
| } |
| |
| // Marks the end of a GC cycle |
| void MarkGarbageCollectionEnd( |
| Isolate* isolate, |
| GCType type, |
| GCCallbackFlags flags, |
| void* data) { |
| Environment* env = static_cast<Environment*>(data); |
| PerformanceState* state = env->performance_state(); |
| if (type != state->current_gc_type) { |
| return; |
| } |
| env->performance_state()->current_gc_type = 0; |
| // If no one is listening to gc performance entries, do not create them. |
| if (LIKELY(!state->observers[NODE_PERFORMANCE_ENTRY_TYPE_GC])) |
| return; |
| |
| double start_time = |
| (state->performance_last_gc_start_mark - env->time_origin()) / |
| NANOS_PER_MILLIS; |
| double duration = (PERFORMANCE_NOW() / NANOS_PER_MILLIS) - |
| (state->performance_last_gc_start_mark / NANOS_PER_MILLIS); |
| |
| std::unique_ptr<GCPerformanceEntry> entry = |
| std::make_unique<GCPerformanceEntry>( |
| "gc", |
| start_time, |
| duration, |
| GCPerformanceEntry::Details(static_cast<PerformanceGCKind>(type), |
| static_cast<PerformanceGCFlags>(flags))); |
| |
| env->SetImmediate([entry = std::move(entry)](Environment* env) { |
| entry->Notify(env); |
| }, CallbackFlags::kUnrefed); |
| } |
| |
| void GarbageCollectionCleanupHook(void* data) { |
| Environment* env = static_cast<Environment*>(data); |
| // Reset current_gc_type to 0 |
| env->performance_state()->current_gc_type = 0; |
| env->isolate()->RemoveGCPrologueCallback(MarkGarbageCollectionStart, data); |
| env->isolate()->RemoveGCEpilogueCallback(MarkGarbageCollectionEnd, data); |
| } |
| |
| static void InstallGarbageCollectionTracking( |
| const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| // Reset current_gc_type to 0 |
| env->performance_state()->current_gc_type = 0; |
| env->isolate()->AddGCPrologueCallback(MarkGarbageCollectionStart, |
| static_cast<void*>(env)); |
| env->isolate()->AddGCEpilogueCallback(MarkGarbageCollectionEnd, |
| static_cast<void*>(env)); |
| env->AddCleanupHook(GarbageCollectionCleanupHook, env); |
| } |
| |
| static void RemoveGarbageCollectionTracking( |
| const FunctionCallbackInfo<Value> &args) { |
| Environment* env = Environment::GetCurrent(args); |
| |
| env->RemoveCleanupHook(GarbageCollectionCleanupHook, env); |
| GarbageCollectionCleanupHook(env); |
| } |
| |
| // Gets the name of a function |
| inline Local<Value> GetName(Local<Function> fn) { |
| Local<Value> val = fn->GetDebugName(); |
| if (val.IsEmpty() || val->IsUndefined()) { |
| Local<Value> boundFunction = fn->GetBoundFunction(); |
| if (!boundFunction.IsEmpty() && !boundFunction->IsUndefined()) { |
| val = GetName(boundFunction.As<Function>()); |
| } |
| } |
| return val; |
| } |
| |
| // Notify a custom PerformanceEntry to observers |
| void Notify(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| Utf8Value type(env->isolate(), args[0]); |
| Local<Value> entry = args[1]; |
| PerformanceEntryType entry_type = ToPerformanceEntryTypeEnum(*type); |
| AliasedUint32Array& observers = env->performance_state()->observers; |
| if (entry_type != NODE_PERFORMANCE_ENTRY_TYPE_INVALID && |
| observers[entry_type]) { |
| USE(env->performance_entry_callback()-> |
| Call(env->context(), Undefined(env->isolate()), 1, &entry)); |
| } |
| } |
| |
| // Return idle time of the event loop |
| void LoopIdleTime(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| uint64_t idle_time = uv_metrics_idle_time(env->event_loop()); |
| args.GetReturnValue().Set(1.0 * idle_time / 1e6); |
| } |
| |
| void CreateELDHistogram(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| int64_t interval = args[0].As<Integer>()->Value(); |
| CHECK_GT(interval, 0); |
| BaseObjectPtr<IntervalHistogram> histogram = |
| IntervalHistogram::Create(env, interval, [](Histogram& histogram) { |
| uint64_t delta = histogram.RecordDelta(); |
| TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop), |
| "delay", delta); |
| TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop), |
| "min", histogram.Min()); |
| TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop), |
| "max", histogram.Max()); |
| TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop), |
| "mean", histogram.Mean()); |
| TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop), |
| "stddev", histogram.Stddev()); |
| }, Histogram::Options { 1000 }); |
| args.GetReturnValue().Set(histogram->object()); |
| } |
| |
| void GetTimeOrigin(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| args.GetReturnValue().Set( |
| Number::New(args.GetIsolate(), env->time_origin() / 1e6)); |
| } |
| |
| void GetTimeOriginTimeStamp(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| args.GetReturnValue().Set(Number::New( |
| args.GetIsolate(), env->time_origin_timestamp() / MICROS_PER_MILLIS)); |
| } |
| |
| void MarkBootstrapComplete(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| env->performance_state()->Mark( |
| performance::NODE_PERFORMANCE_MILESTONE_BOOTSTRAP_COMPLETE); |
| } |
| |
| void Initialize(Local<Object> target, |
| Local<Value> unused, |
| Local<Context> context, |
| void* priv) { |
| Environment* env = Environment::GetCurrent(context); |
| Isolate* isolate = env->isolate(); |
| PerformanceState* state = env->performance_state(); |
| |
| target->Set(context, |
| FIXED_ONE_BYTE_STRING(isolate, "observerCounts"), |
| state->observers.GetJSArray()).Check(); |
| target->Set(context, |
| FIXED_ONE_BYTE_STRING(isolate, "milestones"), |
| state->milestones.GetJSArray()).Check(); |
| |
| Local<String> performanceEntryString = |
| FIXED_ONE_BYTE_STRING(isolate, "PerformanceEntry"); |
| |
| Local<FunctionTemplate> pe = FunctionTemplate::New(isolate); |
| pe->SetClassName(performanceEntryString); |
| Local<Function> fn = pe->GetFunction(context).ToLocalChecked(); |
| target->Set(context, performanceEntryString, fn).Check(); |
| env->set_performance_entry_template(fn); |
| |
| SetMethod(context, target, "markMilestone", MarkMilestone); |
| SetMethod(context, target, "setupObservers", SetupPerformanceObservers); |
| SetMethod(context, |
| target, |
| "installGarbageCollectionTracking", |
| InstallGarbageCollectionTracking); |
| SetMethod(context, |
| target, |
| "removeGarbageCollectionTracking", |
| RemoveGarbageCollectionTracking); |
| SetMethod(context, target, "notify", Notify); |
| SetMethod(context, target, "loopIdleTime", LoopIdleTime); |
| SetMethod(context, target, "getTimeOrigin", GetTimeOrigin); |
| SetMethod(context, target, "getTimeOriginTimestamp", GetTimeOriginTimeStamp); |
| SetMethod(context, target, "createELDHistogram", CreateELDHistogram); |
| SetMethod(context, target, "markBootstrapComplete", MarkBootstrapComplete); |
| |
| Local<Object> 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); |
| |
| NODE_DEFINE_CONSTANT( |
| constants, NODE_PERFORMANCE_GC_FLAGS_NO); |
| NODE_DEFINE_CONSTANT( |
| constants, NODE_PERFORMANCE_GC_FLAGS_CONSTRUCT_RETAINED); |
| NODE_DEFINE_CONSTANT( |
| constants, NODE_PERFORMANCE_GC_FLAGS_FORCED); |
| NODE_DEFINE_CONSTANT( |
| constants, NODE_PERFORMANCE_GC_FLAGS_SYNCHRONOUS_PHANTOM_PROCESSING); |
| NODE_DEFINE_CONSTANT( |
| constants, NODE_PERFORMANCE_GC_FLAGS_ALL_AVAILABLE_GARBAGE); |
| NODE_DEFINE_CONSTANT( |
| constants, NODE_PERFORMANCE_GC_FLAGS_ALL_EXTERNAL_MEMORY); |
| NODE_DEFINE_CONSTANT( |
| constants, NODE_PERFORMANCE_GC_FLAGS_SCHEDULE_IDLE); |
| |
| #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 |
| |
| PropertyAttribute attr = |
| static_cast<PropertyAttribute>(ReadOnly | DontDelete); |
| |
| target->DefineOwnProperty(context, |
| env->constants_string(), |
| constants, |
| attr).ToChecked(); |
| |
| HistogramBase::Initialize(env, target); |
| } |
| |
| void RegisterExternalReferences(ExternalReferenceRegistry* registry) { |
| registry->Register(MarkMilestone); |
| registry->Register(SetupPerformanceObservers); |
| registry->Register(InstallGarbageCollectionTracking); |
| registry->Register(RemoveGarbageCollectionTracking); |
| registry->Register(Notify); |
| registry->Register(LoopIdleTime); |
| registry->Register(GetTimeOrigin); |
| registry->Register(GetTimeOriginTimeStamp); |
| registry->Register(CreateELDHistogram); |
| registry->Register(MarkBootstrapComplete); |
| HistogramBase::RegisterExternalReferences(registry); |
| IntervalHistogram::RegisterExternalReferences(registry); |
| } |
| } // namespace performance |
| } // namespace node |
| |
| NODE_MODULE_CONTEXT_AWARE_INTERNAL(performance, node::performance::Initialize) |
| NODE_MODULE_EXTERNAL_REFERENCE(performance, |
| node::performance::RegisterExternalReferences) |