Skip to content

Commit 60ef53c

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 2e25922 commit 60ef53c

10 files changed

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

‎doc/api/diagnostics_channel.md‎

Lines changed: 180 additions & 0 deletions
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+
constdiagnostics_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+
constdiagnostics_channel=require('diagnostics_channel');
38+
39+
// Get a reusable channel object
40+
constchannel=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+
constdiagnostics_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+
constdiagnostics_channel=require('diagnostics_channel');
86+
87+
constchannel=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+
constdiagnostics_channel=require('diagnostics_channel');
112+
113+
constchannel=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+
constdiagnostics_channel=require('diagnostics_channel');
129+
130+
constchannel=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+
constdiagnostics_channel=require('diagnostics_channel');
149+
150+
constchannel=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+
constdiagnostics_channel=require('diagnostics_channel');
166+
167+
constchannel=diagnostics_channel.channel('my-channel');
168+
169+
functiononMessage(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‎

Lines changed: 1 addition & 0 deletions
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‎

Lines changed: 122 additions & 0 deletions
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+
classActiveChannel{
26+
subscribe(subscription){
27+
if(typeofsubscription!=='function'){
28+
thrownewERR_INVALID_ARG_TYPE('subscription',['function'],
29+
subscription);
30+
}
31+
ArrayPrototypePush(this._subscribers,subscription);
32+
}
33+
34+
unsubscribe(subscription){
35+
constindex=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+
gethasSubscribers(){
48+
returntrue;
49+
}
50+
51+
publish(data){
52+
for(leti=0;i<this._subscribers.length;i++){
53+
try{
54+
constonMessage=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+
classChannel{
66+
constructor(name){
67+
this._subscribers=undefined;
68+
this.name=name;
69+
}
70+
71+
static[SymbolHasInstance](instance){
72+
constprototype=ObjectGetPrototypeOf(instance);
73+
returnprototype===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+
gethasSubscribers(){
84+
returnfalse;
85+
}
86+
87+
publish(){}
88+
}
89+
90+
constchannels=ObjectCreate(null);
91+
92+
functionchannel(name){
93+
letchannel;
94+
constref=channels[name];
95+
if(ref)channel=ref.get();
96+
if(channel)returnchannel;
97+
98+
if(typeofname!=='string'&&typeofname!=='symbol'){
99+
thrownewERR_INVALID_ARG_TYPE('channel',['string','symbol'],name);
100+
}
101+
102+
channel=newChannel(name);
103+
channels[name]=newWeakReference(channel);
104+
returnchannel;
105+
}
106+
107+
functionhasSubscribers(name){
108+
letchannel;
109+
constref=channels[name];
110+
if(ref)channel=WeakRefPrototypeGet(ref);
111+
if(!channel){
112+
returnfalse;
113+
}
114+
115+
returnchannel.hasSubscribers;
116+
}
117+
118+
module.exports={
119+
channel,
120+
hasSubscribers,
121+
Channel
122+
};

‎node.gyp‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
'lib/constants.js',
4848
'lib/crypto.js',
4949
'lib/cluster.js',
50+
'lib/diagnostics_channel.js',
5051
'lib/dgram.js',
5152
'lib/dns.js',
5253
'lib/domain.js',

0 commit comments

Comments
 (0)