Skip to content

Commit

Permalink
lib: add tracing channel to diagnostics_channel
Browse files Browse the repository at this point in the history
PR-URL: #44943
Reviewed-By: Matteo Collina <[email protected]>
Reviewed-By: Benjamin Gruenbaum <[email protected]>
Reviewed-By: Chengzhong Wu <[email protected]>
Reviewed-By: Colin Ihrig <[email protected]>
Reviewed-By: Gerhard Stöbich <[email protected]>
Reviewed-By: Rafael Gonzaga <[email protected]>
Reviewed-By: Bryan English <[email protected]>
  • Loading branch information
Stephen Belanger authored and RafaelGSS committed Apr 7, 2023
1 parent 756a697 commit f8c947f
Show file tree
Hide file tree
Showing 13 changed files with 1,309 additions and 35 deletions.
647 changes: 647 additions & 0 deletions doc/api/diagnostics_channel.md

Large diffs are not rendered by default.

307 changes: 282 additions & 25 deletions lib/diagnostics_channel.js

Large diffs are not rendered by default.

13 changes: 4 additions & 9 deletions src/node_util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -262,18 +262,13 @@ void WeakReference::Get(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(weak_ref->target_.Get(isolate));
}

void WeakReference::GetRef(const FunctionCallbackInfo<Value>& args) {
WeakReference* weak_ref = Unwrap<WeakReference>(args.Holder());
Isolate* isolate = args.GetIsolate();
args.GetReturnValue().Set(
v8::Number::New(isolate, weak_ref->reference_count_));
}

void WeakReference::IncRef(const FunctionCallbackInfo<Value>& args) {
WeakReference* weak_ref = Unwrap<WeakReference>(args.Holder());
weak_ref->reference_count_++;
if (weak_ref->target_.IsEmpty()) return;
if (weak_ref->reference_count_ == 1) weak_ref->target_.ClearWeak();
args.GetReturnValue().Set(
v8::Number::New(args.GetIsolate(), weak_ref->reference_count_));
}

void WeakReference::DecRef(const FunctionCallbackInfo<Value>& args) {
Expand All @@ -282,6 +277,8 @@ void WeakReference::DecRef(const FunctionCallbackInfo<Value>& args) {
weak_ref->reference_count_--;
if (weak_ref->target_.IsEmpty()) return;
if (weak_ref->reference_count_ == 0) weak_ref->target_.SetWeak();
args.GetReturnValue().Set(
v8::Number::New(args.GetIsolate(), weak_ref->reference_count_));
}

static void GuessHandleType(const FunctionCallbackInfo<Value>& args) {
Expand Down Expand Up @@ -365,7 +362,6 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(ArrayBufferViewHasBuffer);
registry->Register(WeakReference::New);
registry->Register(WeakReference::Get);
registry->Register(WeakReference::GetRef);
registry->Register(WeakReference::IncRef);
registry->Register(WeakReference::DecRef);
registry->Register(GuessHandleType);
Expand Down Expand Up @@ -457,7 +453,6 @@ void Initialize(Local<Object> target,
WeakReference::kInternalFieldCount);
weak_ref->Inherit(BaseObject::GetConstructorTemplate(env));
SetProtoMethod(isolate, weak_ref, "get", WeakReference::Get);
SetProtoMethod(isolate, weak_ref, "getRef", WeakReference::GetRef);
SetProtoMethod(isolate, weak_ref, "incRef", WeakReference::IncRef);
SetProtoMethod(isolate, weak_ref, "decRef", WeakReference::DecRef);
SetConstructorFunction(context, target, "WeakReference", weak_ref);
Expand Down
1 change: 0 additions & 1 deletion src/node_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ class WeakReference : public SnapshotableObject {
v8::Local<v8::Object> target);
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Get(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetRef(const v8::FunctionCallbackInfo<v8::Value>& args);
static void IncRef(const v8::FunctionCallbackInfo<v8::Value>& args);
static void DecRef(const v8::FunctionCallbackInfo<v8::Value>& args);

Expand Down
108 changes: 108 additions & 0 deletions test/parallel/test-diagnostics-channel-bind-store.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
'use strict';

const common = require('../common');
const assert = require('assert');
const dc = require('diagnostics_channel');
const { AsyncLocalStorage } = require('async_hooks');

let n = 0;
const thisArg = new Date();
const inputs = [
{ foo: 'bar' },
{ baz: 'buz' },
];

const channel = dc.channel('test');

// Bind a storage directly to published data
const store1 = new AsyncLocalStorage();
channel.bindStore(store1);
let store1bound = true;

// Bind a store with transformation of published data
const store2 = new AsyncLocalStorage();
channel.bindStore(store2, common.mustCall((data) => {
assert.strictEqual(data, inputs[n]);
return { data };
}, 4));

// Regular subscribers should see publishes from runStores calls
channel.subscribe(common.mustCall((data) => {
if (store1bound) {
assert.deepStrictEqual(data, store1.getStore());
}
assert.deepStrictEqual({ data }, store2.getStore());
assert.strictEqual(data, inputs[n]);
}, 4));

// Verify stores are empty before run
assert.strictEqual(store1.getStore(), undefined);
assert.strictEqual(store2.getStore(), undefined);

channel.runStores(inputs[n], common.mustCall(function(a, b) {
// Verify this and argument forwarding
assert.strictEqual(this, thisArg);
assert.strictEqual(a, 1);
assert.strictEqual(b, 2);

// Verify store 1 state matches input
assert.strictEqual(store1.getStore(), inputs[n]);

// Verify store 2 state has expected transformation
assert.deepStrictEqual(store2.getStore(), { data: inputs[n] });

// Should support nested contexts
n++;
channel.runStores(inputs[n], common.mustCall(function() {
// Verify this and argument forwarding
assert.strictEqual(this, undefined);

// Verify store 1 state matches input
assert.strictEqual(store1.getStore(), inputs[n]);

// Verify store 2 state has expected transformation
assert.deepStrictEqual(store2.getStore(), { data: inputs[n] });
}));
n--;

// Verify store 1 state matches input
assert.strictEqual(store1.getStore(), inputs[n]);

// Verify store 2 state has expected transformation
assert.deepStrictEqual(store2.getStore(), { data: inputs[n] });
}), thisArg, 1, 2);

// Verify stores are empty after run
assert.strictEqual(store1.getStore(), undefined);
assert.strictEqual(store2.getStore(), undefined);

// Verify unbinding works
assert.ok(channel.unbindStore(store1));
store1bound = false;

// Verify unbinding a store that is not bound returns false
assert.ok(!channel.unbindStore(store1));

n++;
channel.runStores(inputs[n], common.mustCall(() => {
// Verify after unbinding store 1 will remain undefined
assert.strictEqual(store1.getStore(), undefined);

// Verify still bound store 2 receives expected data
assert.deepStrictEqual(store2.getStore(), { data: inputs[n] });
}));

// Contain transformer errors and emit on next tick
const fail = new Error('fail');
channel.bindStore(store1, () => {
throw fail;
});

let calledRunStores = false;
process.once('uncaughtException', common.mustCall((err) => {
assert.strictEqual(calledRunStores, true);
assert.strictEqual(err, fail);
}));

channel.runStores(inputs[n], common.mustCall());
calledRunStores = true;
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
'use strict';

const common = require('../common');
const dc = require('diagnostics_channel');
const assert = require('assert');

const channel = dc.tracingChannel('test');

const expectedError = new Error('test');
const input = { foo: 'bar' };
const thisArg = { baz: 'buz' };

function check(found) {
assert.deepStrictEqual(found, input);
}

const handlers = {
start: common.mustCall(check, 2),
end: common.mustCall(check, 2),
asyncStart: common.mustCall(check, 2),
asyncEnd: common.mustCall(check, 2),
error: common.mustCall((found) => {
check(found);
assert.deepStrictEqual(found.error, expectedError);
}, 2)
};

channel.subscribe(handlers);

channel.traceCallback(function(cb, err) {
assert.deepStrictEqual(this, thisArg);
setImmediate(cb, err);
}, 0, input, thisArg, common.mustCall((err, res) => {
assert.strictEqual(err, expectedError);
assert.strictEqual(res, undefined);
}), expectedError);

channel.tracePromise(function(value) {
assert.deepStrictEqual(this, thisArg);
return Promise.reject(value);
}, input, thisArg, expectedError).then(
common.mustNotCall(),
common.mustCall((value) => {
assert.deepStrictEqual(value, expectedError);
})
);
60 changes: 60 additions & 0 deletions test/parallel/test-diagnostics-channel-tracing-channel-async.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
'use strict';

const common = require('../common');
const dc = require('diagnostics_channel');
const assert = require('assert');

const channel = dc.tracingChannel('test');

const expectedResult = { foo: 'bar' };
const input = { foo: 'bar' };
const thisArg = { baz: 'buz' };

function check(found) {
assert.deepStrictEqual(found, input);
}

const handlers = {
start: common.mustCall(check, 2),
end: common.mustCall(check, 2),
asyncStart: common.mustCall((found) => {
check(found);
assert.strictEqual(found.error, undefined);
assert.deepStrictEqual(found.result, expectedResult);
}, 2),
asyncEnd: common.mustCall((found) => {
check(found);
assert.strictEqual(found.error, undefined);
assert.deepStrictEqual(found.result, expectedResult);
}, 2),
error: common.mustNotCall()
};

channel.subscribe(handlers);

channel.traceCallback(function(cb, err, res) {
assert.deepStrictEqual(this, thisArg);
setImmediate(cb, err, res);
}, 0, input, thisArg, common.mustCall((err, res) => {
assert.strictEqual(err, null);
assert.deepStrictEqual(res, expectedResult);
}), null, expectedResult);

channel.tracePromise(function(value) {
assert.deepStrictEqual(this, thisArg);
return Promise.resolve(value);
}, input, thisArg, expectedResult).then(
common.mustCall((value) => {
assert.deepStrictEqual(value, expectedResult);
}),
common.mustNotCall()
);

let failed = false;
try {
channel.traceCallback(common.mustNotCall(), 0, input, thisArg, 1, 2, 3);
} catch (err) {
assert.ok(/"callback" argument must be of type function/.test(err.message));
failed = true;
}
assert.strictEqual(failed, true);
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use strict';

const common = require('../common');
const { AsyncLocalStorage } = require('async_hooks');
const dc = require('diagnostics_channel');
const assert = require('assert');

const channel = dc.tracingChannel('test');
const store = new AsyncLocalStorage();

const firstContext = { foo: 'bar' };
const secondContext = { baz: 'buz' };

channel.start.bindStore(store, common.mustCall(() => {
return firstContext;
}));

channel.asyncStart.bindStore(store, common.mustCall(() => {
return secondContext;
}));

assert.strictEqual(store.getStore(), undefined);
channel.traceCallback(common.mustCall((cb) => {
assert.deepStrictEqual(store.getStore(), firstContext);
setImmediate(cb);
}), 0, {}, null, common.mustCall(() => {
assert.deepStrictEqual(store.getStore(), secondContext);
}));
assert.strictEqual(store.getStore(), undefined);
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use strict';

const common = require('../common');
const { setTimeout } = require('node:timers/promises');
const { AsyncLocalStorage } = require('async_hooks');
const dc = require('diagnostics_channel');
const assert = require('assert');

const channel = dc.tracingChannel('test');
const store = new AsyncLocalStorage();

const context = { foo: 'bar' };

channel.start.bindStore(store, common.mustCall(() => {
return context;
}));

assert.strictEqual(store.getStore(), undefined);
channel.tracePromise(common.mustCall(async () => {
assert.deepStrictEqual(store.getStore(), context);
await setTimeout(1);
assert.deepStrictEqual(store.getStore(), context);
}));
assert.strictEqual(store.getStore(), undefined);
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
'use strict';

const common = require('../common');
const { AsyncLocalStorage } = require('async_hooks');
const dc = require('diagnostics_channel');
const assert = require('assert');

const channel = dc.tracingChannel('test');
const store = new AsyncLocalStorage();

const context = { foo: 'bar' };

channel.start.bindStore(store, common.mustCall(() => {
return context;
}));

assert.strictEqual(store.getStore(), undefined);
channel.traceSync(common.mustCall(() => {
assert.deepStrictEqual(store.getStore(), context);
}));
assert.strictEqual(store.getStore(), undefined);
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
'use strict';

const common = require('../common');
const dc = require('diagnostics_channel');
const assert = require('assert');

const channel = dc.tracingChannel('test');

const expectedError = new Error('test');
const input = { foo: 'bar' };
const thisArg = { baz: 'buz' };

function check(found) {
assert.deepStrictEqual(found, input);
}

const handlers = {
start: common.mustCall(check),
end: common.mustCall(check),
asyncStart: common.mustNotCall(),
asyncEnd: common.mustNotCall(),
error: common.mustCall((found) => {
check(found);
assert.deepStrictEqual(found.error, expectedError);
})
};

channel.subscribe(handlers);
try {
channel.traceSync(function(err) {
assert.deepStrictEqual(this, thisArg);
assert.strictEqual(err, expectedError);
throw err;
}, input, thisArg, expectedError);

throw new Error('It should not reach this error');
} catch (error) {
assert.deepStrictEqual(error, expectedError);
}
Loading

0 comments on commit f8c947f

Please sign in to comment.