1 #include "aliased_buffer.h"
2 #include "histogram-inl.h"
3 #include "memory_tracker-inl.h"
4 #include "node_internals.h"
5 #include "node_perf.h"
6 #include "node_buffer.h"
7 #include "node_process-inl.h"
8 #include "util-inl.h"
9
10 #include <cinttypes>
11
12 namespace node {
13 namespace performance {
14
15 using v8::Context;
16 using v8::DontDelete;
17 using v8::Function;
18 using v8::FunctionCallbackInfo;
19 using v8::FunctionTemplate;
20 using v8::GCCallbackFlags;
21 using v8::GCType;
22 using v8::HandleScope;
23 using v8::Int32;
24 using v8::Integer;
25 using v8::Isolate;
26 using v8::Local;
27 using v8::MaybeLocal;
28 using v8::Number;
29 using v8::Object;
30 using v8::PropertyAttribute;
31 using v8::ReadOnly;
32 using v8::String;
33 using v8::Value;
34
35 // Microseconds in a millisecond, as a float.
36 #define MICROS_PER_MILLIS 1e3
37
38 // https://w3c.github.io/hr-time/#dfn-time-origin
39 const uint64_t timeOrigin = PERFORMANCE_NOW();
40 // https://w3c.github.io/hr-time/#dfn-time-origin-timestamp
41 const double timeOriginTimestamp = GetCurrentTimeInMicroseconds();
42 uint64_t performance_v8_start;
43
Mark(enum PerformanceMilestone milestone,uint64_t ts)44 void PerformanceState::Mark(enum PerformanceMilestone milestone,
45 uint64_t ts) {
46 this->milestones[milestone] = ts;
47 TRACE_EVENT_INSTANT_WITH_TIMESTAMP0(
48 TRACING_CATEGORY_NODE1(bootstrap),
49 GetPerformanceMilestoneName(milestone),
50 TRACE_EVENT_SCOPE_THREAD, ts / 1000);
51 }
52
53 // Initialize the performance entry object properties
InitObject(const PerformanceEntry & entry,Local<Object> obj)54 inline void InitObject(const PerformanceEntry& entry, Local<Object> obj) {
55 Environment* env = entry.env();
56 Isolate* isolate = env->isolate();
57 Local<Context> context = env->context();
58 PropertyAttribute attr =
59 static_cast<PropertyAttribute>(ReadOnly | DontDelete);
60 obj->DefineOwnProperty(context,
61 env->name_string(),
62 String::NewFromUtf8(isolate,
63 entry.name().c_str())
64 .ToLocalChecked(),
65 attr)
66 .Check();
67 obj->DefineOwnProperty(context,
68 env->entry_type_string(),
69 String::NewFromUtf8(isolate,
70 entry.type().c_str())
71 .ToLocalChecked(),
72 attr)
73 .Check();
74 obj->DefineOwnProperty(context,
75 env->start_time_string(),
76 Number::New(isolate, entry.startTime()),
77 attr).Check();
78 obj->DefineOwnProperty(context,
79 env->duration_string(),
80 Number::New(isolate, entry.duration()),
81 attr).Check();
82 }
83
84 // Create a new PerformanceEntry object
ToObject() const85 MaybeLocal<Object> PerformanceEntry::ToObject() const {
86 Local<Object> obj;
87 if (!env_->performance_entry_template()
88 ->NewInstance(env_->context())
89 .ToLocal(&obj)) {
90 return MaybeLocal<Object>();
91 }
92 InitObject(*this, obj);
93 return obj;
94 }
95
96 // Allow creating a PerformanceEntry object from JavaScript
New(const FunctionCallbackInfo<Value> & args)97 void PerformanceEntry::New(const FunctionCallbackInfo<Value>& args) {
98 Environment* env = Environment::GetCurrent(args);
99 Isolate* isolate = env->isolate();
100 Utf8Value name(isolate, args[0]);
101 Utf8Value type(isolate, args[1]);
102 uint64_t now = PERFORMANCE_NOW();
103 PerformanceEntry entry(env, *name, *type, now, now);
104 Local<Object> obj = args.This();
105 InitObject(entry, obj);
106 PerformanceEntry::Notify(env, entry.kind(), obj);
107 }
108
109 // Pass the PerformanceEntry object to the PerformanceObservers
Notify(Environment * env,PerformanceEntryType type,Local<Value> object)110 void PerformanceEntry::Notify(Environment* env,
111 PerformanceEntryType type,
112 Local<Value> object) {
113 Context::Scope scope(env->context());
114 AliasedUint32Array& observers = env->performance_state()->observers;
115 if (type != NODE_PERFORMANCE_ENTRY_TYPE_INVALID &&
116 observers[type]) {
117 node::MakeCallback(env->isolate(),
118 object.As<Object>(),
119 env->performance_entry_callback(),
120 1, &object,
121 node::async_context{0, 0});
122 }
123 }
124
125 // Create a User Timing Mark
Mark(const FunctionCallbackInfo<Value> & args)126 void Mark(const FunctionCallbackInfo<Value>& args) {
127 Environment* env = Environment::GetCurrent(args);
128 HandleScope scope(env->isolate());
129 Utf8Value name(env->isolate(), args[0]);
130 uint64_t now = PERFORMANCE_NOW();
131 auto marks = env->performance_marks();
132 (*marks)[*name] = now;
133
134 TRACE_EVENT_COPY_MARK_WITH_TIMESTAMP(
135 TRACING_CATEGORY_NODE2(perf, usertiming),
136 *name, now / 1000);
137
138 PerformanceEntry entry(env, *name, "mark", now, now);
139 Local<Object> obj;
140 if (!entry.ToObject().ToLocal(&obj)) return;
141 PerformanceEntry::Notify(env, entry.kind(), obj);
142 args.GetReturnValue().Set(obj);
143 }
144
ClearMark(const FunctionCallbackInfo<Value> & args)145 void ClearMark(const FunctionCallbackInfo<Value>& args) {
146 Environment* env = Environment::GetCurrent(args);
147 auto marks = env->performance_marks();
148
149 if (args.Length() == 0) {
150 marks->clear();
151 } else {
152 Utf8Value name(env->isolate(), args[0]);
153 marks->erase(*name);
154 }
155 }
156
GetPerformanceMark(Environment * env,const std::string & name)157 inline uint64_t GetPerformanceMark(Environment* env, const std::string& name) {
158 auto marks = env->performance_marks();
159 auto res = marks->find(name);
160 return res != marks->end() ? res->second : 0;
161 }
162
163 // Create a User Timing Measure. A Measure is a PerformanceEntry that
164 // measures the duration between two distinct user timing marks
Measure(const FunctionCallbackInfo<Value> & args)165 void Measure(const FunctionCallbackInfo<Value>& args) {
166 Environment* env = Environment::GetCurrent(args);
167 HandleScope scope(env->isolate());
168 Utf8Value name(env->isolate(), args[0]);
169 Utf8Value startMark(env->isolate(), args[1]);
170
171 AliasedFloat64Array& milestones = env->performance_state()->milestones;
172
173 uint64_t startTimestamp = timeOrigin;
174 uint64_t start = GetPerformanceMark(env, *startMark);
175 if (start != 0) {
176 startTimestamp = start;
177 } else {
178 PerformanceMilestone milestone = ToPerformanceMilestoneEnum(*startMark);
179 if (milestone != NODE_PERFORMANCE_MILESTONE_INVALID)
180 startTimestamp = milestones[milestone];
181 }
182
183 uint64_t endTimestamp = 0;
184 if (args[2]->IsUndefined()) {
185 endTimestamp = PERFORMANCE_NOW();
186 } else {
187 Utf8Value endMark(env->isolate(), args[2]);
188 endTimestamp = GetPerformanceMark(env, *endMark);
189 if (endTimestamp == 0) {
190 PerformanceMilestone milestone = ToPerformanceMilestoneEnum(*endMark);
191 if (milestone != NODE_PERFORMANCE_MILESTONE_INVALID)
192 endTimestamp = milestones[milestone];
193 }
194 }
195
196 if (endTimestamp < startTimestamp)
197 endTimestamp = startTimestamp;
198
199 TRACE_EVENT_COPY_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0(
200 TRACING_CATEGORY_NODE2(perf, usertiming),
201 *name, *name, startTimestamp / 1000);
202 TRACE_EVENT_COPY_NESTABLE_ASYNC_END_WITH_TIMESTAMP0(
203 TRACING_CATEGORY_NODE2(perf, usertiming),
204 *name, *name, endTimestamp / 1000);
205
206 PerformanceEntry entry(env, *name, "measure", startTimestamp, endTimestamp);
207 Local<Object> obj;
208 if (!entry.ToObject().ToLocal(&obj)) return;
209 PerformanceEntry::Notify(env, entry.kind(), obj);
210 args.GetReturnValue().Set(obj);
211 }
212
213 // Allows specific Node.js lifecycle milestones to be set from JavaScript
MarkMilestone(const FunctionCallbackInfo<Value> & args)214 void MarkMilestone(const FunctionCallbackInfo<Value>& args) {
215 Environment* env = Environment::GetCurrent(args);
216 Local<Context> context = env->context();
217 PerformanceMilestone milestone =
218 static_cast<PerformanceMilestone>(
219 args[0]->Int32Value(context).ToChecked());
220 if (milestone != NODE_PERFORMANCE_MILESTONE_INVALID)
221 env->performance_state()->Mark(milestone);
222 }
223
224
SetupPerformanceObservers(const FunctionCallbackInfo<Value> & args)225 void SetupPerformanceObservers(const FunctionCallbackInfo<Value>& args) {
226 Environment* env = Environment::GetCurrent(args);
227 CHECK(args[0]->IsFunction());
228 env->set_performance_entry_callback(args[0].As<Function>());
229 }
230
231 // Creates a GC Performance Entry and passes it to observers
PerformanceGCCallback(Environment * env,std::unique_ptr<GCPerformanceEntry> entry)232 void PerformanceGCCallback(Environment* env,
233 std::unique_ptr<GCPerformanceEntry> entry) {
234 HandleScope scope(env->isolate());
235 Local<Context> context = env->context();
236
237 AliasedUint32Array& observers = env->performance_state()->observers;
238 if (observers[NODE_PERFORMANCE_ENTRY_TYPE_GC]) {
239 Local<Object> obj;
240 if (!entry->ToObject().ToLocal(&obj)) return;
241 PropertyAttribute attr =
242 static_cast<PropertyAttribute>(ReadOnly | DontDelete);
243 obj->DefineOwnProperty(context,
244 env->kind_string(),
245 Integer::New(env->isolate(), entry->gckind()),
246 attr).Check();
247 obj->DefineOwnProperty(context,
248 env->flags_string(),
249 Integer::New(env->isolate(), entry->gcflags()),
250 attr).Check();
251 PerformanceEntry::Notify(env, entry->kind(), obj);
252 }
253 }
254
255 // Marks the start of a GC cycle
MarkGarbageCollectionStart(Isolate * isolate,GCType type,GCCallbackFlags flags,void * data)256 void MarkGarbageCollectionStart(Isolate* isolate,
257 GCType type,
258 GCCallbackFlags flags,
259 void* data) {
260 Environment* env = static_cast<Environment*>(data);
261 env->performance_state()->performance_last_gc_start_mark = PERFORMANCE_NOW();
262 }
263
264 // Marks the end of a GC cycle
MarkGarbageCollectionEnd(Isolate * isolate,GCType type,GCCallbackFlags flags,void * data)265 void MarkGarbageCollectionEnd(Isolate* isolate,
266 GCType type,
267 GCCallbackFlags flags,
268 void* data) {
269 Environment* env = static_cast<Environment*>(data);
270 PerformanceState* state = env->performance_state();
271 // If no one is listening to gc performance entries, do not create them.
272 if (!state->observers[NODE_PERFORMANCE_ENTRY_TYPE_GC])
273 return;
274 auto entry = std::make_unique<GCPerformanceEntry>(
275 env,
276 static_cast<PerformanceGCKind>(type),
277 static_cast<PerformanceGCFlags>(flags),
278 state->performance_last_gc_start_mark,
279 PERFORMANCE_NOW());
280 env->SetImmediate([entry = std::move(entry)](Environment* env) mutable {
281 PerformanceGCCallback(env, std::move(entry));
282 }, CallbackFlags::kUnrefed);
283 }
284
GarbageCollectionCleanupHook(void * data)285 void GarbageCollectionCleanupHook(void* data) {
286 Environment* env = static_cast<Environment*>(data);
287 env->isolate()->RemoveGCPrologueCallback(MarkGarbageCollectionStart, data);
288 env->isolate()->RemoveGCEpilogueCallback(MarkGarbageCollectionEnd, data);
289 }
290
InstallGarbageCollectionTracking(const FunctionCallbackInfo<Value> & args)291 static void InstallGarbageCollectionTracking(
292 const FunctionCallbackInfo<Value>& args) {
293 Environment* env = Environment::GetCurrent(args);
294
295 env->isolate()->AddGCPrologueCallback(MarkGarbageCollectionStart,
296 static_cast<void*>(env));
297 env->isolate()->AddGCEpilogueCallback(MarkGarbageCollectionEnd,
298 static_cast<void*>(env));
299 env->AddCleanupHook(GarbageCollectionCleanupHook, env);
300 }
301
RemoveGarbageCollectionTracking(const FunctionCallbackInfo<Value> & args)302 static void RemoveGarbageCollectionTracking(
303 const FunctionCallbackInfo<Value> &args) {
304 Environment* env = Environment::GetCurrent(args);
305
306 env->RemoveCleanupHook(GarbageCollectionCleanupHook, env);
307 GarbageCollectionCleanupHook(env);
308 }
309
310 // Gets the name of a function
GetName(Local<Function> fn)311 inline Local<Value> GetName(Local<Function> fn) {
312 Local<Value> val = fn->GetDebugName();
313 if (val.IsEmpty() || val->IsUndefined()) {
314 Local<Value> boundFunction = fn->GetBoundFunction();
315 if (!boundFunction.IsEmpty() && !boundFunction->IsUndefined()) {
316 val = GetName(boundFunction.As<Function>());
317 }
318 }
319 return val;
320 }
321
322 // Executes a wrapped Function and captures timing information, causing a
323 // Function PerformanceEntry to be emitted to PerformanceObservers after
324 // execution.
TimerFunctionCall(const FunctionCallbackInfo<Value> & args)325 void TimerFunctionCall(const FunctionCallbackInfo<Value>& args) {
326 Isolate* isolate = args.GetIsolate();
327 Local<Context> context = isolate->GetCurrentContext();
328 Environment* env = Environment::GetCurrent(context);
329 CHECK_NOT_NULL(env);
330 Local<Function> fn = args.Data().As<Function>();
331 size_t count = args.Length();
332 size_t idx;
333 SlicedArguments call_args(args);
334 Utf8Value name(isolate, GetName(fn));
335 bool is_construct_call = args.IsConstructCall();
336
337 uint64_t start = PERFORMANCE_NOW();
338 TRACE_EVENT_COPY_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0(
339 TRACING_CATEGORY_NODE2(perf, timerify),
340 *name, *name, start / 1000);
341 v8::MaybeLocal<Value> ret;
342
343 if (is_construct_call) {
344 ret = fn->NewInstance(context, call_args.length(), call_args.out())
345 .FromMaybe(Local<Object>());
346 } else {
347 ret = fn->Call(context, args.This(), call_args.length(), call_args.out());
348 }
349
350 uint64_t end = PERFORMANCE_NOW();
351 TRACE_EVENT_COPY_NESTABLE_ASYNC_END_WITH_TIMESTAMP0(
352 TRACING_CATEGORY_NODE2(perf, timerify),
353 *name, *name, end / 1000);
354
355 if (ret.IsEmpty())
356 return;
357 args.GetReturnValue().Set(ret.ToLocalChecked());
358
359 AliasedUint32Array& observers = env->performance_state()->observers;
360 if (!observers[NODE_PERFORMANCE_ENTRY_TYPE_FUNCTION])
361 return;
362
363 PerformanceEntry entry(env, *name, "function", start, end);
364 Local<Object> obj;
365 if (!entry.ToObject().ToLocal(&obj)) return;
366 for (idx = 0; idx < count; idx++)
367 obj->Set(context, idx, args[idx]).Check();
368 PerformanceEntry::Notify(env, entry.kind(), obj);
369 }
370
371 // Wraps a Function in a TimerFunctionCall
Timerify(const FunctionCallbackInfo<Value> & args)372 void Timerify(const FunctionCallbackInfo<Value>& args) {
373 Environment* env = Environment::GetCurrent(args);
374 Local<Context> context = env->context();
375 CHECK(args[0]->IsFunction());
376 CHECK(args[1]->IsNumber());
377 Local<Function> fn = args[0].As<Function>();
378 int length = args[1]->IntegerValue(context).ToChecked();
379 Local<Function> wrap =
380 Function::New(context, TimerFunctionCall, fn, length).ToLocalChecked();
381 args.GetReturnValue().Set(wrap);
382 }
383
384 // Notify a custom PerformanceEntry to observers
Notify(const FunctionCallbackInfo<Value> & args)385 void Notify(const FunctionCallbackInfo<Value>& args) {
386 Environment* env = Environment::GetCurrent(args);
387 Utf8Value type(env->isolate(), args[0]);
388 Local<Value> entry = args[1];
389 PerformanceEntryType entry_type = ToPerformanceEntryTypeEnum(*type);
390 AliasedUint32Array& observers = env->performance_state()->observers;
391 if (entry_type != NODE_PERFORMANCE_ENTRY_TYPE_INVALID &&
392 observers[entry_type]) {
393 USE(env->performance_entry_callback()->
394 Call(env->context(), Undefined(env->isolate()), 1, &entry));
395 }
396 }
397
398 // Return idle time of the event loop
LoopIdleTime(const FunctionCallbackInfo<Value> & args)399 void LoopIdleTime(const FunctionCallbackInfo<Value>& args) {
400 Environment* env = Environment::GetCurrent(args);
401 uint64_t idle_time = uv_metrics_idle_time(env->event_loop());
402 args.GetReturnValue().Set(1.0 * idle_time / 1e6);
403 }
404
405 // Event Loop Timing Histogram
New(const FunctionCallbackInfo<Value> & args)406 void ELDHistogram::New(const FunctionCallbackInfo<Value>& args) {
407 Environment* env = Environment::GetCurrent(args);
408 CHECK(args.IsConstructCall());
409 int32_t resolution = args[0].As<Int32>()->Value();
410 CHECK_GT(resolution, 0);
411 new ELDHistogram(env, args.This(), resolution);
412 }
413
Initialize(Environment * env,Local<Object> target)414 void ELDHistogram::Initialize(Environment* env, Local<Object> target) {
415 Local<FunctionTemplate> tmpl = env->NewFunctionTemplate(New);
416 tmpl->Inherit(IntervalHistogram::GetConstructorTemplate(env));
417 tmpl->InstanceTemplate()->SetInternalFieldCount(
418 ELDHistogram::kInternalFieldCount);
419 env->SetConstructorFunction(target, "ELDHistogram", tmpl);
420 }
421
ELDHistogram(Environment * env,Local<Object> wrap,int32_t interval)422 ELDHistogram::ELDHistogram(
423 Environment* env,
424 Local<Object> wrap,
425 int32_t interval)
426 : IntervalHistogram(
427 env,
428 wrap,
429 AsyncWrap::PROVIDER_ELDHISTOGRAM,
430 interval, 1, 3.6e12, 3) {}
431
OnInterval()432 void ELDHistogram::OnInterval() {
433 uint64_t delta = histogram()->RecordDelta();
434 TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop),
435 "delay", delta);
436 TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop),
437 "min", histogram()->Min());
438 TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop),
439 "max", histogram()->Max());
440 TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop),
441 "mean", histogram()->Mean());
442 TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop),
443 "stddev", histogram()->Stddev());
444 }
445
Initialize(Local<Object> target,Local<Value> unused,Local<Context> context,void * priv)446 void Initialize(Local<Object> target,
447 Local<Value> unused,
448 Local<Context> context,
449 void* priv) {
450 Environment* env = Environment::GetCurrent(context);
451 Isolate* isolate = env->isolate();
452 PerformanceState* state = env->performance_state();
453
454 target->Set(context,
455 FIXED_ONE_BYTE_STRING(isolate, "observerCounts"),
456 state->observers.GetJSArray()).Check();
457 target->Set(context,
458 FIXED_ONE_BYTE_STRING(isolate, "milestones"),
459 state->milestones.GetJSArray()).Check();
460
461 Local<String> performanceEntryString =
462 FIXED_ONE_BYTE_STRING(isolate, "PerformanceEntry");
463
464 Local<FunctionTemplate> pe = FunctionTemplate::New(isolate);
465 pe->SetClassName(performanceEntryString);
466 Local<Function> fn = pe->GetFunction(context).ToLocalChecked();
467 target->Set(context, performanceEntryString, fn).Check();
468 env->set_performance_entry_template(fn);
469
470 env->SetMethod(target, "clearMark", ClearMark);
471 env->SetMethod(target, "mark", Mark);
472 env->SetMethod(target, "measure", Measure);
473 env->SetMethod(target, "markMilestone", MarkMilestone);
474 env->SetMethod(target, "setupObservers", SetupPerformanceObservers);
475 env->SetMethod(target, "timerify", Timerify);
476 env->SetMethod(target,
477 "installGarbageCollectionTracking",
478 InstallGarbageCollectionTracking);
479 env->SetMethod(target,
480 "removeGarbageCollectionTracking",
481 RemoveGarbageCollectionTracking);
482 env->SetMethod(target, "notify", Notify);
483 env->SetMethod(target, "loopIdleTime", LoopIdleTime);
484
485 Local<Object> constants = Object::New(isolate);
486
487 NODE_DEFINE_CONSTANT(constants, NODE_PERFORMANCE_GC_MAJOR);
488 NODE_DEFINE_CONSTANT(constants, NODE_PERFORMANCE_GC_MINOR);
489 NODE_DEFINE_CONSTANT(constants, NODE_PERFORMANCE_GC_INCREMENTAL);
490 NODE_DEFINE_CONSTANT(constants, NODE_PERFORMANCE_GC_WEAKCB);
491
492 NODE_DEFINE_CONSTANT(
493 constants, NODE_PERFORMANCE_GC_FLAGS_NO);
494 NODE_DEFINE_CONSTANT(
495 constants, NODE_PERFORMANCE_GC_FLAGS_CONSTRUCT_RETAINED);
496 NODE_DEFINE_CONSTANT(
497 constants, NODE_PERFORMANCE_GC_FLAGS_FORCED);
498 NODE_DEFINE_CONSTANT(
499 constants, NODE_PERFORMANCE_GC_FLAGS_SYNCHRONOUS_PHANTOM_PROCESSING);
500 NODE_DEFINE_CONSTANT(
501 constants, NODE_PERFORMANCE_GC_FLAGS_ALL_AVAILABLE_GARBAGE);
502 NODE_DEFINE_CONSTANT(
503 constants, NODE_PERFORMANCE_GC_FLAGS_ALL_EXTERNAL_MEMORY);
504 NODE_DEFINE_CONSTANT(
505 constants, NODE_PERFORMANCE_GC_FLAGS_SCHEDULE_IDLE);
506
507 #define V(name, _) \
508 NODE_DEFINE_HIDDEN_CONSTANT(constants, NODE_PERFORMANCE_ENTRY_TYPE_##name);
509 NODE_PERFORMANCE_ENTRY_TYPES(V)
510 #undef V
511
512 #define V(name, _) \
513 NODE_DEFINE_HIDDEN_CONSTANT(constants, NODE_PERFORMANCE_MILESTONE_##name);
514 NODE_PERFORMANCE_MILESTONES(V)
515 #undef V
516
517 PropertyAttribute attr =
518 static_cast<PropertyAttribute>(ReadOnly | DontDelete);
519
520 target->DefineOwnProperty(context,
521 FIXED_ONE_BYTE_STRING(isolate, "timeOrigin"),
522 Number::New(isolate, timeOrigin / 1e6),
523 attr).ToChecked();
524
525 target->DefineOwnProperty(
526 context,
527 FIXED_ONE_BYTE_STRING(isolate, "timeOriginTimestamp"),
528 Number::New(isolate, timeOriginTimestamp / MICROS_PER_MILLIS),
529 attr).ToChecked();
530
531 target->DefineOwnProperty(context,
532 env->constants_string(),
533 constants,
534 attr).ToChecked();
535
536 HistogramBase::Initialize(env, target);
537 ELDHistogram::Initialize(env, target);
538 }
539
540 } // namespace performance
541 } // namespace node
542
543 NODE_MODULE_CONTEXT_AWARE_INTERNAL(performance, node::performance::Initialize)
544