Skip to content

Commit d217b28

Browse files
committedNov 16, 2017
async_hooks: add trace events to async_hooks
This will allow trace event to record timing information for all asynchronous operations that are observed by async_hooks. PR-URL: #15538 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
1 parent ed0327b commit d217b28

18 files changed

+607
-73
lines changed
 

‎doc/api/tracing.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ Node.js application.
88

99
The set of categories for which traces are recorded can be specified using the
1010
`--trace-event-categories` flag followed by a list of comma separated category names.
11-
By default the `node` and `v8` categories are enabled.
11+
By default the `node`, `node.async_hooks`, and `v8` categories are enabled.
1212

1313
```txt
14-
node --trace-events-enabled --trace-event-categories v8,node server.js
14+
node --trace-events-enabled --trace-event-categories v8,node,node.async_hooks server.js
1515
```
1616

1717
Running Node.js with tracing enabled will produce log files that can be opened

‎lib/_http_common.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ const { methods, HTTPParser } = binding;
2727
const FreeList = require('internal/freelist');
2828
const { ondrain } = require('internal/http');
2929
const incoming = require('_http_incoming');
30-
const { emitDestroy } = require('async_hooks');
3130
const {
3231
IncomingMessage,
3332
readStart,
@@ -218,7 +217,7 @@ function freeParser(parser, req, socket) {
218217
} else {
219218
// Since the Parser destructor isn't going to run the destroy() callbacks
220219
// it needs to be triggered manually.
221-
emitDestroy(parser.getAsyncId());
220+
parser.free();
222221
}
223222
}
224223
if (req) {

‎lib/internal/bootstrap_node.js

+1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
if (global.__coverage__)
6060
NativeModule.require('internal/process/write-coverage').setup();
6161

62+
NativeModule.require('internal/trace_events_async_hooks').setup();
6263
NativeModule.require('internal/inspector_async_hook').setup();
6364

6465
// Do not initialize channel in debugger agent, it deletes env variable
+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
'use strict';
2+
3+
const trace_events = process.binding('trace_events');
4+
const async_wrap = process.binding('async_wrap');
5+
const async_hooks = require('async_hooks');
6+
7+
// Use small letters such that chrome://traceing groups by the name.
8+
// The behaviour is not only useful but the same as the events emitted using
9+
// the specific C++ macros.
10+
const BEFORE_EVENT = 'b'.charCodeAt(0);
11+
const END_EVENT = 'e'.charCodeAt(0);
12+
13+
// In trace_events it is not only the id but also the name that needs to be
14+
// repeated. Since async_hooks doesn't expose the provider type in the
15+
// non-init events, use a map to manually map the asyncId to the type name.
16+
const typeMemory = new Map();
17+
18+
// It is faster to emit trace_events directly from C++. Thus, this happens in
19+
// async_wrap.cc. However, events emitted from the JavaScript API or the
20+
// Embedder C++ API can't be emitted from async_wrap.cc. Thus they are
21+
// emitted using the JavaScript API. To prevent emitting the same event
22+
// twice the async_wrap.Providers list is used to filter the events.
23+
const nativeProviders = new Set(Object.keys(async_wrap.Providers));
24+
25+
const hook = async_hooks.createHook({
26+
init(asyncId, type, triggerAsyncId, resource) {
27+
if (nativeProviders.has(type)) return;
28+
29+
typeMemory.set(asyncId, type);
30+
trace_events.emit(BEFORE_EVENT, 'node.async_hooks',
31+
type, asyncId, 'triggerAsyncId', triggerAsyncId);
32+
},
33+
34+
before(asyncId) {
35+
const type = typeMemory.get(asyncId);
36+
if (type === undefined) return;
37+
38+
trace_events.emit(BEFORE_EVENT, 'node.async_hooks',
39+
type + '_CALLBACK', asyncId);
40+
},
41+
42+
after(asyncId) {
43+
const type = typeMemory.get(asyncId);
44+
if (type === undefined) return;
45+
46+
trace_events.emit(END_EVENT, 'node.async_hooks',
47+
type + '_CALLBACK', asyncId);
48+
},
49+
50+
destroy(asyncId) {
51+
const type = typeMemory.get(asyncId);
52+
if (type === undefined) return;
53+
54+
trace_events.emit(END_EVENT, 'node.async_hooks',
55+
type, asyncId);
56+
57+
// cleanup asyncId to type map
58+
typeMemory.delete(asyncId);
59+
}
60+
});
61+
62+
63+
exports.setup = function() {
64+
if (trace_events.categoryGroupEnabled('node.async_hooks')) {
65+
hook.enable();
66+
}
67+
};

‎node.gyp

+2
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@
121121
'lib/internal/socket_list.js',
122122
'lib/internal/test/unicode.js',
123123
'lib/internal/tls.js',
124+
'lib/internal/trace_events_async_hooks.js',
124125
'lib/internal/url.js',
125126
'lib/internal/util.js',
126127
'lib/internal/util/comparisons.js',
@@ -213,6 +214,7 @@
213214
'src/node_platform.cc',
214215
'src/node_perf.cc',
215216
'src/node_serdes.cc',
217+
'src/node_trace_events.cc',
216218
'src/node_url.cc',
217219
'src/node_util.cc',
218220
'src/node_v8.cc',

‎src/async-wrap.cc

+85-17
Original file line numberDiff line numberDiff line change
@@ -167,18 +167,6 @@ static void DestroyAsyncIdsCallback(uv_timer_t* handle) {
167167
}
168168

169169

170-
static void PushBackDestroyAsyncId(Environment* env, double id) {
171-
if (env->async_hooks()->fields()[AsyncHooks::kDestroy] == 0)
172-
return;
173-
174-
if (env->destroy_async_id_list()->empty())
175-
uv_timer_start(env->destroy_async_ids_timer_handle(),
176-
DestroyAsyncIdsCallback, 0, 0);
177-
178-
env->destroy_async_id_list()->push_back(id);
179-
}
180-
181-
182170
void AsyncWrap::EmitPromiseResolve(Environment* env, double async_id) {
183171
AsyncHooks* async_hooks = env->async_hooks();
184172

@@ -198,6 +186,21 @@ void AsyncWrap::EmitPromiseResolve(Environment* env, double async_id) {
198186
}
199187

200188

189+
void AsyncWrap::EmitTraceEventBefore() {
190+
switch (provider_type()) {
191+
#define V(PROVIDER) \
192+
case PROVIDER_ ## PROVIDER: \
193+
TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("node.async_hooks", \
194+
#PROVIDER "_CALLBACK", static_cast<int64_t>(get_async_id())); \
195+
break;
196+
NODE_ASYNC_PROVIDER_TYPES(V)
197+
#undef V
198+
default:
199+
UNREACHABLE();
200+
}
201+
}
202+
203+
201204
void AsyncWrap::EmitBefore(Environment* env, double async_id) {
202205
AsyncHooks* async_hooks = env->async_hooks();
203206

@@ -217,6 +220,21 @@ void AsyncWrap::EmitBefore(Environment* env, double async_id) {
217220
}
218221

219222

223+
void AsyncWrap::EmitTraceEventAfter() {
224+
switch (provider_type()) {
225+
#define V(PROVIDER) \
226+
case PROVIDER_ ## PROVIDER: \
227+
TRACE_EVENT_NESTABLE_ASYNC_END0("node.async_hooks", \
228+
#PROVIDER "_CALLBACK", static_cast<int64_t>(get_async_id())); \
229+
break;
230+
NODE_ASYNC_PROVIDER_TYPES(V)
231+
#undef V
232+
default:
233+
UNREACHABLE();
234+
}
235+
}
236+
237+
220238
void AsyncWrap::EmitAfter(Environment* env, double async_id) {
221239
AsyncHooks* async_hooks = env->async_hooks();
222240

@@ -327,8 +345,10 @@ static void PromiseHook(PromiseHookType type, Local<Promise> promise,
327345
if (type == PromiseHookType::kBefore) {
328346
env->async_hooks()->push_async_ids(
329347
wrap->get_async_id(), wrap->get_trigger_async_id());
348+
wrap->EmitTraceEventBefore();
330349
AsyncWrap::EmitBefore(wrap->env(), wrap->get_async_id());
331350
} else if (type == PromiseHookType::kAfter) {
351+
wrap->EmitTraceEventAfter();
332352
AsyncWrap::EmitAfter(wrap->env(), wrap->get_async_id());
333353
if (env->execution_async_id() == wrap->get_async_id()) {
334354
// This condition might not be true if async_hooks was enabled during
@@ -455,7 +475,8 @@ void AsyncWrap::AsyncReset(const FunctionCallbackInfo<Value>& args) {
455475

456476
void AsyncWrap::QueueDestroyAsyncId(const FunctionCallbackInfo<Value>& args) {
457477
CHECK(args[0]->IsNumber());
458-
PushBackDestroyAsyncId(Environment::GetCurrent(args), args[0]->NumberValue());
478+
AsyncWrap::EmitDestroy(
479+
Environment::GetCurrent(args), args[0]->NumberValue());
459480
}
460481

461482
void AsyncWrap::AddWrapMethods(Environment* env,
@@ -604,7 +625,34 @@ AsyncWrap::AsyncWrap(Environment* env,
604625

605626

606627
AsyncWrap::~AsyncWrap() {
607-
PushBackDestroyAsyncId(env(), get_async_id());
628+
EmitTraceEventDestroy();
629+
EmitDestroy(env(), get_async_id());
630+
}
631+
632+
void AsyncWrap::EmitTraceEventDestroy() {
633+
switch (provider_type()) {
634+
#define V(PROVIDER) \
635+
case PROVIDER_ ## PROVIDER: \
636+
TRACE_EVENT_NESTABLE_ASYNC_END0("node.async_hooks", \
637+
#PROVIDER, static_cast<int64_t>(get_async_id())); \
638+
break;
639+
NODE_ASYNC_PROVIDER_TYPES(V)
640+
#undef V
641+
default:
642+
UNREACHABLE();
643+
}
644+
}
645+
646+
void AsyncWrap::EmitDestroy(Environment* env, double async_id) {
647+
if (env->async_hooks()->fields()[AsyncHooks::kDestroy] == 0)
648+
return;
649+
650+
if (env->destroy_async_id_list()->empty()) {
651+
uv_timer_start(env->destroy_async_ids_timer_handle(),
652+
DestroyAsyncIdsCallback, 0, 0);
653+
}
654+
655+
env->destroy_async_id_list()->push_back(async_id);
608656
}
609657

610658

@@ -616,6 +664,19 @@ void AsyncWrap::AsyncReset(double execution_async_id, bool silent) {
616664
execution_async_id == -1 ? env()->new_async_id() : execution_async_id;
617665
trigger_async_id_ = env()->get_init_trigger_async_id();
618666

667+
switch (provider_type()) {
668+
#define V(PROVIDER) \
669+
case PROVIDER_ ## PROVIDER: \
670+
TRACE_EVENT_NESTABLE_ASYNC_BEGIN1("node.async_hooks", \
671+
#PROVIDER, static_cast<int64_t>(get_async_id()), \
672+
"triggerAsyncId", static_cast<int64_t>(get_trigger_async_id())); \
673+
break;
674+
NODE_ASYNC_PROVIDER_TYPES(V)
675+
#undef V
676+
default:
677+
UNREACHABLE();
678+
}
679+
619680
if (silent) return;
620681

621682
EmitAsyncInit(env(), object(),
@@ -662,8 +723,15 @@ void AsyncWrap::EmitAsyncInit(Environment* env,
662723
MaybeLocal<Value> AsyncWrap::MakeCallback(const Local<Function> cb,
663724
int argc,
664725
Local<Value>* argv) {
726+
EmitTraceEventBefore();
727+
665728
async_context context { get_async_id(), get_trigger_async_id() };
666-
return InternalMakeCallback(env(), object(), cb, argc, argv, context);
729+
MaybeLocal<Value> ret = InternalMakeCallback(
730+
env(), object(), cb, argc, argv, context);
731+
732+
EmitTraceEventAfter();
733+
734+
return ret;
667735
}
668736

669737

@@ -713,8 +781,8 @@ async_context EmitAsyncInit(Isolate* isolate,
713781
}
714782

715783
void EmitAsyncDestroy(Isolate* isolate, async_context asyncContext) {
716-
PushBackDestroyAsyncId(Environment::GetCurrent(isolate),
717-
asyncContext.async_id);
784+
AsyncWrap::EmitDestroy(
785+
Environment::GetCurrent(isolate), asyncContext.async_id);
718786
}
719787

720788
} // namespace node

‎src/async-wrap.h

+10-4
Original file line numberDiff line numberDiff line change
@@ -128,12 +128,18 @@ class AsyncWrap : public BaseObject {
128128
static void EmitAsyncInit(Environment* env,
129129
v8::Local<v8::Object> object,
130130
v8::Local<v8::String> type,
131-
double id,
131+
double async_id,
132132
double trigger_async_id);
133133

134-
static void EmitBefore(Environment* env, double id);
135-
static void EmitAfter(Environment* env, double id);
136-
static void EmitPromiseResolve(Environment* env, double id);
134+
static void EmitDestroy(Environment* env, double async_id);
135+
static void EmitBefore(Environment* env, double async_id);
136+
static void EmitAfter(Environment* env, double async_id);
137+
static void EmitPromiseResolve(Environment* env, double async_id);
138+
139+
void EmitTraceEventBefore();
140+
void EmitTraceEventAfter();
141+
void EmitTraceEventDestroy();
142+
137143

138144
inline ProviderType provider_type() const;
139145

‎src/node.cc

+14-7
Original file line numberDiff line numberDiff line change
@@ -273,13 +273,20 @@ node::DebugOptions debug_options;
273273
static struct {
274274
#if NODE_USE_V8_PLATFORM
275275
void Initialize(int thread_pool_size) {
276-
tracing_agent_.reset(
277-
trace_enabled ? new tracing::Agent() : nullptr);
278-
platform_ = new NodePlatform(thread_pool_size,
279-
trace_enabled ? tracing_agent_->GetTracingController() : nullptr);
280-
V8::InitializePlatform(platform_);
281-
tracing::TraceEventHelper::SetTracingController(
282-
trace_enabled ? tracing_agent_->GetTracingController() : nullptr);
276+
if (trace_enabled) {
277+
tracing_agent_.reset(new tracing::Agent());
278+
platform_ = new NodePlatform(thread_pool_size,
279+
tracing_agent_->GetTracingController());
280+
V8::InitializePlatform(platform_);
281+
tracing::TraceEventHelper::SetTracingController(
282+
tracing_agent_->GetTracingController());
283+
} else {
284+
tracing_agent_.reset(nullptr);
285+
platform_ = new NodePlatform(thread_pool_size, nullptr);
286+
V8::InitializePlatform(platform_);
287+
tracing::TraceEventHelper::SetTracingController(
288+
new v8::TracingController());
289+
}
283290
}
284291

285292
void Dispose() {

‎src/node_http_parser.cc

+13
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,18 @@ class Parser : public AsyncWrap {
397397
}
398398

399399

400+
static void Free(const FunctionCallbackInfo<Value>& args) {
401+
Environment* env = Environment::GetCurrent(args);
402+
Parser* parser;
403+
ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder());
404+
405+
// Since the Parser destructor isn't going to run the destroy() callbacks
406+
// it needs to be triggered manually.
407+
parser->EmitTraceEventDestroy();
408+
parser->EmitDestroy(env, parser->get_async_id());
409+
}
410+
411+
400412
void Save() {
401413
url_.Save();
402414
status_message_.Save();
@@ -792,6 +804,7 @@ void InitHttpParser(Local<Object> target,
792804

793805
AsyncWrap::AddWrapMethods(env, t);
794806
env->SetProtoMethod(t, "close", Parser::Close);
807+
env->SetProtoMethod(t, "free", Parser::Free);
795808
env->SetProtoMethod(t, "execute", Parser::Execute);
796809
env->SetProtoMethod(t, "finish", Parser::Finish);
797810
env->SetProtoMethod(t, "reinitialize", Parser::Reinitialize);

‎src/node_trace_events.cc

+136
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
#include "node_internals.h"
2+
#include "tracing/agent.h"
3+
4+
namespace node {
5+
6+
using v8::Context;
7+
using v8::FunctionCallbackInfo;
8+
using v8::Int32;
9+
using v8::Local;
10+
using v8::Object;
11+
using v8::Value;
12+
13+
// The tracing APIs require category groups to be pointers to long-lived
14+
// strings. Those strings are stored here.
15+
static std::unordered_set<std::string> categoryGroups;
16+
17+
// Gets a pointer to the category-enabled flags for a tracing category group,
18+
// if tracing is enabled for it.
19+
static const uint8_t* GetCategoryGroupEnabled(const char* category_group) {
20+
if (category_group == nullptr) return nullptr;
21+
22+
return TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED(category_group);
23+
}
24+
25+
static const char* GetCategoryGroup(Environment* env,
26+
const Local<Value> categoryValue) {
27+
CHECK(categoryValue->IsString());
28+
29+
Utf8Value category(env->isolate(), categoryValue);
30+
// If the value already exists in the set, insertion.first will point
31+
// to the existing value. Thus, this will maintain a long lived pointer
32+
// to the category c-string.
33+
auto insertion = categoryGroups.insert(category.out());
34+
35+
// The returned insertion is a pair whose first item is the object that was
36+
// inserted or that blocked the insertion and second item is a boolean
37+
// indicating whether it was inserted.
38+
return insertion.first->c_str();
39+
}
40+
41+
static void Emit(const FunctionCallbackInfo<Value>& args) {
42+
Environment* env = Environment::GetCurrent(args);
43+
Local<Context> context = env->context();
44+
45+
// Args: [type, category, name, id, argName, argValue]
46+
CHECK_GE(args.Length(), 3);
47+
48+
// Check the category group first, to avoid doing more work if it's not
49+
// enabled.
50+
const char* category_group = GetCategoryGroup(env, args[1]);
51+
const uint8_t* category_group_enabled =
52+
GetCategoryGroupEnabled(category_group);
53+
if (*category_group_enabled == 0) return;
54+
55+
// get trace_event phase
56+
CHECK(args[0]->IsNumber());
57+
char phase = static_cast<char>(args[0]->Int32Value(context).ToChecked());
58+
59+
// get trace_event name
60+
CHECK(args[2]->IsString());
61+
Utf8Value nameValue(env->isolate(), args[2]);
62+
const char* name = nameValue.out();
63+
64+
// get trace_event id
65+
int64_t id = 0;
66+
bool has_id = false;
67+
if (args.Length() >= 4 && !args[3]->IsUndefined() && !args[3]->IsNull()) {
68+
has_id = true;
69+
CHECK(args[3]->IsNumber());
70+
id = args[3]->IntegerValue(context).ToChecked();
71+
}
72+
73+
// TODO(AndreasMadsen): Two extra arguments are not supported.
74+
// TODO(AndreasMadsen): String values are not supported.
75+
int32_t num_args = 0;
76+
const char* arg_names[1];
77+
uint8_t arg_types[1];
78+
uint64_t arg_values[1];
79+
80+
// Create Utf8Value in the function scope to prevent deallocation.
81+
// The c-string will be copied by TRACE_EVENT_API_ADD_TRACE_EVENT at the end.
82+
Utf8Value arg1NameValue(env->isolate(), args[4]);
83+
84+
if (args.Length() < 6 || (args[4]->IsUndefined() && args[5]->IsUndefined())) {
85+
num_args = 0;
86+
} else {
87+
num_args = 1;
88+
arg_types[0] = TRACE_VALUE_TYPE_INT;
89+
90+
CHECK(args[4]->IsString());
91+
arg_names[0] = arg1NameValue.out();
92+
93+
CHECK(args[5]->IsNumber());
94+
arg_values[0] = args[5]->IntegerValue(context).ToChecked();
95+
}
96+
97+
// The TRACE_EVENT_FLAG_COPY flag indicates that the name and argument
98+
// name should be copied thus they don't need to long-lived pointers.
99+
// The category name still won't be copied and thus need to be a long-lived
100+
// pointer.
101+
uint32_t flags = TRACE_EVENT_FLAG_COPY;
102+
if (has_id) {
103+
flags |= TRACE_EVENT_FLAG_HAS_ID;
104+
}
105+
106+
const char* scope = node::tracing::kGlobalScope;
107+
uint64_t bind_id = node::tracing::kNoId;
108+
109+
TRACE_EVENT_API_ADD_TRACE_EVENT(
110+
phase, category_group_enabled, name, scope, id, bind_id,
111+
num_args, arg_names, arg_types, arg_values,
112+
flags);
113+
}
114+
115+
static void CategoryGroupEnabled(const FunctionCallbackInfo<Value>& args) {
116+
Environment* env = Environment::GetCurrent(args);
117+
118+
const char* category_group = GetCategoryGroup(env, args[0]);
119+
const uint8_t* category_group_enabled =
120+
GetCategoryGroupEnabled(category_group);
121+
args.GetReturnValue().Set(*category_group_enabled > 0);
122+
}
123+
124+
void InitializeTraceEvents(Local<Object> target,
125+
Local<Value> unused,
126+
Local<Context> context,
127+
void* priv) {
128+
Environment* env = Environment::GetCurrent(context);
129+
130+
env->SetMethod(target, "emit", Emit);
131+
env->SetMethod(target, "categoryGroupEnabled", CategoryGroupEnabled);
132+
}
133+
134+
} // namespace node
135+
136+
NODE_MODULE_CONTEXT_AWARE_BUILTIN(trace_events, node::InitializeTraceEvents)

‎src/tracing/agent.cc

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ void Agent::Start(const string& enabled_categories) {
3636
} else {
3737
trace_config->AddIncludedCategory("v8");
3838
trace_config->AddIncludedCategory("node");
39+
trace_config->AddIncludedCategory("node.async_hooks");
3940
}
4041

4142
// This thread should be created *after* async handles are created

‎test/parallel/test-trace-event.js

-41
This file was deleted.
+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const cp = require('child_process');
5+
const fs = require('fs');
6+
7+
const CODE =
8+
'setTimeout(() => { for (var i = 0; i < 100000; i++) { "test" + i } }, 1)';
9+
const FILE_NAME = 'node_trace.1.log';
10+
11+
common.refreshTmpDir();
12+
process.chdir(common.tmpDir);
13+
14+
const proc = cp.spawn(process.execPath,
15+
[ '--trace-events-enabled', '-e', CODE ]);
16+
17+
proc.once('exit', common.mustCall(() => {
18+
assert(common.fileExists(FILE_NAME));
19+
fs.readFile(FILE_NAME, common.mustCall((err, data) => {
20+
const traces = JSON.parse(data.toString()).traceEvents;
21+
assert(traces.length > 0);
22+
// V8 trace events should be generated.
23+
assert(traces.some((trace) => {
24+
if (trace.pid !== proc.pid)
25+
return false;
26+
if (trace.cat !== 'v8')
27+
return false;
28+
if (trace.name !== 'V8.ScriptCompiler')
29+
return false;
30+
return true;
31+
}));
32+
33+
// C++ async_hooks trace events should be generated.
34+
assert(traces.some((trace) => {
35+
if (trace.pid !== proc.pid)
36+
return false;
37+
if (trace.cat !== 'node.async_hooks')
38+
return false;
39+
if (trace.name !== 'TIMERWRAP')
40+
return false;
41+
return true;
42+
}));
43+
44+
45+
// JavaScript async_hooks trace events should be generated.
46+
assert(traces.some((trace) => {
47+
if (trace.pid !== proc.pid)
48+
return false;
49+
if (trace.cat !== 'node.async_hooks')
50+
return false;
51+
if (trace.name !== 'Timeout')
52+
return false;
53+
return true;
54+
}));
55+
}));
56+
}));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const cp = require('child_process');
5+
const fs = require('fs');
6+
7+
const CODE =
8+
'setTimeout(() => { for (var i = 0; i < 100000; i++) { "test" + i } }, 1)';
9+
const FILE_NAME = 'node_trace.1.log';
10+
11+
common.refreshTmpDir();
12+
process.chdir(common.tmpDir);
13+
14+
const proc = cp.spawn(process.execPath,
15+
[ '--trace-events-enabled',
16+
'--trace-event-categories', 'node.async_hooks',
17+
'-e', CODE ]);
18+
19+
proc.once('exit', common.mustCall(() => {
20+
assert(common.fileExists(FILE_NAME));
21+
fs.readFile(FILE_NAME, common.mustCall((err, data) => {
22+
const traces = JSON.parse(data.toString()).traceEvents;
23+
assert(traces.length > 0);
24+
// V8 trace events should be generated.
25+
assert(!traces.some((trace) => {
26+
if (trace.pid !== proc.pid)
27+
return false;
28+
if (trace.cat !== 'v8')
29+
return false;
30+
if (trace.name !== 'V8.ScriptCompiler')
31+
return false;
32+
return true;
33+
}));
34+
35+
// C++ async_hooks trace events should be generated.
36+
assert(traces.some((trace) => {
37+
if (trace.pid !== proc.pid)
38+
return false;
39+
if (trace.cat !== 'node.async_hooks')
40+
return false;
41+
if (trace.name !== 'TIMERWRAP')
42+
return false;
43+
return true;
44+
}));
45+
46+
47+
// JavaScript async_hooks trace events should be generated.
48+
assert(traces.some((trace) => {
49+
if (trace.pid !== proc.pid)
50+
return false;
51+
if (trace.cat !== 'node.async_hooks')
52+
return false;
53+
if (trace.name !== 'Timeout')
54+
return false;
55+
return true;
56+
}));
57+
}));
58+
}));
+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const cp = require('child_process');
5+
const fs = require('fs');
6+
7+
const CODE = `
8+
process.binding("trace_events").emit(
9+
'b'.charCodeAt(0), 'custom',
10+
'type-value', 10, 'extra-value', 20);
11+
process.binding("trace_events").emit(
12+
'b'.charCodeAt(0), 'custom',
13+
'type-value', 20);
14+
process.binding("trace_events").emit(
15+
'b'.charCodeAt(0), 'missing',
16+
'type-value', 10, 'extra-value', 20);
17+
`;
18+
const FILE_NAME = 'node_trace.1.log';
19+
20+
common.refreshTmpDir();
21+
process.chdir(common.tmpDir);
22+
23+
const proc = cp.spawn(process.execPath,
24+
[ '--trace-events-enabled',
25+
'--trace-event-categories', 'custom',
26+
'-e', CODE ]);
27+
28+
proc.once('exit', common.mustCall(() => {
29+
assert(common.fileExists(FILE_NAME));
30+
fs.readFile(FILE_NAME, common.mustCall((err, data) => {
31+
const traces = JSON.parse(data.toString()).traceEvents;
32+
assert.strictEqual(traces.length, 2);
33+
34+
assert.strictEqual(traces[0].pid, proc.pid);
35+
assert.strictEqual(traces[0].ph, 'b');
36+
assert.strictEqual(traces[0].cat, 'custom');
37+
assert.strictEqual(traces[0].name, 'type-value');
38+
assert.strictEqual(traces[0].id, '0xa');
39+
assert.deepStrictEqual(traces[0].args, { 'extra-value': 20 });
40+
41+
assert.strictEqual(traces[1].pid, proc.pid);
42+
assert.strictEqual(traces[1].ph, 'b');
43+
assert.strictEqual(traces[1].cat, 'custom');
44+
assert.strictEqual(traces[1].name, 'type-value');
45+
assert.strictEqual(traces[1].id, '0x14');
46+
assert.deepStrictEqual(traces[1].args, { });
47+
}));
48+
}));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const cp = require('child_process');
5+
6+
const CODE = `console.log(
7+
process.binding("trace_events").categoryGroupEnabled("custom")
8+
);`;
9+
10+
common.refreshTmpDir();
11+
process.chdir(common.tmpDir);
12+
13+
const procEnabled = cp.spawn(
14+
process.execPath,
15+
[ '--trace-events-enabled', '--trace-event-categories', 'custom', '-e', CODE ]
16+
);
17+
let procEnabledOutput = '';
18+
19+
procEnabled.stdout.on('data', (data) => procEnabledOutput += data);
20+
procEnabled.stderr.pipe(process.stderr);
21+
procEnabled.once('exit', common.mustCall(() => {
22+
assert.strictEqual(procEnabledOutput, 'true\n');
23+
}));
24+
25+
const procDisabled = cp.spawn(
26+
process.execPath,
27+
[ '--trace-events-enabled', '--trace-event-categories', 'other', '-e', CODE ]
28+
);
29+
let procDisabledOutput = '';
30+
31+
procDisabled.stdout.on('data', (data) => procDisabledOutput += data);
32+
procDisabled.stderr.pipe(process.stderr);
33+
procDisabled.once('exit', common.mustCall(() => {
34+
assert.strictEqual(procDisabledOutput, 'false\n');
35+
}));
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const cp = require('child_process');
5+
6+
const CODE =
7+
'setTimeout(() => { for (var i = 0; i < 100000; i++) { "test" + i } }, 1)';
8+
const FILE_NAME = 'node_trace.1.log';
9+
10+
common.refreshTmpDir();
11+
process.chdir(common.tmpDir);
12+
13+
const proc_no_categories = cp.spawn(
14+
process.execPath,
15+
[ '--trace-events-enabled', '--trace-event-categories', '""', '-e', CODE ]
16+
);
17+
18+
proc_no_categories.once('exit', common.mustCall(() => {
19+
assert(!common.fileExists(FILE_NAME));
20+
}));

‎test/parallel/test-trace-events-v8.js

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const cp = require('child_process');
5+
const fs = require('fs');
6+
7+
const CODE =
8+
'setTimeout(() => { for (var i = 0; i < 100000; i++) { "test" + i } }, 1)';
9+
const FILE_NAME = 'node_trace.1.log';
10+
11+
common.refreshTmpDir();
12+
process.chdir(common.tmpDir);
13+
14+
const proc = cp.spawn(process.execPath,
15+
[ '--trace-events-enabled',
16+
'--trace-event-categories', 'v8',
17+
'-e', CODE ]);
18+
19+
proc.once('exit', common.mustCall(() => {
20+
assert(common.fileExists(FILE_NAME));
21+
fs.readFile(FILE_NAME, common.mustCall((err, data) => {
22+
const traces = JSON.parse(data.toString()).traceEvents;
23+
assert(traces.length > 0);
24+
// V8 trace events should be generated.
25+
assert(traces.some((trace) => {
26+
if (trace.pid !== proc.pid)
27+
return false;
28+
if (trace.cat !== 'v8')
29+
return false;
30+
if (trace.name !== 'V8.ScriptCompiler')
31+
return false;
32+
return true;
33+
}));
34+
35+
// C++ async_hooks trace events should be generated.
36+
assert(!traces.some((trace) => {
37+
if (trace.pid !== proc.pid)
38+
return false;
39+
if (trace.cat !== 'node.async_hooks')
40+
return false;
41+
if (trace.name !== 'TIMERWRAP')
42+
return false;
43+
return true;
44+
}));
45+
46+
47+
// JavaScript async_hooks trace events should be generated.
48+
assert(!traces.some((trace) => {
49+
if (trace.pid !== proc.pid)
50+
return false;
51+
if (trace.cat !== 'node.async_hooks')
52+
return false;
53+
if (trace.name !== 'Timeout')
54+
return false;
55+
return true;
56+
}));
57+
}));
58+
}));

0 commit comments

Comments
 (0)
Please sign in to comment.