From 891a918c85d717c814d98ee0ce01363970b99007 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 18 Oct 2021 13:31:08 -0700 Subject: [PATCH] grpc-js: Allow users to disable channelz --- packages/grpc-js/src/channel-options.ts | 2 + packages/grpc-js/src/channel.ts | 69 +++++++++++++----- packages/grpc-js/src/server.ts | 41 ++++++++--- packages/grpc-js/src/subchannel.ts | 94 ++++++++++++++++--------- 4 files changed, 147 insertions(+), 59 deletions(-) diff --git a/packages/grpc-js/src/channel-options.ts b/packages/grpc-js/src/channel-options.ts index 604fd8685..4e2ee6132 100644 --- a/packages/grpc-js/src/channel-options.ts +++ b/packages/grpc-js/src/channel-options.ts @@ -36,6 +36,7 @@ export interface ChannelOptions { 'grpc.enable_http_proxy'?: number; 'grpc.http_connect_target'?: string; 'grpc.http_connect_creds'?: string; + 'grpc.enable_channelz'?: number; 'grpc-node.max_session_memory'?: number; // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: string]: any; @@ -61,6 +62,7 @@ export const recognizedOptions = { 'grpc.max_send_message_length': true, 'grpc.max_receive_message_length': true, 'grpc.enable_http_proxy': true, + 'grpc.enable_channelz': true, 'grpc-node.max_session_memory': true, }; diff --git a/packages/grpc-js/src/channel.ts b/packages/grpc-js/src/channel.ts index f998ecef4..e442e6e64 100644 --- a/packages/grpc-js/src/channel.ts +++ b/packages/grpc-js/src/channel.ts @@ -172,6 +172,7 @@ export class ChannelImplementation implements Channel { private configSelector: ConfigSelector | null = null; // Channelz info + private readonly channelzEnabled: boolean = true; private originalTarget: string; private channelzRef: ChannelRef; private channelzTrace: ChannelzTrace; @@ -213,9 +214,22 @@ export class ChannelImplementation implements Channel { this.callRefTimer = setInterval(() => {}, MAX_TIMEOUT_TIME); this.callRefTimer.unref?.(); - this.channelzRef = registerChannelzChannel(target, () => this.getChannelzInfo()); + if (this.options['grpc.enable_channelz'] === 0) { + this.channelzEnabled = false; + } + this.channelzTrace = new ChannelzTrace(); - this.channelzTrace.addTrace('CT_INFO', 'Channel created'); + if (this.channelzEnabled) { + this.channelzRef = registerChannelzChannel(target, () => this.getChannelzInfo()); + this.channelzTrace.addTrace('CT_INFO', 'Channel created'); + } else { + // Dummy channelz ref that will never be used + this.channelzRef = { + kind: 'channel', + id: -1, + name: '' + }; + } if (this.options['grpc.default_authority']) { this.defaultAuthority = this.options['grpc.default_authority'] as string; @@ -242,7 +256,9 @@ export class ChannelImplementation implements Channel { Object.assign({}, this.options, subchannelArgs), this.credentials ); - this.channelzTrace.addTrace('CT_INFO', 'Created subchannel or used existing subchannel', subchannel.getChannelzRef()); + if (this.channelzEnabled) { + this.channelzTrace.addTrace('CT_INFO', 'Created subchannel or used existing subchannel', subchannel.getChannelzRef()); + } return subchannel; }, updateState: (connectivityState: ConnectivityState, picker: Picker) => { @@ -262,10 +278,14 @@ export class ChannelImplementation implements Channel { ); }, addChannelzChild: (child: ChannelRef | SubchannelRef) => { - this.childrenTracker.refChild(child); + if (this.channelzEnabled) { + this.childrenTracker.refChild(child); + } }, removeChannelzChild: (child: ChannelRef | SubchannelRef) => { - this.childrenTracker.unrefChild(child); + if (this.channelzEnabled) { + this.childrenTracker.unrefChild(child); + } } }; this.resolvingLoadBalancer = new ResolvingLoadBalancer( @@ -273,7 +293,9 @@ export class ChannelImplementation implements Channel { channelControlHelper, options, (configSelector) => { - this.channelzTrace.addTrace('CT_INFO', 'Address resolution succeeded'); + if (this.channelzEnabled) { + this.channelzTrace.addTrace('CT_INFO', 'Address resolution succeeded'); + } this.configSelector = configSelector; /* We process the queue asynchronously to ensure that the corresponding * load balancer update has completed. */ @@ -288,7 +310,9 @@ export class ChannelImplementation implements Channel { }); }, (status) => { - this.channelzTrace.addTrace('CT_WARNING', 'Address resolution failed with code ' + status.code + ' and details "' + status.details + '"'); + if (this.channelzEnabled) { + this.channelzTrace.addTrace('CT_WARNING', 'Address resolution failed with code ' + status.code + ' and details "' + status.details + '"'); + } if (this.configSelectionQueue.length > 0) { this.trace('Name resolution failed with calls queued for config selection'); } @@ -553,7 +577,9 @@ export class ChannelImplementation implements Channel { ' -> ' + ConnectivityState[newState] ); - this.channelzTrace.addTrace('CT_INFO', ConnectivityState[this.connectivityState] + ' -> ' + ConnectivityState[newState]); + if (this.channelzEnabled) { + this.channelzTrace.addTrace('CT_INFO', ConnectivityState[this.connectivityState] + ' -> ' + ConnectivityState[newState]); + } this.connectivityState = newState; const watchersCopy = this.connectivityStateWatchers.slice(); for (const watcherObject of watchersCopy) { @@ -638,7 +664,9 @@ export class ChannelImplementation implements Channel { this.resolvingLoadBalancer.destroy(); this.updateState(ConnectivityState.SHUTDOWN); clearInterval(this.callRefTimer); - unregisterChannelzRef(this.channelzRef); + if (this.channelzEnabled) { + unregisterChannelzRef(this.channelzRef); + } this.subchannelPool.unrefUnusedSubchannels(); } @@ -690,6 +718,11 @@ export class ChannelImplementation implements Channel { this.connectivityStateWatchers.push(watcherObject); } + /** + * Get the channelz reference object for this channel. The returned value is + * garbage if channelz is disabled for this channel. + * @returns + */ getChannelzRef() { return this.channelzRef; } @@ -735,14 +768,16 @@ export class ChannelImplementation implements Channel { this.credentials._getCallCredentials(), callNumber ); - this.callTracker.addCallStarted(); - stream.addStatusWatcher(status => { - if (status.code === Status.OK) { - this.callTracker.addCallSucceeded(); - } else { - this.callTracker.addCallFailed(); - } - }); + if (this.channelzEnabled) { + this.callTracker.addCallStarted(); + stream.addStatusWatcher(status => { + if (status.code === Status.OK) { + this.callTracker.addCallSucceeded(); + } else { + this.callTracker.addCallFailed(); + } + }); + } return stream; } } diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index aed04adec..8bfc2a5bc 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -149,6 +149,7 @@ export class Server { private options: ChannelOptions; // Channelz Info + private readonly channelzEnabled: boolean = true; private channelzRef: ServerRef; private channelzTrace = new ChannelzTrace(); private callTracker = new ChannelzCallTracker(); @@ -157,9 +158,20 @@ export class Server { constructor(options?: ChannelOptions) { this.options = options ?? {}; - this.channelzRef = registerChannelzServer(() => this.getChannelzInfo()); - this.channelzTrace.addTrace('CT_INFO', 'Server created'); - this.trace('Server constructed'); + if (this.options['grpc.enable_channelz'] === 0) { + this.channelzEnabled = false; + } + if (this.channelzEnabled) { + this.channelzRef = registerChannelzServer(() => this.getChannelzInfo()); + this.channelzTrace.addTrace('CT_INFO', 'Server created'); + this.trace('Server constructed'); + } else { + // Dummy channelz ref that will never be used + this.channelzRef = { + kind: 'server', + id: -1 + }; + } } private getChannelzInfo(): ServerInfo { @@ -638,7 +650,9 @@ export class Server { if (this.started === true) { throw new Error('server is already started'); } - this.channelzTrace.addTrace('CT_INFO', 'Starting'); + if (this.channelzEnabled) { + this.channelzTrace.addTrace('CT_INFO', 'Starting'); + } this.started = true; } @@ -686,6 +700,11 @@ export class Server { throw new Error('Not yet implemented'); } + /** + * Get the channelz reference object for this server. The returned value is + * garbage if channelz is disabled for this server. + * @returns + */ getChannelzRef() { return this.channelzRef; } @@ -841,12 +860,16 @@ export class Server { this.sessions.set(session, channelzSessionInfo); const clientAddress = session.socket.remoteAddress; - this.channelzTrace.addTrace('CT_INFO', 'Connection established by client ' + clientAddress); - this.sessionChildrenTracker.refChild(channelzRef); + if (this.channelzEnabled) { + this.channelzTrace.addTrace('CT_INFO', 'Connection established by client ' + clientAddress); + this.sessionChildrenTracker.refChild(channelzRef); + } session.on('close', () => { - this.channelzTrace.addTrace('CT_INFO', 'Connection dropped by client ' + clientAddress); - this.sessionChildrenTracker.unrefChild(channelzRef); - unregisterChannelzRef(channelzRef); + if (this.channelzEnabled) { + this.channelzTrace.addTrace('CT_INFO', 'Connection dropped by client ' + clientAddress); + this.sessionChildrenTracker.unrefChild(channelzRef); + unregisterChannelzRef(channelzRef); + } this.sessions.delete(session); }); }); diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index a3b86b283..edc843a8a 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -157,6 +157,7 @@ export class Subchannel { private subchannelAddressString: string; // Channelz info + private readonly channelzEnabled: boolean = true; private channelzRef: SubchannelRef; private channelzTrace: ChannelzTrace; private callTracker = new ChannelzCallTracker(); @@ -226,9 +227,21 @@ export class Subchannel { }, backoffOptions); this.subchannelAddressString = subchannelAddressToString(subchannelAddress); - this.channelzRef = registerChannelzSubchannel(this.subchannelAddressString, () => this.getChannelzInfo()); + if (options['grpc.enable_channelz'] === 0) { + this.channelzEnabled = false; + } this.channelzTrace = new ChannelzTrace(); - this.channelzTrace.addTrace('CT_INFO', 'Subchannel created'); + if (this.channelzEnabled) { + this.channelzRef = registerChannelzSubchannel(this.subchannelAddressString, () => this.getChannelzInfo()); + this.channelzTrace.addTrace('CT_INFO', 'Subchannel created'); + } else { + // Dummy channelz ref that will never be used + this.channelzRef = { + kind: 'subchannel', + id: -1, + name: '' + }; + } this.trace('Subchannel constructed with options ' + JSON.stringify(options, undefined, 2)); } @@ -286,6 +299,9 @@ export class Subchannel { } private resetChannelzSocketInfo() { + if (!this.channelzEnabled) { + return; + } if (this.channelzSocketRef) { unregisterChannelzRef(this.channelzSocketRef); this.childrenTracker.unrefChild(this.channelzSocketRef); @@ -335,7 +351,9 @@ export class Subchannel { } private sendPing() { - this.keepalivesSent += 1; + if (this.channelzEnabled) { + this.keepalivesSent += 1; + } logging.trace( LogVerbosity.DEBUG, 'keepalive', @@ -462,8 +480,10 @@ export class Subchannel { connectionOptions ); this.session = session; - this.channelzSocketRef = registerChannelzSocket(this.subchannelAddressString, () => this.getChannelzSocketInfo()!); - this.childrenTracker.refChild(this.channelzSocketRef); + if (this.channelzEnabled) { + this.channelzSocketRef = registerChannelzSocket(this.subchannelAddressString, () => this.getChannelzSocketInfo()!); + this.childrenTracker.refChild(this.channelzSocketRef); + } session.unref(); /* For all of these events, check if the session at the time of the event * is the same one currently attached to this subchannel, to ensure that @@ -615,7 +635,9 @@ export class Subchannel { ' -> ' + ConnectivityState[newState] ); - this.channelzTrace.addTrace('CT_INFO', ConnectivityState[this.connectivityState] + ' -> ' + ConnectivityState[newState]); + if (this.channelzEnabled) { + this.channelzTrace.addTrace('CT_INFO', ConnectivityState[this.connectivityState] + ' -> ' + ConnectivityState[newState]); + } const previousState = this.connectivityState; this.connectivityState = newState; switch (newState) { @@ -678,12 +700,16 @@ export class Subchannel { /* If no calls, channels, or subchannel pools have any more references to * this subchannel, we can be sure it will never be used again. */ if (this.callRefcount === 0 && this.refcount === 0) { - this.channelzTrace.addTrace('CT_INFO', 'Shutting down'); + if (this.channelzEnabled) { + this.channelzTrace.addTrace('CT_INFO', 'Shutting down'); + } this.transitionToState( [ConnectivityState.CONNECTING, ConnectivityState.READY], ConnectivityState.TRANSIENT_FAILURE ); - unregisterChannelzRef(this.channelzRef); + if (this.channelzEnabled) { + unregisterChannelzRef(this.channelzRef); + } } } @@ -805,34 +831,36 @@ export class Subchannel { ' with headers\n' + headersString ); - this.callTracker.addCallStarted(); - callStream.addStatusWatcher(status => { - if (status.code === Status.OK) { - this.callTracker.addCallSucceeded(); - } else { - this.callTracker.addCallFailed(); - } - }); const streamSession = this.session; - this.streamTracker.addCallStarted(); - callStream.addStreamEndWatcher(success => { - if (streamSession === this.session) { - if (success) { - this.streamTracker.addCallSucceeded(); + if (this.channelzEnabled) { + this.callTracker.addCallStarted(); + callStream.addStatusWatcher(status => { + if (status.code === Status.OK) { + this.callTracker.addCallSucceeded(); } else { - this.streamTracker.addCallFailed(); + this.callTracker.addCallFailed(); } - } - }); - callStream.attachHttp2Stream(http2Stream, this, extraFilters, { - addMessageSent: () => { - this.messagesSent += 1; - this.lastMessageSentTimestamp = new Date(); - }, - addMessageReceived: () => { - this.messagesReceived += 1; - } - }); + }); + this.streamTracker.addCallStarted(); + callStream.addStreamEndWatcher(success => { + if (streamSession === this.session) { + if (success) { + this.streamTracker.addCallSucceeded(); + } else { + this.streamTracker.addCallFailed(); + } + } + }); + callStream.attachHttp2Stream(http2Stream, this, extraFilters, { + addMessageSent: () => { + this.messagesSent += 1; + this.lastMessageSentTimestamp = new Date(); + }, + addMessageReceived: () => { + this.messagesReceived += 1; + } + }); + } } /**