1'use strict'; 2 3const { 4 ObjectKeys, 5 SafeMap, 6 SafeSet, 7 Symbol, 8} = primordials; 9 10const { trace } = internalBinding('trace_events'); 11const async_wrap = internalBinding('async_wrap'); 12const async_hooks = require('async_hooks'); 13const { 14 CHAR_LOWERCASE_B, 15 CHAR_LOWERCASE_E, 16} = require('internal/constants'); 17 18// Use small letters such that chrome://tracing groups by the name. 19// The behavior is not only useful but the same as the events emitted using 20// the specific C++ macros. 21const kBeforeEvent = CHAR_LOWERCASE_B; 22const kEndEvent = CHAR_LOWERCASE_E; 23const kTraceEventCategory = 'node,node.async_hooks'; 24 25const kEnabled = Symbol('enabled'); 26 27// It is faster to emit traceEvents directly from C++. Thus, this happens 28// in async_wrap.cc. However, events emitted from the JavaScript API or the 29// Embedder C++ API can't be emitted from async_wrap.cc. Thus they are 30// emitted using the JavaScript API. To prevent emitting the same event 31// twice the async_wrap.Providers list is used to filter the events. 32const nativeProviders = new SafeSet(ObjectKeys(async_wrap.Providers)); 33const typeMemory = new SafeMap(); 34 35// Promises are not AsyncWrap resources so they should emit trace_events here. 36nativeProviders.delete('PROMISE'); 37 38function createHook() { 39 // In traceEvents it is not only the id but also the name that needs to be 40 // repeated. Since async_hooks doesn't expose the provider type in the 41 // non-init events, use a map to manually map the asyncId to the type name. 42 43 const hook = async_hooks.createHook({ 44 init(asyncId, type, triggerAsyncId, resource) { 45 if (nativeProviders.has(type)) return; 46 47 typeMemory.set(asyncId, type); 48 trace(kBeforeEvent, kTraceEventCategory, 49 type, asyncId, 50 { 51 triggerAsyncId, 52 executionAsyncId: async_hooks.executionAsyncId() 53 }); 54 }, 55 56 before(asyncId) { 57 const type = typeMemory.get(asyncId); 58 if (type === undefined) return; 59 60 trace(kBeforeEvent, kTraceEventCategory, `${type}_CALLBACK`, asyncId); 61 }, 62 63 after(asyncId) { 64 const type = typeMemory.get(asyncId); 65 if (type === undefined) return; 66 67 trace(kEndEvent, kTraceEventCategory, `${type}_CALLBACK`, asyncId); 68 }, 69 70 destroy(asyncId) { 71 const type = typeMemory.get(asyncId); 72 if (type === undefined) return; 73 74 trace(kEndEvent, kTraceEventCategory, type, asyncId); 75 76 // Cleanup asyncId to type map 77 typeMemory.delete(asyncId); 78 } 79 }); 80 81 return { 82 enable() { 83 if (this[kEnabled]) 84 return; 85 this[kEnabled] = true; 86 hook.enable(); 87 }, 88 89 disable() { 90 if (!this[kEnabled]) 91 return; 92 this[kEnabled] = false; 93 hook.disable(); 94 typeMemory.clear(); 95 } 96 }; 97} 98 99exports.createHook = createHook; 100