UNPKG

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