UNPKG

27.7 kBPlain TextView Raw
1/*
2 * Copyright 2021 gRPC authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 */
17
18import { isIPv4, isIPv6 } from 'net';
19import { OrderedMap, type OrderedMapIterator } from '@js-sdsl/ordered-map';
20import { ConnectivityState } from './connectivity-state';
21import { Status } from './constants';
22import { Timestamp } from './generated/google/protobuf/Timestamp';
23import { Channel as ChannelMessage } from './generated/grpc/channelz/v1/Channel';
24import { ChannelConnectivityState__Output } from './generated/grpc/channelz/v1/ChannelConnectivityState';
25import { ChannelRef as ChannelRefMessage } from './generated/grpc/channelz/v1/ChannelRef';
26import { ChannelTrace } from './generated/grpc/channelz/v1/ChannelTrace';
27import { GetChannelRequest__Output } from './generated/grpc/channelz/v1/GetChannelRequest';
28import { GetChannelResponse } from './generated/grpc/channelz/v1/GetChannelResponse';
29import { sendUnaryData, ServerUnaryCall } from './server-call';
30import { ServerRef as ServerRefMessage } from './generated/grpc/channelz/v1/ServerRef';
31import { SocketRef as SocketRefMessage } from './generated/grpc/channelz/v1/SocketRef';
32import {
33 isTcpSubchannelAddress,
34 SubchannelAddress,
35} from './subchannel-address';
36import { SubchannelRef as SubchannelRefMessage } from './generated/grpc/channelz/v1/SubchannelRef';
37import { GetServerRequest__Output } from './generated/grpc/channelz/v1/GetServerRequest';
38import { GetServerResponse } from './generated/grpc/channelz/v1/GetServerResponse';
39import { Server as ServerMessage } from './generated/grpc/channelz/v1/Server';
40import { GetServersRequest__Output } from './generated/grpc/channelz/v1/GetServersRequest';
41import { GetServersResponse } from './generated/grpc/channelz/v1/GetServersResponse';
42import { GetTopChannelsRequest__Output } from './generated/grpc/channelz/v1/GetTopChannelsRequest';
43import { GetTopChannelsResponse } from './generated/grpc/channelz/v1/GetTopChannelsResponse';
44import { GetSubchannelRequest__Output } from './generated/grpc/channelz/v1/GetSubchannelRequest';
45import { GetSubchannelResponse } from './generated/grpc/channelz/v1/GetSubchannelResponse';
46import { Subchannel as SubchannelMessage } from './generated/grpc/channelz/v1/Subchannel';
47import { GetSocketRequest__Output } from './generated/grpc/channelz/v1/GetSocketRequest';
48import { GetSocketResponse } from './generated/grpc/channelz/v1/GetSocketResponse';
49import { Socket as SocketMessage } from './generated/grpc/channelz/v1/Socket';
50import { Address } from './generated/grpc/channelz/v1/Address';
51import { Security } from './generated/grpc/channelz/v1/Security';
52import { GetServerSocketsRequest__Output } from './generated/grpc/channelz/v1/GetServerSocketsRequest';
53import { GetServerSocketsResponse } from './generated/grpc/channelz/v1/GetServerSocketsResponse';
54import {
55 ChannelzDefinition,
56 ChannelzHandlers,
57} from './generated/grpc/channelz/v1/Channelz';
58import { ProtoGrpcType as ChannelzProtoGrpcType } from './generated/channelz';
59import type { loadSync } from '@grpc/proto-loader';
60import { registerAdminService } from './admin';
61import { loadPackageDefinition } from './make-client';
62
63export type TraceSeverity =
64 | 'CT_UNKNOWN'
65 | 'CT_INFO'
66 | 'CT_WARNING'
67 | 'CT_ERROR';
68
69interface Ref {
70 kind: EntityTypes;
71 id: number;
72 name: string;
73}
74
75export interface ChannelRef extends Ref {
76 kind: EntityTypes.channel;
77}
78
79export interface SubchannelRef extends Ref {
80 kind: EntityTypes.subchannel;
81}
82
83export interface ServerRef extends Ref {
84 kind: EntityTypes.server;
85}
86
87export interface SocketRef extends Ref {
88 kind: EntityTypes.socket;
89}
90
91function channelRefToMessage(ref: ChannelRef): ChannelRefMessage {
92 return {
93 channel_id: ref.id,
94 name: ref.name,
95 };
96}
97
98function subchannelRefToMessage(ref: SubchannelRef): SubchannelRefMessage {
99 return {
100 subchannel_id: ref.id,
101 name: ref.name,
102 };
103}
104
105function serverRefToMessage(ref: ServerRef): ServerRefMessage {
106 return {
107 server_id: ref.id,
108 };
109}
110
111function socketRefToMessage(ref: SocketRef): SocketRefMessage {
112 return {
113 socket_id: ref.id,
114 name: ref.name,
115 };
116}
117
118interface TraceEvent {
119 description: string;
120 severity: TraceSeverity;
121 timestamp: Date;
122 childChannel?: ChannelRef;
123 childSubchannel?: SubchannelRef;
124}
125
126/**
127 * The loose upper bound on the number of events that should be retained in a
128 * trace. This may be exceeded by up to a factor of 2. Arbitrarily chosen as a
129 * number that should be large enough to contain the recent relevant
130 * information, but small enough to not use excessive memory.
131 */
132const TARGET_RETAINED_TRACES = 32;
133
134/**
135 * Default number of sockets/servers/channels/subchannels to return
136 */
137const DEFAULT_MAX_RESULTS = 100;
138
139export class ChannelzTraceStub {
140 readonly events: TraceEvent[] = [];
141 readonly creationTimestamp: Date = new Date();
142 readonly eventsLogged = 0;
143
144 addTrace(): void {}
145 getTraceMessage(): ChannelTrace {
146 return {
147 creation_timestamp: dateToProtoTimestamp(this.creationTimestamp),
148 num_events_logged: this.eventsLogged,
149 events: [],
150 };
151 }
152}
153
154export class ChannelzTrace {
155 events: TraceEvent[] = [];
156 creationTimestamp: Date;
157 eventsLogged = 0;
158
159 constructor() {
160 this.creationTimestamp = new Date();
161 }
162
163 addTrace(
164 severity: TraceSeverity,
165 description: string,
166 child?: ChannelRef | SubchannelRef
167 ) {
168 const timestamp = new Date();
169 this.events.push({
170 description: description,
171 severity: severity,
172 timestamp: timestamp,
173 childChannel: child?.kind === 'channel' ? child : undefined,
174 childSubchannel: child?.kind === 'subchannel' ? child : undefined,
175 });
176 // Whenever the trace array gets too large, discard the first half
177 if (this.events.length >= TARGET_RETAINED_TRACES * 2) {
178 this.events = this.events.slice(TARGET_RETAINED_TRACES);
179 }
180 this.eventsLogged += 1;
181 }
182
183 getTraceMessage(): ChannelTrace {
184 return {
185 creation_timestamp: dateToProtoTimestamp(this.creationTimestamp),
186 num_events_logged: this.eventsLogged,
187 events: this.events.map(event => {
188 return {
189 description: event.description,
190 severity: event.severity,
191 timestamp: dateToProtoTimestamp(event.timestamp),
192 channel_ref: event.childChannel
193 ? channelRefToMessage(event.childChannel)
194 : null,
195 subchannel_ref: event.childSubchannel
196 ? subchannelRefToMessage(event.childSubchannel)
197 : null,
198 };
199 }),
200 };
201 }
202}
203
204type RefOrderedMap = OrderedMap<
205 number,
206 { ref: { id: number; kind: EntityTypes; name: string }; count: number }
207>;
208
209export class ChannelzChildrenTracker {
210 private channelChildren: RefOrderedMap = new OrderedMap();
211 private subchannelChildren: RefOrderedMap = new OrderedMap();
212 private socketChildren: RefOrderedMap = new OrderedMap();
213 private trackerMap = {
214 [EntityTypes.channel]: this.channelChildren,
215 [EntityTypes.subchannel]: this.subchannelChildren,
216 [EntityTypes.socket]: this.socketChildren,
217 } as const;
218
219 refChild(child: ChannelRef | SubchannelRef | SocketRef) {
220 const tracker = this.trackerMap[child.kind];
221 const trackedChild = tracker.find(child.id);
222
223 if (trackedChild.equals(tracker.end())) {
224 tracker.setElement(
225 child.id,
226 {
227 ref: child,
228 count: 1,
229 },
230 trackedChild
231 );
232 } else {
233 trackedChild.pointer[1].count += 1;
234 }
235 }
236
237 unrefChild(child: ChannelRef | SubchannelRef | SocketRef) {
238 const tracker = this.trackerMap[child.kind];
239 const trackedChild = tracker.getElementByKey(child.id);
240 if (trackedChild !== undefined) {
241 trackedChild.count -= 1;
242 if (trackedChild.count === 0) {
243 tracker.eraseElementByKey(child.id);
244 }
245 }
246 }
247
248 getChildLists(): ChannelzChildren {
249 return {
250 channels: this.channelChildren as ChannelzChildren['channels'],
251 subchannels: this.subchannelChildren as ChannelzChildren['subchannels'],
252 sockets: this.socketChildren as ChannelzChildren['sockets'],
253 };
254 }
255}
256
257export class ChannelzChildrenTrackerStub extends ChannelzChildrenTracker {
258 override refChild(): void {}
259 override unrefChild(): void {}
260}
261
262export class ChannelzCallTracker {
263 callsStarted = 0;
264 callsSucceeded = 0;
265 callsFailed = 0;
266 lastCallStartedTimestamp: Date | null = null;
267
268 addCallStarted() {
269 this.callsStarted += 1;
270 this.lastCallStartedTimestamp = new Date();
271 }
272 addCallSucceeded() {
273 this.callsSucceeded += 1;
274 }
275 addCallFailed() {
276 this.callsFailed += 1;
277 }
278}
279
280export class ChannelzCallTrackerStub extends ChannelzCallTracker {
281 override addCallStarted() {}
282 override addCallSucceeded() {}
283 override addCallFailed() {}
284}
285
286export interface ChannelzChildren {
287 channels: OrderedMap<number, { ref: ChannelRef; count: number }>;
288 subchannels: OrderedMap<number, { ref: SubchannelRef; count: number }>;
289 sockets: OrderedMap<number, { ref: SocketRef; count: number }>;
290}
291
292export interface ChannelInfo {
293 target: string;
294 state: ConnectivityState;
295 trace: ChannelzTrace | ChannelzTraceStub;
296 callTracker: ChannelzCallTracker | ChannelzCallTrackerStub;
297 children: ChannelzChildren;
298}
299
300export type SubchannelInfo = ChannelInfo;
301
302export interface ServerInfo {
303 trace: ChannelzTrace;
304 callTracker: ChannelzCallTracker;
305 listenerChildren: ChannelzChildren;
306 sessionChildren: ChannelzChildren;
307}
308
309export interface TlsInfo {
310 cipherSuiteStandardName: string | null;
311 cipherSuiteOtherName: string | null;
312 localCertificate: Buffer | null;
313 remoteCertificate: Buffer | null;
314}
315
316export interface SocketInfo {
317 localAddress: SubchannelAddress | null;
318 remoteAddress: SubchannelAddress | null;
319 security: TlsInfo | null;
320 remoteName: string | null;
321 streamsStarted: number;
322 streamsSucceeded: number;
323 streamsFailed: number;
324 messagesSent: number;
325 messagesReceived: number;
326 keepAlivesSent: number;
327 lastLocalStreamCreatedTimestamp: Date | null;
328 lastRemoteStreamCreatedTimestamp: Date | null;
329 lastMessageSentTimestamp: Date | null;
330 lastMessageReceivedTimestamp: Date | null;
331 localFlowControlWindow: number | null;
332 remoteFlowControlWindow: number | null;
333}
334
335interface ChannelEntry {
336 ref: ChannelRef;
337 getInfo(): ChannelInfo;
338}
339
340interface SubchannelEntry {
341 ref: SubchannelRef;
342 getInfo(): SubchannelInfo;
343}
344
345interface ServerEntry {
346 ref: ServerRef;
347 getInfo(): ServerInfo;
348}
349
350interface SocketEntry {
351 ref: SocketRef;
352 getInfo(): SocketInfo;
353}
354
355export const enum EntityTypes {
356 channel = 'channel',
357 subchannel = 'subchannel',
358 server = 'server',
359 socket = 'socket',
360}
361
362type EntryOrderedMap = OrderedMap<number, { ref: Ref; getInfo: () => any }>;
363
364const entityMaps = {
365 [EntityTypes.channel]: new OrderedMap<number, ChannelEntry>(),
366 [EntityTypes.subchannel]: new OrderedMap<number, SubchannelEntry>(),
367 [EntityTypes.server]: new OrderedMap<number, ServerEntry>(),
368 [EntityTypes.socket]: new OrderedMap<number, SocketEntry>(),
369} as const;
370
371export type RefByType<T extends EntityTypes> = T extends EntityTypes.channel
372 ? ChannelRef
373 : T extends EntityTypes.server
374 ? ServerRef
375 : T extends EntityTypes.socket
376 ? SocketRef
377 : T extends EntityTypes.subchannel
378 ? SubchannelRef
379 : never;
380
381export type EntryByType<T extends EntityTypes> = T extends EntityTypes.channel
382 ? ChannelEntry
383 : T extends EntityTypes.server
384 ? ServerEntry
385 : T extends EntityTypes.socket
386 ? SocketEntry
387 : T extends EntityTypes.subchannel
388 ? SubchannelEntry
389 : never;
390
391export type InfoByType<T extends EntityTypes> = T extends EntityTypes.channel
392 ? ChannelInfo
393 : T extends EntityTypes.subchannel
394 ? SubchannelInfo
395 : T extends EntityTypes.server
396 ? ServerInfo
397 : T extends EntityTypes.socket
398 ? SocketInfo
399 : never;
400
401const generateRegisterFn = <R extends EntityTypes>(kind: R) => {
402 let nextId = 1;
403 function getNextId(): number {
404 return nextId++;
405 }
406
407 const entityMap: EntryOrderedMap = entityMaps[kind];
408
409 return (
410 name: string,
411 getInfo: () => InfoByType<R>,
412 channelzEnabled: boolean
413 ): RefByType<R> => {
414 const id = getNextId();
415 const ref = { id, name, kind } as RefByType<R>;
416 if (channelzEnabled) {
417 entityMap.setElement(id, { ref, getInfo });
418 }
419 return ref;
420 };
421};
422
423export const registerChannelzChannel = generateRegisterFn(EntityTypes.channel);
424export const registerChannelzSubchannel = generateRegisterFn(
425 EntityTypes.subchannel
426);
427export const registerChannelzServer = generateRegisterFn(EntityTypes.server);
428export const registerChannelzSocket = generateRegisterFn(EntityTypes.socket);
429
430export function unregisterChannelzRef(
431 ref: ChannelRef | SubchannelRef | ServerRef | SocketRef
432) {
433 entityMaps[ref.kind].eraseElementByKey(ref.id);
434}
435
436/**
437 * Parse a single section of an IPv6 address as two bytes
438 * @param addressSection A hexadecimal string of length up to 4
439 * @returns The pair of bytes representing this address section
440 */
441function parseIPv6Section(addressSection: string): [number, number] {
442 const numberValue = Number.parseInt(addressSection, 16);
443 return [(numberValue / 256) | 0, numberValue % 256];
444}
445
446/**
447 * Parse a chunk of an IPv6 address string to some number of bytes
448 * @param addressChunk Some number of segments of up to 4 hexadecimal
449 * characters each, joined by colons.
450 * @returns The list of bytes representing this address chunk
451 */
452function parseIPv6Chunk(addressChunk: string): number[] {
453 if (addressChunk === '') {
454 return [];
455 }
456 const bytePairs = addressChunk
457 .split(':')
458 .map(section => parseIPv6Section(section));
459 const result: number[] = [];
460 return result.concat(...bytePairs);
461}
462
463/**
464 * Converts an IPv4 or IPv6 address from string representation to binary
465 * representation
466 * @param ipAddress an IP address in standard IPv4 or IPv6 text format
467 * @returns
468 */
469function ipAddressStringToBuffer(ipAddress: string): Buffer | null {
470 if (isIPv4(ipAddress)) {
471 return Buffer.from(
472 Uint8Array.from(
473 ipAddress.split('.').map(segment => Number.parseInt(segment))
474 )
475 );
476 } else if (isIPv6(ipAddress)) {
477 let leftSection: string;
478 let rightSection: string;
479 const doubleColonIndex = ipAddress.indexOf('::');
480 if (doubleColonIndex === -1) {
481 leftSection = ipAddress;
482 rightSection = '';
483 } else {
484 leftSection = ipAddress.substring(0, doubleColonIndex);
485 rightSection = ipAddress.substring(doubleColonIndex + 2);
486 }
487 const leftBuffer = Buffer.from(parseIPv6Chunk(leftSection));
488 const rightBuffer = Buffer.from(parseIPv6Chunk(rightSection));
489 const middleBuffer = Buffer.alloc(
490 16 - leftBuffer.length - rightBuffer.length,
491 0
492 );
493 return Buffer.concat([leftBuffer, middleBuffer, rightBuffer]);
494 } else {
495 return null;
496 }
497}
498
499function connectivityStateToMessage(
500 state: ConnectivityState
501): ChannelConnectivityState__Output {
502 switch (state) {
503 case ConnectivityState.CONNECTING:
504 return {
505 state: 'CONNECTING',
506 };
507 case ConnectivityState.IDLE:
508 return {
509 state: 'IDLE',
510 };
511 case ConnectivityState.READY:
512 return {
513 state: 'READY',
514 };
515 case ConnectivityState.SHUTDOWN:
516 return {
517 state: 'SHUTDOWN',
518 };
519 case ConnectivityState.TRANSIENT_FAILURE:
520 return {
521 state: 'TRANSIENT_FAILURE',
522 };
523 default:
524 return {
525 state: 'UNKNOWN',
526 };
527 }
528}
529
530function dateToProtoTimestamp(date?: Date | null): Timestamp | null {
531 if (!date) {
532 return null;
533 }
534 const millisSinceEpoch = date.getTime();
535 return {
536 seconds: (millisSinceEpoch / 1000) | 0,
537 nanos: (millisSinceEpoch % 1000) * 1_000_000,
538 };
539}
540
541function getChannelMessage(channelEntry: ChannelEntry): ChannelMessage {
542 const resolvedInfo = channelEntry.getInfo();
543 const channelRef: ChannelRefMessage[] = [];
544 const subchannelRef: SubchannelRefMessage[] = [];
545
546 resolvedInfo.children.channels.forEach(el => {
547 channelRef.push(channelRefToMessage(el[1].ref));
548 });
549
550 resolvedInfo.children.subchannels.forEach(el => {
551 subchannelRef.push(subchannelRefToMessage(el[1].ref));
552 });
553
554 return {
555 ref: channelRefToMessage(channelEntry.ref),
556 data: {
557 target: resolvedInfo.target,
558 state: connectivityStateToMessage(resolvedInfo.state),
559 calls_started: resolvedInfo.callTracker.callsStarted,
560 calls_succeeded: resolvedInfo.callTracker.callsSucceeded,
561 calls_failed: resolvedInfo.callTracker.callsFailed,
562 last_call_started_timestamp: dateToProtoTimestamp(
563 resolvedInfo.callTracker.lastCallStartedTimestamp
564 ),
565 trace: resolvedInfo.trace.getTraceMessage(),
566 },
567 channel_ref: channelRef,
568 subchannel_ref: subchannelRef,
569 };
570}
571
572function GetChannel(
573 call: ServerUnaryCall<GetChannelRequest__Output, GetChannelResponse>,
574 callback: sendUnaryData<GetChannelResponse>
575): void {
576 const channelId = parseInt(call.request.channel_id, 10);
577 const channelEntry =
578 entityMaps[EntityTypes.channel].getElementByKey(channelId);
579 if (channelEntry === undefined) {
580 callback({
581 code: Status.NOT_FOUND,
582 details: 'No channel data found for id ' + channelId,
583 });
584 return;
585 }
586 callback(null, { channel: getChannelMessage(channelEntry) });
587}
588
589function GetTopChannels(
590 call: ServerUnaryCall<GetTopChannelsRequest__Output, GetTopChannelsResponse>,
591 callback: sendUnaryData<GetTopChannelsResponse>
592): void {
593 const maxResults =
594 parseInt(call.request.max_results, 10) || DEFAULT_MAX_RESULTS;
595 const resultList: ChannelMessage[] = [];
596 const startId = parseInt(call.request.start_channel_id, 10);
597 const channelEntries = entityMaps[EntityTypes.channel];
598
599 let i: OrderedMapIterator<number, ChannelEntry>;
600 for (
601 i = channelEntries.lowerBound(startId);
602 !i.equals(channelEntries.end()) && resultList.length < maxResults;
603 i = i.next()
604 ) {
605 resultList.push(getChannelMessage(i.pointer[1]));
606 }
607
608 callback(null, {
609 channel: resultList,
610 end: i.equals(channelEntries.end()),
611 });
612}
613
614function getServerMessage(serverEntry: ServerEntry): ServerMessage {
615 const resolvedInfo = serverEntry.getInfo();
616 const listenSocket: SocketRefMessage[] = [];
617
618 resolvedInfo.listenerChildren.sockets.forEach(el => {
619 listenSocket.push(socketRefToMessage(el[1].ref));
620 });
621
622 return {
623 ref: serverRefToMessage(serverEntry.ref),
624 data: {
625 calls_started: resolvedInfo.callTracker.callsStarted,
626 calls_succeeded: resolvedInfo.callTracker.callsSucceeded,
627 calls_failed: resolvedInfo.callTracker.callsFailed,
628 last_call_started_timestamp: dateToProtoTimestamp(
629 resolvedInfo.callTracker.lastCallStartedTimestamp
630 ),
631 trace: resolvedInfo.trace.getTraceMessage(),
632 },
633 listen_socket: listenSocket,
634 };
635}
636
637function GetServer(
638 call: ServerUnaryCall<GetServerRequest__Output, GetServerResponse>,
639 callback: sendUnaryData<GetServerResponse>
640): void {
641 const serverId = parseInt(call.request.server_id, 10);
642 const serverEntries = entityMaps[EntityTypes.server];
643 const serverEntry = serverEntries.getElementByKey(serverId);
644 if (serverEntry === undefined) {
645 callback({
646 code: Status.NOT_FOUND,
647 details: 'No server data found for id ' + serverId,
648 });
649 return;
650 }
651 callback(null, { server: getServerMessage(serverEntry) });
652}
653
654function GetServers(
655 call: ServerUnaryCall<GetServersRequest__Output, GetServersResponse>,
656 callback: sendUnaryData<GetServersResponse>
657): void {
658 const maxResults =
659 parseInt(call.request.max_results, 10) || DEFAULT_MAX_RESULTS;
660 const startId = parseInt(call.request.start_server_id, 10);
661 const serverEntries = entityMaps[EntityTypes.server];
662 const resultList: ServerMessage[] = [];
663
664 let i: OrderedMapIterator<number, ServerEntry>;
665 for (
666 i = serverEntries.lowerBound(startId);
667 !i.equals(serverEntries.end()) && resultList.length < maxResults;
668 i = i.next()
669 ) {
670 resultList.push(getServerMessage(i.pointer[1]));
671 }
672
673 callback(null, {
674 server: resultList,
675 end: i.equals(serverEntries.end()),
676 });
677}
678
679function GetSubchannel(
680 call: ServerUnaryCall<GetSubchannelRequest__Output, GetSubchannelResponse>,
681 callback: sendUnaryData<GetSubchannelResponse>
682): void {
683 const subchannelId = parseInt(call.request.subchannel_id, 10);
684 const subchannelEntry =
685 entityMaps[EntityTypes.subchannel].getElementByKey(subchannelId);
686 if (subchannelEntry === undefined) {
687 callback({
688 code: Status.NOT_FOUND,
689 details: 'No subchannel data found for id ' + subchannelId,
690 });
691 return;
692 }
693 const resolvedInfo = subchannelEntry.getInfo();
694 const listenSocket: SocketRefMessage[] = [];
695
696 resolvedInfo.children.sockets.forEach(el => {
697 listenSocket.push(socketRefToMessage(el[1].ref));
698 });
699
700 const subchannelMessage: SubchannelMessage = {
701 ref: subchannelRefToMessage(subchannelEntry.ref),
702 data: {
703 target: resolvedInfo.target,
704 state: connectivityStateToMessage(resolvedInfo.state),
705 calls_started: resolvedInfo.callTracker.callsStarted,
706 calls_succeeded: resolvedInfo.callTracker.callsSucceeded,
707 calls_failed: resolvedInfo.callTracker.callsFailed,
708 last_call_started_timestamp: dateToProtoTimestamp(
709 resolvedInfo.callTracker.lastCallStartedTimestamp
710 ),
711 trace: resolvedInfo.trace.getTraceMessage(),
712 },
713 socket_ref: listenSocket,
714 };
715 callback(null, { subchannel: subchannelMessage });
716}
717
718function subchannelAddressToAddressMessage(
719 subchannelAddress: SubchannelAddress
720): Address {
721 if (isTcpSubchannelAddress(subchannelAddress)) {
722 return {
723 address: 'tcpip_address',
724 tcpip_address: {
725 ip_address:
726 ipAddressStringToBuffer(subchannelAddress.host) ?? undefined,
727 port: subchannelAddress.port,
728 },
729 };
730 } else {
731 return {
732 address: 'uds_address',
733 uds_address: {
734 filename: subchannelAddress.path,
735 },
736 };
737 }
738}
739
740function GetSocket(
741 call: ServerUnaryCall<GetSocketRequest__Output, GetSocketResponse>,
742 callback: sendUnaryData<GetSocketResponse>
743): void {
744 const socketId = parseInt(call.request.socket_id, 10);
745 const socketEntry = entityMaps[EntityTypes.socket].getElementByKey(socketId);
746 if (socketEntry === undefined) {
747 callback({
748 code: Status.NOT_FOUND,
749 details: 'No socket data found for id ' + socketId,
750 });
751 return;
752 }
753 const resolvedInfo = socketEntry.getInfo();
754 const securityMessage: Security | null = resolvedInfo.security
755 ? {
756 model: 'tls',
757 tls: {
758 cipher_suite: resolvedInfo.security.cipherSuiteStandardName
759 ? 'standard_name'
760 : 'other_name',
761 standard_name:
762 resolvedInfo.security.cipherSuiteStandardName ?? undefined,
763 other_name: resolvedInfo.security.cipherSuiteOtherName ?? undefined,
764 local_certificate:
765 resolvedInfo.security.localCertificate ?? undefined,
766 remote_certificate:
767 resolvedInfo.security.remoteCertificate ?? undefined,
768 },
769 }
770 : null;
771 const socketMessage: SocketMessage = {
772 ref: socketRefToMessage(socketEntry.ref),
773 local: resolvedInfo.localAddress
774 ? subchannelAddressToAddressMessage(resolvedInfo.localAddress)
775 : null,
776 remote: resolvedInfo.remoteAddress
777 ? subchannelAddressToAddressMessage(resolvedInfo.remoteAddress)
778 : null,
779 remote_name: resolvedInfo.remoteName ?? undefined,
780 security: securityMessage,
781 data: {
782 keep_alives_sent: resolvedInfo.keepAlivesSent,
783 streams_started: resolvedInfo.streamsStarted,
784 streams_succeeded: resolvedInfo.streamsSucceeded,
785 streams_failed: resolvedInfo.streamsFailed,
786 last_local_stream_created_timestamp: dateToProtoTimestamp(
787 resolvedInfo.lastLocalStreamCreatedTimestamp
788 ),
789 last_remote_stream_created_timestamp: dateToProtoTimestamp(
790 resolvedInfo.lastRemoteStreamCreatedTimestamp
791 ),
792 messages_received: resolvedInfo.messagesReceived,
793 messages_sent: resolvedInfo.messagesSent,
794 last_message_received_timestamp: dateToProtoTimestamp(
795 resolvedInfo.lastMessageReceivedTimestamp
796 ),
797 last_message_sent_timestamp: dateToProtoTimestamp(
798 resolvedInfo.lastMessageSentTimestamp
799 ),
800 local_flow_control_window: resolvedInfo.localFlowControlWindow
801 ? { value: resolvedInfo.localFlowControlWindow }
802 : null,
803 remote_flow_control_window: resolvedInfo.remoteFlowControlWindow
804 ? { value: resolvedInfo.remoteFlowControlWindow }
805 : null,
806 },
807 };
808 callback(null, { socket: socketMessage });
809}
810
811function GetServerSockets(
812 call: ServerUnaryCall<
813 GetServerSocketsRequest__Output,
814 GetServerSocketsResponse
815 >,
816 callback: sendUnaryData<GetServerSocketsResponse>
817): void {
818 const serverId = parseInt(call.request.server_id, 10);
819 const serverEntry = entityMaps[EntityTypes.server].getElementByKey(serverId);
820
821 if (serverEntry === undefined) {
822 callback({
823 code: Status.NOT_FOUND,
824 details: 'No server data found for id ' + serverId,
825 });
826 return;
827 }
828
829 const startId = parseInt(call.request.start_socket_id, 10);
830 const maxResults =
831 parseInt(call.request.max_results, 10) || DEFAULT_MAX_RESULTS;
832 const resolvedInfo = serverEntry.getInfo();
833 // If we wanted to include listener sockets in the result, this line would
834 // instead say
835 // const allSockets = resolvedInfo.listenerChildren.sockets.concat(resolvedInfo.sessionChildren.sockets).sort((ref1, ref2) => ref1.id - ref2.id);
836 const allSockets = resolvedInfo.sessionChildren.sockets;
837 const resultList: SocketRefMessage[] = [];
838
839 let i: OrderedMapIterator<number, { ref: SocketRef }>;
840 for (
841 i = allSockets.lowerBound(startId);
842 !i.equals(allSockets.end()) && resultList.length < maxResults;
843 i = i.next()
844 ) {
845 resultList.push(socketRefToMessage(i.pointer[1].ref));
846 }
847
848 callback(null, {
849 socket_ref: resultList,
850 end: i.equals(allSockets.end()),
851 });
852}
853
854export function getChannelzHandlers(): ChannelzHandlers {
855 return {
856 GetChannel,
857 GetTopChannels,
858 GetServer,
859 GetServers,
860 GetSubchannel,
861 GetSocket,
862 GetServerSockets,
863 };
864}
865
866let loadedChannelzDefinition: ChannelzDefinition | null = null;
867
868export function getChannelzServiceDefinition(): ChannelzDefinition {
869 if (loadedChannelzDefinition) {
870 return loadedChannelzDefinition;
871 }
872 /* The purpose of this complexity is to avoid loading @grpc/proto-loader at
873 * runtime for users who will not use/enable channelz. */
874 const loaderLoadSync = require('@grpc/proto-loader')
875 .loadSync as typeof loadSync;
876 const loadedProto = loaderLoadSync('channelz.proto', {
877 keepCase: true,
878 longs: String,
879 enums: String,
880 defaults: true,
881 oneofs: true,
882 includeDirs: [`${__dirname}/../../proto`],
883 });
884 const channelzGrpcObject = loadPackageDefinition(
885 loadedProto
886 ) as unknown as ChannelzProtoGrpcType;
887 loadedChannelzDefinition =
888 channelzGrpcObject.grpc.channelz.v1.Channelz.service;
889 return loadedChannelzDefinition;
890}
891
892export function setup() {
893 registerAdminService(getChannelzServiceDefinition, getChannelzHandlers);
894}