UNPKG

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