Skip to content

Commit b38a43d

Browse files
Qardtargos
authored andcommitted
lib: create diagnostics_channel module
PR-URL: #34895 Reviewed-By: Bryan English <bryan@bryanenglish.com> Reviewed-By: Gerhard Stöbich <deb2001-github@yahoo.de> Reviewed-By: Vladimir de Turckheim <vlad2t@hotmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Gabriel Schulhof <gabriel.schulhof@intel.com> Reviewed-By: Michael Dawson <midawson@redhat.com>
1 parent 66ad4be commit b38a43d

10 files changed

+444
-0
lines changed
+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
'use strict';
2+
const common = require('../common.js');
3+
const dc = require('diagnostics_channel');
4+
5+
const bench = common.createBenchmark(main, {
6+
n: [1e8],
7+
subscribers: [0, 1, 10],
8+
});
9+
10+
function noop() {}
11+
12+
function main({ n, subscribers }) {
13+
const channel = dc.channel('test');
14+
for (let i = 0; i < subscribers; i++) {
15+
channel.subscribe(noop);
16+
}
17+
18+
const data = {
19+
foo: 'bar'
20+
};
21+
22+
bench.start();
23+
for (let i = 0; i < n; i++) {
24+
if (channel.hasSubscribers) {
25+
channel.publish(data);
26+
}
27+
}
28+
bench.end(n);
29+
}
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use strict';
2+
const common = require('../common.js');
3+
const dc = require('diagnostics_channel');
4+
5+
const bench = common.createBenchmark(main, {
6+
n: [1e8],
7+
});
8+
9+
function noop() {}
10+
11+
function main({ n }) {
12+
const channel = dc.channel('channel.0');
13+
14+
bench.start();
15+
for (let i = 0; i < n; i++) {
16+
channel.subscribe(noop);
17+
}
18+
bench.end(n);
19+
}

doc/api/diagnostics_channel.md

+180
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
# Diagnostics Channel
2+
3+
<!--introduced_in=REPLACEME-->
4+
5+
> Stability: 1 - Experimental
6+
7+
<!-- source_link=lib/diagnostics_channel.js -->
8+
9+
The `diagnostics_channel` module provides an API to create named channels
10+
to report arbitrary message data for diagnostics purposes.
11+
12+
It can be accessed using:
13+
14+
```js
15+
const diagnostics_channel = require('diagnostics_channel');
16+
```
17+
18+
It is intended that a module writer wanting to report diagnostics messages
19+
will create one or many top-level channels to report messages through.
20+
Channels may also be acquired at runtime but it is not encouraged
21+
due to the additional overhead of doing so. Channels may be exported for
22+
convenience, but as long as the name is known it can be acquired anywhere.
23+
24+
If you intend for your module to produce diagnostics data for others to
25+
consume it is recommended that you include documentation of what named
26+
channels are used along with the shape of the message data. Channel names
27+
should generally include the module name to avoid collisions with data from
28+
other modules.
29+
30+
## Public API
31+
32+
### Overview
33+
34+
Following is a simple overview of the public API.
35+
36+
```js
37+
const diagnostics_channel = require('diagnostics_channel');
38+
39+
// Get a reusable channel object
40+
const channel = diagnostics_channel.channel('my-channel');
41+
42+
// Subscribe to the channel
43+
channel.subscribe((message, name) => {
44+
// Received data
45+
});
46+
47+
// Check if the channel has an active subscriber
48+
if (channel.hasSubscribers) {
49+
// Publish data to the channel
50+
channel.publish({
51+
some: 'data'
52+
});
53+
}
54+
```
55+
56+
#### `diagnostics_channel.hasSubscribers(name)`
57+
58+
* `name` {string|symbol} The channel name
59+
* Returns: {boolean} If there are active subscribers
60+
61+
Check if there are active subscribers to the named channel. This is helpful if
62+
the message you want to send might be expensive to prepare.
63+
64+
This API is optional but helpful when trying to publish messages from very
65+
performance-senstive code.
66+
67+
```js
68+
const diagnostics_channel = require('diagnostics_channel');
69+
70+
if (diagnostics_channel.hasSubscribers('my-channel')) {
71+
// There are subscribers, prepare and publish message
72+
}
73+
```
74+
75+
#### `diagnostics_channel.channel(name)`
76+
77+
* `name` {string|symbol} The channel name
78+
* Returns: {Channel} The named channel object
79+
80+
This is the primary entry-point for anyone wanting to interact with a named
81+
channel. It produces a channel object which is optimized to reduce overhead at
82+
publish time as much as possible.
83+
84+
```js
85+
const diagnostics_channel = require('diagnostics_channel');
86+
87+
const channel = diagnostics_channel.channel('my-channel');
88+
```
89+
90+
### Class: `Channel`
91+
92+
The class `Channel` represents an individual named channel within the data
93+
pipeline. It is use to track subscribers and to publish messages when there
94+
are subscribers present. It exists as a separate object to avoid channel
95+
lookups at publish time, enabling very fast publish speeds and allowing
96+
for heavy use while incurring very minimal cost. Channels are created with
97+
[`diagnostics_channel.channel(name)`][], constructing a channel directly
98+
with `new Channel(name)` is not supported.
99+
100+
#### `channel.hasSubscribers`
101+
102+
* Returns: {boolean} If there are active subscribers
103+
104+
Check if there are active subscribers to this channel. This is helpful if
105+
the message you want to send might be expensive to prepare.
106+
107+
This API is optional but helpful when trying to publish messages from very
108+
performance-senstive code.
109+
110+
```js
111+
const diagnostics_channel = require('diagnostics_channel');
112+
113+
const channel = diagnostics_channel.channel('my-channel');
114+
115+
if (channel.hasSubscribers) {
116+
// There are subscribers, prepare and publish message
117+
}
118+
```
119+
120+
#### `channel.publish(message)`
121+
122+
* `message` {any} The message to send to the channel subscribers
123+
124+
Publish a message to any subscribers to the channel. This will trigger
125+
message handlers synchronously so they will execute within the same context.
126+
127+
```js
128+
const diagnostics_channel = require('diagnostics_channel');
129+
130+
const channel = diagnostics_channel.channel('my-channel');
131+
132+
channel.publish({
133+
some: 'message'
134+
});
135+
```
136+
137+
#### `channel.subscribe(onMessage)`
138+
139+
* `onMessage` {Function} The handler to receive channel messages
140+
* `message` {any} The message data
141+
* `name` {string|symbol} The name of the channel
142+
143+
Register a message handler to subscribe to this channel. This message handler
144+
will be run synchronously whenever a message is published to the channel. Any
145+
errors thrown in the message handler will trigger an [`'uncaughtException'`][].
146+
147+
```js
148+
const diagnostics_channel = require('diagnostics_channel');
149+
150+
const channel = diagnostics_channel.channel('my-channel');
151+
152+
channel.subscribe((message, name) => {
153+
// Received data
154+
});
155+
```
156+
157+
#### `channel.unsubscribe(onMessage)`
158+
159+
* `onMessage` {Function} The previous subscribed handler to remove
160+
161+
Remove a message handler previously registered to this channel with
162+
[`channel.subscribe(onMessage)`][].
163+
164+
```js
165+
const diagnostics_channel = require('diagnostics_channel');
166+
167+
const channel = diagnostics_channel.channel('my-channel');
168+
169+
function onMessage(message, name) {
170+
// Received data
171+
}
172+
173+
channel.subscribe(onMessage);
174+
175+
channel.unsubscribe(onMessage);
176+
```
177+
178+
[`diagnostics_channel.channel(name)`]: #diagnostics_channel_diagnostics_channel_channel_name
179+
[`channel.subscribe(onMessage)`]: #diagnostics_channel_channel_subscribe_onmessage
180+
[`'uncaughtException'`]: process.md#process_event_uncaughtexception

doc/api/index.md

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
* [Crypto](crypto.md)
2424
* [Debugger](debugger.md)
2525
* [Deprecated APIs](deprecations.md)
26+
* [Diagnostics Channel](diagnostics_channel.md)
2627
* [DNS](dns.md)
2728
* [Domain](domain.md)
2829
* [Errors](errors.md)

lib/diagnostics_channel.js

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
'use strict';
2+
3+
const {
4+
ArrayPrototypeIndexOf,
5+
ArrayPrototypePush,
6+
ArrayPrototypeSplice,
7+
ObjectCreate,
8+
ObjectGetPrototypeOf,
9+
ObjectSetPrototypeOf,
10+
SymbolHasInstance,
11+
WeakRefPrototypeGet
12+
} = primordials;
13+
14+
const {
15+
codes: {
16+
ERR_INVALID_ARG_TYPE,
17+
}
18+
} = require('internal/errors');
19+
20+
const { triggerUncaughtException } = internalBinding('errors');
21+
22+
const { WeakReference } = internalBinding('util');
23+
24+
// TODO(qard): should there be a C++ channel interface?
25+
class ActiveChannel {
26+
subscribe(subscription) {
27+
if (typeof subscription !== 'function') {
28+
throw new ERR_INVALID_ARG_TYPE('subscription', ['function'],
29+
subscription);
30+
}
31+
ArrayPrototypePush(this._subscribers, subscription);
32+
}
33+
34+
unsubscribe(subscription) {
35+
const index = ArrayPrototypeIndexOf(this._subscribers, subscription);
36+
if (index >= 0) {
37+
ArrayPrototypeSplice(this._subscribers, index, 1);
38+
39+
// When there are no more active subscribers, restore to fast prototype.
40+
if (!this._subscribers.length) {
41+
// eslint-disable-next-line no-use-before-define
42+
ObjectSetPrototypeOf(this, Channel.prototype);
43+
}
44+
}
45+
}
46+
47+
get hasSubscribers() {
48+
return true;
49+
}
50+
51+
publish(data) {
52+
for (let i = 0; i < this._subscribers.length; i++) {
53+
try {
54+
const onMessage = this._subscribers[i];
55+
onMessage(data, this.name);
56+
} catch (err) {
57+
process.nextTick(() => {
58+
triggerUncaughtException(err, false);
59+
});
60+
}
61+
}
62+
}
63+
}
64+
65+
class Channel {
66+
constructor(name) {
67+
this._subscribers = undefined;
68+
this.name = name;
69+
}
70+
71+
static [SymbolHasInstance](instance) {
72+
const prototype = ObjectGetPrototypeOf(instance);
73+
return prototype === Channel.prototype ||
74+
prototype === ActiveChannel.prototype;
75+
}
76+
77+
subscribe(subscription) {
78+
ObjectSetPrototypeOf(this, ActiveChannel.prototype);
79+
this._subscribers = [];
80+
this.subscribe(subscription);
81+
}
82+
83+
get hasSubscribers() {
84+
return false;
85+
}
86+
87+
publish() {}
88+
}
89+
90+
const channels = ObjectCreate(null);
91+
92+
function channel(name) {
93+
let channel;
94+
const ref = channels[name];
95+
if (ref) channel = ref.get();
96+
if (channel) return channel;
97+
98+
if (typeof name !== 'string' && typeof name !== 'symbol') {
99+
throw new ERR_INVALID_ARG_TYPE('channel', ['string', 'symbol'], name);
100+
}
101+
102+
channel = new Channel(name);
103+
channels[name] = new WeakReference(channel);
104+
return channel;
105+
}
106+
107+
function hasSubscribers(name) {
108+
let channel;
109+
const ref = channels[name];
110+
if (ref) channel = WeakRefPrototypeGet(ref);
111+
if (!channel) {
112+
return false;
113+
}
114+
115+
return channel.hasSubscribers;
116+
}
117+
118+
module.exports = {
119+
channel,
120+
hasSubscribers,
121+
Channel
122+
};

node.gyp

+1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
'lib/constants.js',
5252
'lib/crypto.js',
5353
'lib/cluster.js',
54+
'lib/diagnostics_channel.js',
5455
'lib/dgram.js',
5556
'lib/dns.js',
5657
'lib/dns/promises.js',

0 commit comments

Comments
 (0)