|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import { isIPv4, isIPv6 } from 'net'; |
|
import { ConnectivityState } from './connectivity-state'; |
|
import { Status } from './constants'; |
|
import { Timestamp } from './generated/google/protobuf/Timestamp'; |
|
import { Channel as ChannelMessage } from './generated/grpc/channelz/v1/Channel'; |
|
import { ChannelConnectivityState__Output } from './generated/grpc/channelz/v1/ChannelConnectivityState'; |
|
import { ChannelRef as ChannelRefMessage } from './generated/grpc/channelz/v1/ChannelRef'; |
|
import { ChannelTrace } from './generated/grpc/channelz/v1/ChannelTrace'; |
|
import { GetChannelRequest__Output } from './generated/grpc/channelz/v1/GetChannelRequest'; |
|
import { GetChannelResponse } from './generated/grpc/channelz/v1/GetChannelResponse'; |
|
import { sendUnaryData, ServerUnaryCall } from './server-call'; |
|
import { ServerRef as ServerRefMessage } from './generated/grpc/channelz/v1/ServerRef'; |
|
import { SocketRef as SocketRefMessage } from './generated/grpc/channelz/v1/SocketRef'; |
|
import { |
|
isTcpSubchannelAddress, |
|
SubchannelAddress, |
|
} from './subchannel-address'; |
|
import { SubchannelRef as SubchannelRefMessage } from './generated/grpc/channelz/v1/SubchannelRef'; |
|
import { GetServerRequest__Output } from './generated/grpc/channelz/v1/GetServerRequest'; |
|
import { GetServerResponse } from './generated/grpc/channelz/v1/GetServerResponse'; |
|
import { Server as ServerMessage } from './generated/grpc/channelz/v1/Server'; |
|
import { GetServersRequest__Output } from './generated/grpc/channelz/v1/GetServersRequest'; |
|
import { GetServersResponse } from './generated/grpc/channelz/v1/GetServersResponse'; |
|
import { GetTopChannelsRequest__Output } from './generated/grpc/channelz/v1/GetTopChannelsRequest'; |
|
import { GetTopChannelsResponse } from './generated/grpc/channelz/v1/GetTopChannelsResponse'; |
|
import { GetSubchannelRequest__Output } from './generated/grpc/channelz/v1/GetSubchannelRequest'; |
|
import { GetSubchannelResponse } from './generated/grpc/channelz/v1/GetSubchannelResponse'; |
|
import { Subchannel as SubchannelMessage } from './generated/grpc/channelz/v1/Subchannel'; |
|
import { GetSocketRequest__Output } from './generated/grpc/channelz/v1/GetSocketRequest'; |
|
import { GetSocketResponse } from './generated/grpc/channelz/v1/GetSocketResponse'; |
|
import { Socket as SocketMessage } from './generated/grpc/channelz/v1/Socket'; |
|
import { Address } from './generated/grpc/channelz/v1/Address'; |
|
import { Security } from './generated/grpc/channelz/v1/Security'; |
|
import { GetServerSocketsRequest__Output } from './generated/grpc/channelz/v1/GetServerSocketsRequest'; |
|
import { GetServerSocketsResponse } from './generated/grpc/channelz/v1/GetServerSocketsResponse'; |
|
import { |
|
ChannelzDefinition, |
|
ChannelzHandlers, |
|
} from './generated/grpc/channelz/v1/Channelz'; |
|
import { ProtoGrpcType as ChannelzProtoGrpcType } from './generated/channelz'; |
|
import type { loadSync } from '@grpc/proto-loader'; |
|
import { registerAdminService } from './admin'; |
|
import { loadPackageDefinition } from './make-client'; |
|
|
|
export type TraceSeverity = |
|
| 'CT_UNKNOWN' |
|
| 'CT_INFO' |
|
| 'CT_WARNING' |
|
| 'CT_ERROR'; |
|
|
|
export interface ChannelRef { |
|
kind: 'channel'; |
|
id: number; |
|
name: string; |
|
} |
|
|
|
export interface SubchannelRef { |
|
kind: 'subchannel'; |
|
id: number; |
|
name: string; |
|
} |
|
|
|
export interface ServerRef { |
|
kind: 'server'; |
|
id: number; |
|
} |
|
|
|
export interface SocketRef { |
|
kind: 'socket'; |
|
id: number; |
|
name: string; |
|
} |
|
|
|
function channelRefToMessage(ref: ChannelRef): ChannelRefMessage { |
|
return { |
|
channel_id: ref.id, |
|
name: ref.name, |
|
}; |
|
} |
|
|
|
function subchannelRefToMessage(ref: SubchannelRef): SubchannelRefMessage { |
|
return { |
|
subchannel_id: ref.id, |
|
name: ref.name, |
|
}; |
|
} |
|
|
|
function serverRefToMessage(ref: ServerRef): ServerRefMessage { |
|
return { |
|
server_id: ref.id, |
|
}; |
|
} |
|
|
|
function socketRefToMessage(ref: SocketRef): SocketRefMessage { |
|
return { |
|
socket_id: ref.id, |
|
name: ref.name, |
|
}; |
|
} |
|
|
|
interface TraceEvent { |
|
description: string; |
|
severity: TraceSeverity; |
|
timestamp: Date; |
|
childChannel?: ChannelRef; |
|
childSubchannel?: SubchannelRef; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const TARGET_RETAINED_TRACES = 32; |
|
|
|
export class ChannelzTrace { |
|
events: TraceEvent[] = []; |
|
creationTimestamp: Date; |
|
eventsLogged = 0; |
|
|
|
constructor() { |
|
this.creationTimestamp = new Date(); |
|
} |
|
|
|
addTrace( |
|
severity: TraceSeverity, |
|
description: string, |
|
child?: ChannelRef | SubchannelRef |
|
) { |
|
const timestamp = new Date(); |
|
this.events.push({ |
|
description: description, |
|
severity: severity, |
|
timestamp: timestamp, |
|
childChannel: child?.kind === 'channel' ? child : undefined, |
|
childSubchannel: child?.kind === 'subchannel' ? child : undefined, |
|
}); |
|
|
|
if (this.events.length >= TARGET_RETAINED_TRACES * 2) { |
|
this.events = this.events.slice(TARGET_RETAINED_TRACES); |
|
} |
|
this.eventsLogged += 1; |
|
} |
|
|
|
getTraceMessage(): ChannelTrace { |
|
return { |
|
creation_timestamp: dateToProtoTimestamp(this.creationTimestamp), |
|
num_events_logged: this.eventsLogged, |
|
events: this.events.map(event => { |
|
return { |
|
description: event.description, |
|
severity: event.severity, |
|
timestamp: dateToProtoTimestamp(event.timestamp), |
|
channel_ref: event.childChannel |
|
? channelRefToMessage(event.childChannel) |
|
: null, |
|
subchannel_ref: event.childSubchannel |
|
? subchannelRefToMessage(event.childSubchannel) |
|
: null, |
|
}; |
|
}), |
|
}; |
|
} |
|
} |
|
|
|
export class ChannelzChildrenTracker { |
|
private channelChildren: Map<number, { ref: ChannelRef; count: number }> = |
|
new Map<number, { ref: ChannelRef; count: number }>(); |
|
private subchannelChildren: Map< |
|
number, |
|
{ ref: SubchannelRef; count: number } |
|
> = new Map<number, { ref: SubchannelRef; count: number }>(); |
|
private socketChildren: Map<number, { ref: SocketRef; count: number }> = |
|
new Map<number, { ref: SocketRef; count: number }>(); |
|
|
|
refChild(child: ChannelRef | SubchannelRef | SocketRef) { |
|
switch (child.kind) { |
|
case 'channel': { |
|
const trackedChild = this.channelChildren.get(child.id) ?? { |
|
ref: child, |
|
count: 0, |
|
}; |
|
trackedChild.count += 1; |
|
this.channelChildren.set(child.id, trackedChild); |
|
break; |
|
} |
|
case 'subchannel': { |
|
const trackedChild = this.subchannelChildren.get(child.id) ?? { |
|
ref: child, |
|
count: 0, |
|
}; |
|
trackedChild.count += 1; |
|
this.subchannelChildren.set(child.id, trackedChild); |
|
break; |
|
} |
|
case 'socket': { |
|
const trackedChild = this.socketChildren.get(child.id) ?? { |
|
ref: child, |
|
count: 0, |
|
}; |
|
trackedChild.count += 1; |
|
this.socketChildren.set(child.id, trackedChild); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
unrefChild(child: ChannelRef | SubchannelRef | SocketRef) { |
|
switch (child.kind) { |
|
case 'channel': { |
|
const trackedChild = this.channelChildren.get(child.id); |
|
if (trackedChild !== undefined) { |
|
trackedChild.count -= 1; |
|
if (trackedChild.count === 0) { |
|
this.channelChildren.delete(child.id); |
|
} else { |
|
this.channelChildren.set(child.id, trackedChild); |
|
} |
|
} |
|
break; |
|
} |
|
case 'subchannel': { |
|
const trackedChild = this.subchannelChildren.get(child.id); |
|
if (trackedChild !== undefined) { |
|
trackedChild.count -= 1; |
|
if (trackedChild.count === 0) { |
|
this.subchannelChildren.delete(child.id); |
|
} else { |
|
this.subchannelChildren.set(child.id, trackedChild); |
|
} |
|
} |
|
break; |
|
} |
|
case 'socket': { |
|
const trackedChild = this.socketChildren.get(child.id); |
|
if (trackedChild !== undefined) { |
|
trackedChild.count -= 1; |
|
if (trackedChild.count === 0) { |
|
this.socketChildren.delete(child.id); |
|
} else { |
|
this.socketChildren.set(child.id, trackedChild); |
|
} |
|
} |
|
break; |
|
} |
|
} |
|
} |
|
|
|
getChildLists(): ChannelzChildren { |
|
const channels: ChannelRef[] = []; |
|
for (const { ref } of this.channelChildren.values()) { |
|
channels.push(ref); |
|
} |
|
const subchannels: SubchannelRef[] = []; |
|
for (const { ref } of this.subchannelChildren.values()) { |
|
subchannels.push(ref); |
|
} |
|
const sockets: SocketRef[] = []; |
|
for (const { ref } of this.socketChildren.values()) { |
|
sockets.push(ref); |
|
} |
|
return { channels, subchannels, sockets }; |
|
} |
|
} |
|
|
|
export class ChannelzCallTracker { |
|
callsStarted = 0; |
|
callsSucceeded = 0; |
|
callsFailed = 0; |
|
lastCallStartedTimestamp: Date | null = null; |
|
|
|
addCallStarted() { |
|
this.callsStarted += 1; |
|
this.lastCallStartedTimestamp = new Date(); |
|
} |
|
addCallSucceeded() { |
|
this.callsSucceeded += 1; |
|
} |
|
addCallFailed() { |
|
this.callsFailed += 1; |
|
} |
|
} |
|
|
|
export interface ChannelzChildren { |
|
channels: ChannelRef[]; |
|
subchannels: SubchannelRef[]; |
|
sockets: SocketRef[]; |
|
} |
|
|
|
export interface ChannelInfo { |
|
target: string; |
|
state: ConnectivityState; |
|
trace: ChannelzTrace; |
|
callTracker: ChannelzCallTracker; |
|
children: ChannelzChildren; |
|
} |
|
|
|
export type SubchannelInfo = ChannelInfo; |
|
|
|
export interface ServerInfo { |
|
trace: ChannelzTrace; |
|
callTracker: ChannelzCallTracker; |
|
listenerChildren: ChannelzChildren; |
|
sessionChildren: ChannelzChildren; |
|
} |
|
|
|
export interface TlsInfo { |
|
cipherSuiteStandardName: string | null; |
|
cipherSuiteOtherName: string | null; |
|
localCertificate: Buffer | null; |
|
remoteCertificate: Buffer | null; |
|
} |
|
|
|
export interface SocketInfo { |
|
localAddress: SubchannelAddress | null; |
|
remoteAddress: SubchannelAddress | null; |
|
security: TlsInfo | null; |
|
remoteName: string | null; |
|
streamsStarted: number; |
|
streamsSucceeded: number; |
|
streamsFailed: number; |
|
messagesSent: number; |
|
messagesReceived: number; |
|
keepAlivesSent: number; |
|
lastLocalStreamCreatedTimestamp: Date | null; |
|
lastRemoteStreamCreatedTimestamp: Date | null; |
|
lastMessageSentTimestamp: Date | null; |
|
lastMessageReceivedTimestamp: Date | null; |
|
localFlowControlWindow: number | null; |
|
remoteFlowControlWindow: number | null; |
|
} |
|
|
|
interface ChannelEntry { |
|
ref: ChannelRef; |
|
getInfo(): ChannelInfo; |
|
} |
|
|
|
interface SubchannelEntry { |
|
ref: SubchannelRef; |
|
getInfo(): SubchannelInfo; |
|
} |
|
|
|
interface ServerEntry { |
|
ref: ServerRef; |
|
getInfo(): ServerInfo; |
|
} |
|
|
|
interface SocketEntry { |
|
ref: SocketRef; |
|
getInfo(): SocketInfo; |
|
} |
|
|
|
let nextId = 1; |
|
|
|
function getNextId(): number { |
|
return nextId++; |
|
} |
|
|
|
const channels: (ChannelEntry | undefined)[] = []; |
|
const subchannels: (SubchannelEntry | undefined)[] = []; |
|
const servers: (ServerEntry | undefined)[] = []; |
|
const sockets: (SocketEntry | undefined)[] = []; |
|
|
|
export function registerChannelzChannel( |
|
name: string, |
|
getInfo: () => ChannelInfo, |
|
channelzEnabled: boolean |
|
): ChannelRef { |
|
const id = getNextId(); |
|
const ref: ChannelRef = { id, name, kind: 'channel' }; |
|
if (channelzEnabled) { |
|
channels[id] = { ref, getInfo }; |
|
} |
|
return ref; |
|
} |
|
|
|
export function registerChannelzSubchannel( |
|
name: string, |
|
getInfo: () => SubchannelInfo, |
|
channelzEnabled: boolean |
|
): SubchannelRef { |
|
const id = getNextId(); |
|
const ref: SubchannelRef = { id, name, kind: 'subchannel' }; |
|
if (channelzEnabled) { |
|
subchannels[id] = { ref, getInfo }; |
|
} |
|
return ref; |
|
} |
|
|
|
export function registerChannelzServer( |
|
getInfo: () => ServerInfo, |
|
channelzEnabled: boolean |
|
): ServerRef { |
|
const id = getNextId(); |
|
const ref: ServerRef = { id, kind: 'server' }; |
|
if (channelzEnabled) { |
|
servers[id] = { ref, getInfo }; |
|
} |
|
return ref; |
|
} |
|
|
|
export function registerChannelzSocket( |
|
name: string, |
|
getInfo: () => SocketInfo, |
|
channelzEnabled: boolean |
|
): SocketRef { |
|
const id = getNextId(); |
|
const ref: SocketRef = { id, name, kind: 'socket' }; |
|
if (channelzEnabled) { |
|
sockets[id] = { ref, getInfo }; |
|
} |
|
return ref; |
|
} |
|
|
|
export function unregisterChannelzRef( |
|
ref: ChannelRef | SubchannelRef | ServerRef | SocketRef |
|
) { |
|
switch (ref.kind) { |
|
case 'channel': |
|
delete channels[ref.id]; |
|
return; |
|
case 'subchannel': |
|
delete subchannels[ref.id]; |
|
return; |
|
case 'server': |
|
delete servers[ref.id]; |
|
return; |
|
case 'socket': |
|
delete sockets[ref.id]; |
|
return; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
function parseIPv6Section(addressSection: string): [number, number] { |
|
const numberValue = Number.parseInt(addressSection, 16); |
|
return [(numberValue / 256) | 0, numberValue % 256]; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function parseIPv6Chunk(addressChunk: string): number[] { |
|
if (addressChunk === '') { |
|
return []; |
|
} |
|
const bytePairs = addressChunk |
|
.split(':') |
|
.map(section => parseIPv6Section(section)); |
|
const result: number[] = []; |
|
return result.concat(...bytePairs); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function ipAddressStringToBuffer(ipAddress: string): Buffer | null { |
|
if (isIPv4(ipAddress)) { |
|
return Buffer.from( |
|
Uint8Array.from( |
|
ipAddress.split('.').map(segment => Number.parseInt(segment)) |
|
) |
|
); |
|
} else if (isIPv6(ipAddress)) { |
|
let leftSection: string; |
|
let rightSection: string; |
|
const doubleColonIndex = ipAddress.indexOf('::'); |
|
if (doubleColonIndex === -1) { |
|
leftSection = ipAddress; |
|
rightSection = ''; |
|
} else { |
|
leftSection = ipAddress.substring(0, doubleColonIndex); |
|
rightSection = ipAddress.substring(doubleColonIndex + 2); |
|
} |
|
const leftBuffer = Buffer.from(parseIPv6Chunk(leftSection)); |
|
const rightBuffer = Buffer.from(parseIPv6Chunk(rightSection)); |
|
const middleBuffer = Buffer.alloc( |
|
16 - leftBuffer.length - rightBuffer.length, |
|
0 |
|
); |
|
return Buffer.concat([leftBuffer, middleBuffer, rightBuffer]); |
|
} else { |
|
return null; |
|
} |
|
} |
|
|
|
function connectivityStateToMessage( |
|
state: ConnectivityState |
|
): ChannelConnectivityState__Output { |
|
switch (state) { |
|
case ConnectivityState.CONNECTING: |
|
return { |
|
state: 'CONNECTING', |
|
}; |
|
case ConnectivityState.IDLE: |
|
return { |
|
state: 'IDLE', |
|
}; |
|
case ConnectivityState.READY: |
|
return { |
|
state: 'READY', |
|
}; |
|
case ConnectivityState.SHUTDOWN: |
|
return { |
|
state: 'SHUTDOWN', |
|
}; |
|
case ConnectivityState.TRANSIENT_FAILURE: |
|
return { |
|
state: 'TRANSIENT_FAILURE', |
|
}; |
|
default: |
|
return { |
|
state: 'UNKNOWN', |
|
}; |
|
} |
|
} |
|
|
|
function dateToProtoTimestamp(date?: Date | null): Timestamp | null { |
|
if (!date) { |
|
return null; |
|
} |
|
const millisSinceEpoch = date.getTime(); |
|
return { |
|
seconds: (millisSinceEpoch / 1000) | 0, |
|
nanos: (millisSinceEpoch % 1000) * 1_000_000, |
|
}; |
|
} |
|
|
|
function getChannelMessage(channelEntry: ChannelEntry): ChannelMessage { |
|
const resolvedInfo = channelEntry.getInfo(); |
|
return { |
|
ref: channelRefToMessage(channelEntry.ref), |
|
data: { |
|
target: resolvedInfo.target, |
|
state: connectivityStateToMessage(resolvedInfo.state), |
|
calls_started: resolvedInfo.callTracker.callsStarted, |
|
calls_succeeded: resolvedInfo.callTracker.callsSucceeded, |
|
calls_failed: resolvedInfo.callTracker.callsFailed, |
|
last_call_started_timestamp: dateToProtoTimestamp( |
|
resolvedInfo.callTracker.lastCallStartedTimestamp |
|
), |
|
trace: resolvedInfo.trace.getTraceMessage(), |
|
}, |
|
channel_ref: resolvedInfo.children.channels.map(ref => |
|
channelRefToMessage(ref) |
|
), |
|
subchannel_ref: resolvedInfo.children.subchannels.map(ref => |
|
subchannelRefToMessage(ref) |
|
), |
|
}; |
|
} |
|
|
|
function GetChannel( |
|
call: ServerUnaryCall<GetChannelRequest__Output, GetChannelResponse>, |
|
callback: sendUnaryData<GetChannelResponse> |
|
): void { |
|
const channelId = Number.parseInt(call.request.channel_id); |
|
const channelEntry = channels[channelId]; |
|
if (channelEntry === undefined) { |
|
callback({ |
|
code: Status.NOT_FOUND, |
|
details: 'No channel data found for id ' + channelId, |
|
}); |
|
return; |
|
} |
|
callback(null, { channel: getChannelMessage(channelEntry) }); |
|
} |
|
|
|
function GetTopChannels( |
|
call: ServerUnaryCall<GetTopChannelsRequest__Output, GetTopChannelsResponse>, |
|
callback: sendUnaryData<GetTopChannelsResponse> |
|
): void { |
|
const maxResults = Number.parseInt(call.request.max_results); |
|
const resultList: ChannelMessage[] = []; |
|
let i = Number.parseInt(call.request.start_channel_id); |
|
for (; i < channels.length; i++) { |
|
const channelEntry = channels[i]; |
|
if (channelEntry === undefined) { |
|
continue; |
|
} |
|
resultList.push(getChannelMessage(channelEntry)); |
|
if (resultList.length >= maxResults) { |
|
break; |
|
} |
|
} |
|
callback(null, { |
|
channel: resultList, |
|
end: i >= servers.length, |
|
}); |
|
} |
|
|
|
function getServerMessage(serverEntry: ServerEntry): ServerMessage { |
|
const resolvedInfo = serverEntry.getInfo(); |
|
return { |
|
ref: serverRefToMessage(serverEntry.ref), |
|
data: { |
|
calls_started: resolvedInfo.callTracker.callsStarted, |
|
calls_succeeded: resolvedInfo.callTracker.callsSucceeded, |
|
calls_failed: resolvedInfo.callTracker.callsFailed, |
|
last_call_started_timestamp: dateToProtoTimestamp( |
|
resolvedInfo.callTracker.lastCallStartedTimestamp |
|
), |
|
trace: resolvedInfo.trace.getTraceMessage(), |
|
}, |
|
listen_socket: resolvedInfo.listenerChildren.sockets.map(ref => |
|
socketRefToMessage(ref) |
|
), |
|
}; |
|
} |
|
|
|
function GetServer( |
|
call: ServerUnaryCall<GetServerRequest__Output, GetServerResponse>, |
|
callback: sendUnaryData<GetServerResponse> |
|
): void { |
|
const serverId = Number.parseInt(call.request.server_id); |
|
const serverEntry = servers[serverId]; |
|
if (serverEntry === undefined) { |
|
callback({ |
|
code: Status.NOT_FOUND, |
|
details: 'No server data found for id ' + serverId, |
|
}); |
|
return; |
|
} |
|
callback(null, { server: getServerMessage(serverEntry) }); |
|
} |
|
|
|
function GetServers( |
|
call: ServerUnaryCall<GetServersRequest__Output, GetServersResponse>, |
|
callback: sendUnaryData<GetServersResponse> |
|
): void { |
|
const maxResults = Number.parseInt(call.request.max_results); |
|
const resultList: ServerMessage[] = []; |
|
let i = Number.parseInt(call.request.start_server_id); |
|
for (; i < servers.length; i++) { |
|
const serverEntry = servers[i]; |
|
if (serverEntry === undefined) { |
|
continue; |
|
} |
|
resultList.push(getServerMessage(serverEntry)); |
|
if (resultList.length >= maxResults) { |
|
break; |
|
} |
|
} |
|
callback(null, { |
|
server: resultList, |
|
end: i >= servers.length, |
|
}); |
|
} |
|
|
|
function GetSubchannel( |
|
call: ServerUnaryCall<GetSubchannelRequest__Output, GetSubchannelResponse>, |
|
callback: sendUnaryData<GetSubchannelResponse> |
|
): void { |
|
const subchannelId = Number.parseInt(call.request.subchannel_id); |
|
const subchannelEntry = subchannels[subchannelId]; |
|
if (subchannelEntry === undefined) { |
|
callback({ |
|
code: Status.NOT_FOUND, |
|
details: 'No subchannel data found for id ' + subchannelId, |
|
}); |
|
return; |
|
} |
|
const resolvedInfo = subchannelEntry.getInfo(); |
|
const subchannelMessage: SubchannelMessage = { |
|
ref: subchannelRefToMessage(subchannelEntry.ref), |
|
data: { |
|
target: resolvedInfo.target, |
|
state: connectivityStateToMessage(resolvedInfo.state), |
|
calls_started: resolvedInfo.callTracker.callsStarted, |
|
calls_succeeded: resolvedInfo.callTracker.callsSucceeded, |
|
calls_failed: resolvedInfo.callTracker.callsFailed, |
|
last_call_started_timestamp: dateToProtoTimestamp( |
|
resolvedInfo.callTracker.lastCallStartedTimestamp |
|
), |
|
trace: resolvedInfo.trace.getTraceMessage(), |
|
}, |
|
socket_ref: resolvedInfo.children.sockets.map(ref => |
|
socketRefToMessage(ref) |
|
), |
|
}; |
|
callback(null, { subchannel: subchannelMessage }); |
|
} |
|
|
|
function subchannelAddressToAddressMessage( |
|
subchannelAddress: SubchannelAddress |
|
): Address { |
|
if (isTcpSubchannelAddress(subchannelAddress)) { |
|
return { |
|
address: 'tcpip_address', |
|
tcpip_address: { |
|
ip_address: |
|
ipAddressStringToBuffer(subchannelAddress.host) ?? undefined, |
|
port: subchannelAddress.port, |
|
}, |
|
}; |
|
} else { |
|
return { |
|
address: 'uds_address', |
|
uds_address: { |
|
filename: subchannelAddress.path, |
|
}, |
|
}; |
|
} |
|
} |
|
|
|
function GetSocket( |
|
call: ServerUnaryCall<GetSocketRequest__Output, GetSocketResponse>, |
|
callback: sendUnaryData<GetSocketResponse> |
|
): void { |
|
const socketId = Number.parseInt(call.request.socket_id); |
|
const socketEntry = sockets[socketId]; |
|
if (socketEntry === undefined) { |
|
callback({ |
|
code: Status.NOT_FOUND, |
|
details: 'No socket data found for id ' + socketId, |
|
}); |
|
return; |
|
} |
|
const resolvedInfo = socketEntry.getInfo(); |
|
const securityMessage: Security | null = resolvedInfo.security |
|
? { |
|
model: 'tls', |
|
tls: { |
|
cipher_suite: resolvedInfo.security.cipherSuiteStandardName |
|
? 'standard_name' |
|
: 'other_name', |
|
standard_name: |
|
resolvedInfo.security.cipherSuiteStandardName ?? undefined, |
|
other_name: resolvedInfo.security.cipherSuiteOtherName ?? undefined, |
|
local_certificate: |
|
resolvedInfo.security.localCertificate ?? undefined, |
|
remote_certificate: |
|
resolvedInfo.security.remoteCertificate ?? undefined, |
|
}, |
|
} |
|
: null; |
|
const socketMessage: SocketMessage = { |
|
ref: socketRefToMessage(socketEntry.ref), |
|
local: resolvedInfo.localAddress |
|
? subchannelAddressToAddressMessage(resolvedInfo.localAddress) |
|
: null, |
|
remote: resolvedInfo.remoteAddress |
|
? subchannelAddressToAddressMessage(resolvedInfo.remoteAddress) |
|
: null, |
|
remote_name: resolvedInfo.remoteName ?? undefined, |
|
security: securityMessage, |
|
data: { |
|
keep_alives_sent: resolvedInfo.keepAlivesSent, |
|
streams_started: resolvedInfo.streamsStarted, |
|
streams_succeeded: resolvedInfo.streamsSucceeded, |
|
streams_failed: resolvedInfo.streamsFailed, |
|
last_local_stream_created_timestamp: dateToProtoTimestamp( |
|
resolvedInfo.lastLocalStreamCreatedTimestamp |
|
), |
|
last_remote_stream_created_timestamp: dateToProtoTimestamp( |
|
resolvedInfo.lastRemoteStreamCreatedTimestamp |
|
), |
|
messages_received: resolvedInfo.messagesReceived, |
|
messages_sent: resolvedInfo.messagesSent, |
|
last_message_received_timestamp: dateToProtoTimestamp( |
|
resolvedInfo.lastMessageReceivedTimestamp |
|
), |
|
last_message_sent_timestamp: dateToProtoTimestamp( |
|
resolvedInfo.lastMessageSentTimestamp |
|
), |
|
local_flow_control_window: resolvedInfo.localFlowControlWindow |
|
? { value: resolvedInfo.localFlowControlWindow } |
|
: null, |
|
remote_flow_control_window: resolvedInfo.remoteFlowControlWindow |
|
? { value: resolvedInfo.remoteFlowControlWindow } |
|
: null, |
|
}, |
|
}; |
|
callback(null, { socket: socketMessage }); |
|
} |
|
|
|
function GetServerSockets( |
|
call: ServerUnaryCall< |
|
GetServerSocketsRequest__Output, |
|
GetServerSocketsResponse |
|
>, |
|
callback: sendUnaryData<GetServerSocketsResponse> |
|
): void { |
|
const serverId = Number.parseInt(call.request.server_id); |
|
const serverEntry = servers[serverId]; |
|
if (serverEntry === undefined) { |
|
callback({ |
|
code: Status.NOT_FOUND, |
|
details: 'No server data found for id ' + serverId, |
|
}); |
|
return; |
|
} |
|
const startId = Number.parseInt(call.request.start_socket_id); |
|
const maxResults = Number.parseInt(call.request.max_results); |
|
const resolvedInfo = serverEntry.getInfo(); |
|
|
|
|
|
|
|
const allSockets = resolvedInfo.sessionChildren.sockets.sort( |
|
(ref1, ref2) => ref1.id - ref2.id |
|
); |
|
const resultList: SocketRefMessage[] = []; |
|
let i = 0; |
|
for (; i < allSockets.length; i++) { |
|
if (allSockets[i].id >= startId) { |
|
resultList.push(socketRefToMessage(allSockets[i])); |
|
if (resultList.length >= maxResults) { |
|
break; |
|
} |
|
} |
|
} |
|
callback(null, { |
|
socket_ref: resultList, |
|
end: i >= allSockets.length, |
|
}); |
|
} |
|
|
|
export function getChannelzHandlers(): ChannelzHandlers { |
|
return { |
|
GetChannel, |
|
GetTopChannels, |
|
GetServer, |
|
GetServers, |
|
GetSubchannel, |
|
GetSocket, |
|
GetServerSockets, |
|
}; |
|
} |
|
|
|
let loadedChannelzDefinition: ChannelzDefinition | null = null; |
|
|
|
export function getChannelzServiceDefinition(): ChannelzDefinition { |
|
if (loadedChannelzDefinition) { |
|
return loadedChannelzDefinition; |
|
} |
|
|
|
|
|
const loaderLoadSync = require('@grpc/proto-loader') |
|
.loadSync as typeof loadSync; |
|
const loadedProto = loaderLoadSync('channelz.proto', { |
|
keepCase: true, |
|
longs: String, |
|
enums: String, |
|
defaults: true, |
|
oneofs: true, |
|
includeDirs: [`${__dirname}/../../proto`], |
|
}); |
|
const channelzGrpcObject = loadPackageDefinition( |
|
loadedProto |
|
) as unknown as ChannelzProtoGrpcType; |
|
loadedChannelzDefinition = |
|
channelzGrpcObject.grpc.channelz.v1.Channelz.service; |
|
return loadedChannelzDefinition; |
|
} |
|
|
|
export function setup() { |
|
registerAdminService(getChannelzServiceDefinition, getChannelzHandlers); |
|
} |
|
|