|
"use strict"; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Object.defineProperty(exports, "__esModule", { value: true }); |
|
exports.registerChannelzSocket = exports.registerChannelzServer = exports.registerChannelzSubchannel = exports.registerChannelzChannel = exports.ChannelzCallTrackerStub = exports.ChannelzCallTracker = exports.ChannelzChildrenTrackerStub = exports.ChannelzChildrenTracker = exports.ChannelzTrace = exports.ChannelzTraceStub = void 0; |
|
exports.unregisterChannelzRef = unregisterChannelzRef; |
|
exports.getChannelzHandlers = getChannelzHandlers; |
|
exports.getChannelzServiceDefinition = getChannelzServiceDefinition; |
|
exports.setup = setup; |
|
const net_1 = require("net"); |
|
const ordered_map_1 = require("@js-sdsl/ordered-map"); |
|
const connectivity_state_1 = require("./connectivity-state"); |
|
const constants_1 = require("./constants"); |
|
const subchannel_address_1 = require("./subchannel-address"); |
|
const admin_1 = require("./admin"); |
|
const make_client_1 = require("./make-client"); |
|
function channelRefToMessage(ref) { |
|
return { |
|
channel_id: ref.id, |
|
name: ref.name, |
|
}; |
|
} |
|
function subchannelRefToMessage(ref) { |
|
return { |
|
subchannel_id: ref.id, |
|
name: ref.name, |
|
}; |
|
} |
|
function serverRefToMessage(ref) { |
|
return { |
|
server_id: ref.id, |
|
}; |
|
} |
|
function socketRefToMessage(ref) { |
|
return { |
|
socket_id: ref.id, |
|
name: ref.name, |
|
}; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
const TARGET_RETAINED_TRACES = 32; |
|
|
|
|
|
|
|
const DEFAULT_MAX_RESULTS = 100; |
|
class ChannelzTraceStub { |
|
constructor() { |
|
this.events = []; |
|
this.creationTimestamp = new Date(); |
|
this.eventsLogged = 0; |
|
} |
|
addTrace() { } |
|
getTraceMessage() { |
|
return { |
|
creation_timestamp: dateToProtoTimestamp(this.creationTimestamp), |
|
num_events_logged: this.eventsLogged, |
|
events: [], |
|
}; |
|
} |
|
} |
|
exports.ChannelzTraceStub = ChannelzTraceStub; |
|
class ChannelzTrace { |
|
constructor() { |
|
this.events = []; |
|
this.eventsLogged = 0; |
|
this.creationTimestamp = new Date(); |
|
} |
|
addTrace(severity, description, child) { |
|
const timestamp = new Date(); |
|
this.events.push({ |
|
description: description, |
|
severity: severity, |
|
timestamp: timestamp, |
|
childChannel: (child === null || child === void 0 ? void 0 : child.kind) === 'channel' ? child : undefined, |
|
childSubchannel: (child === null || child === void 0 ? void 0 : 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() { |
|
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, |
|
}; |
|
}), |
|
}; |
|
} |
|
} |
|
exports.ChannelzTrace = ChannelzTrace; |
|
class ChannelzChildrenTracker { |
|
constructor() { |
|
this.channelChildren = new ordered_map_1.OrderedMap(); |
|
this.subchannelChildren = new ordered_map_1.OrderedMap(); |
|
this.socketChildren = new ordered_map_1.OrderedMap(); |
|
this.trackerMap = { |
|
["channel" ]: this.channelChildren, |
|
["subchannel" ]: this.subchannelChildren, |
|
["socket" ]: this.socketChildren, |
|
}; |
|
} |
|
refChild(child) { |
|
const tracker = this.trackerMap[child.kind]; |
|
const trackedChild = tracker.find(child.id); |
|
if (trackedChild.equals(tracker.end())) { |
|
tracker.setElement(child.id, { |
|
ref: child, |
|
count: 1, |
|
}, trackedChild); |
|
} |
|
else { |
|
trackedChild.pointer[1].count += 1; |
|
} |
|
} |
|
unrefChild(child) { |
|
const tracker = this.trackerMap[child.kind]; |
|
const trackedChild = tracker.getElementByKey(child.id); |
|
if (trackedChild !== undefined) { |
|
trackedChild.count -= 1; |
|
if (trackedChild.count === 0) { |
|
tracker.eraseElementByKey(child.id); |
|
} |
|
} |
|
} |
|
getChildLists() { |
|
return { |
|
channels: this.channelChildren, |
|
subchannels: this.subchannelChildren, |
|
sockets: this.socketChildren, |
|
}; |
|
} |
|
} |
|
exports.ChannelzChildrenTracker = ChannelzChildrenTracker; |
|
class ChannelzChildrenTrackerStub extends ChannelzChildrenTracker { |
|
refChild() { } |
|
unrefChild() { } |
|
} |
|
exports.ChannelzChildrenTrackerStub = ChannelzChildrenTrackerStub; |
|
class ChannelzCallTracker { |
|
constructor() { |
|
this.callsStarted = 0; |
|
this.callsSucceeded = 0; |
|
this.callsFailed = 0; |
|
this.lastCallStartedTimestamp = null; |
|
} |
|
addCallStarted() { |
|
this.callsStarted += 1; |
|
this.lastCallStartedTimestamp = new Date(); |
|
} |
|
addCallSucceeded() { |
|
this.callsSucceeded += 1; |
|
} |
|
addCallFailed() { |
|
this.callsFailed += 1; |
|
} |
|
} |
|
exports.ChannelzCallTracker = ChannelzCallTracker; |
|
class ChannelzCallTrackerStub extends ChannelzCallTracker { |
|
addCallStarted() { } |
|
addCallSucceeded() { } |
|
addCallFailed() { } |
|
} |
|
exports.ChannelzCallTrackerStub = ChannelzCallTrackerStub; |
|
const entityMaps = { |
|
["channel" ]: new ordered_map_1.OrderedMap(), |
|
["subchannel" ]: new ordered_map_1.OrderedMap(), |
|
["server" ]: new ordered_map_1.OrderedMap(), |
|
["socket" ]: new ordered_map_1.OrderedMap(), |
|
}; |
|
const generateRegisterFn = (kind) => { |
|
let nextId = 1; |
|
function getNextId() { |
|
return nextId++; |
|
} |
|
const entityMap = entityMaps[kind]; |
|
return (name, getInfo, channelzEnabled) => { |
|
const id = getNextId(); |
|
const ref = { id, name, kind }; |
|
if (channelzEnabled) { |
|
entityMap.setElement(id, { ref, getInfo }); |
|
} |
|
return ref; |
|
}; |
|
}; |
|
exports.registerChannelzChannel = generateRegisterFn("channel" ); |
|
exports.registerChannelzSubchannel = generateRegisterFn("subchannel" ); |
|
exports.registerChannelzServer = generateRegisterFn("server" ); |
|
exports.registerChannelzSocket = generateRegisterFn("socket" ); |
|
function unregisterChannelzRef(ref) { |
|
entityMaps[ref.kind].eraseElementByKey(ref.id); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function parseIPv6Section(addressSection) { |
|
const numberValue = Number.parseInt(addressSection, 16); |
|
return [(numberValue / 256) | 0, numberValue % 256]; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
function parseIPv6Chunk(addressChunk) { |
|
if (addressChunk === '') { |
|
return []; |
|
} |
|
const bytePairs = addressChunk |
|
.split(':') |
|
.map(section => parseIPv6Section(section)); |
|
const result = []; |
|
return result.concat(...bytePairs); |
|
} |
|
function isIPv6MappedIPv4(ipAddress) { |
|
return (0, net_1.isIPv6)(ipAddress) && ipAddress.toLowerCase().startsWith('::ffff:') && (0, net_1.isIPv4)(ipAddress.substring(7)); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function ipv4AddressStringToBuffer(ipAddress) { |
|
return Buffer.from(Uint8Array.from(ipAddress.split('.').map(segment => Number.parseInt(segment)))); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
function ipAddressStringToBuffer(ipAddress) { |
|
if ((0, net_1.isIPv4)(ipAddress)) { |
|
return ipv4AddressStringToBuffer(ipAddress); |
|
} |
|
else if (isIPv6MappedIPv4(ipAddress)) { |
|
return ipv4AddressStringToBuffer(ipAddress.substring(7)); |
|
} |
|
else if ((0, net_1.isIPv6)(ipAddress)) { |
|
let leftSection; |
|
let rightSection; |
|
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) { |
|
switch (state) { |
|
case connectivity_state_1.ConnectivityState.CONNECTING: |
|
return { |
|
state: 'CONNECTING', |
|
}; |
|
case connectivity_state_1.ConnectivityState.IDLE: |
|
return { |
|
state: 'IDLE', |
|
}; |
|
case connectivity_state_1.ConnectivityState.READY: |
|
return { |
|
state: 'READY', |
|
}; |
|
case connectivity_state_1.ConnectivityState.SHUTDOWN: |
|
return { |
|
state: 'SHUTDOWN', |
|
}; |
|
case connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE: |
|
return { |
|
state: 'TRANSIENT_FAILURE', |
|
}; |
|
default: |
|
return { |
|
state: 'UNKNOWN', |
|
}; |
|
} |
|
} |
|
function dateToProtoTimestamp(date) { |
|
if (!date) { |
|
return null; |
|
} |
|
const millisSinceEpoch = date.getTime(); |
|
return { |
|
seconds: (millisSinceEpoch / 1000) | 0, |
|
nanos: (millisSinceEpoch % 1000) * 1000000, |
|
}; |
|
} |
|
function getChannelMessage(channelEntry) { |
|
const resolvedInfo = channelEntry.getInfo(); |
|
const channelRef = []; |
|
const subchannelRef = []; |
|
resolvedInfo.children.channels.forEach(el => { |
|
channelRef.push(channelRefToMessage(el[1].ref)); |
|
}); |
|
resolvedInfo.children.subchannels.forEach(el => { |
|
subchannelRef.push(subchannelRefToMessage(el[1].ref)); |
|
}); |
|
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: channelRef, |
|
subchannel_ref: subchannelRef, |
|
}; |
|
} |
|
function GetChannel(call, callback) { |
|
const channelId = parseInt(call.request.channel_id, 10); |
|
const channelEntry = entityMaps["channel" ].getElementByKey(channelId); |
|
if (channelEntry === undefined) { |
|
callback({ |
|
code: constants_1.Status.NOT_FOUND, |
|
details: 'No channel data found for id ' + channelId, |
|
}); |
|
return; |
|
} |
|
callback(null, { channel: getChannelMessage(channelEntry) }); |
|
} |
|
function GetTopChannels(call, callback) { |
|
const maxResults = parseInt(call.request.max_results, 10) || DEFAULT_MAX_RESULTS; |
|
const resultList = []; |
|
const startId = parseInt(call.request.start_channel_id, 10); |
|
const channelEntries = entityMaps["channel" ]; |
|
let i; |
|
for (i = channelEntries.lowerBound(startId); !i.equals(channelEntries.end()) && resultList.length < maxResults; i = i.next()) { |
|
resultList.push(getChannelMessage(i.pointer[1])); |
|
} |
|
callback(null, { |
|
channel: resultList, |
|
end: i.equals(channelEntries.end()), |
|
}); |
|
} |
|
function getServerMessage(serverEntry) { |
|
const resolvedInfo = serverEntry.getInfo(); |
|
const listenSocket = []; |
|
resolvedInfo.listenerChildren.sockets.forEach(el => { |
|
listenSocket.push(socketRefToMessage(el[1].ref)); |
|
}); |
|
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: listenSocket, |
|
}; |
|
} |
|
function GetServer(call, callback) { |
|
const serverId = parseInt(call.request.server_id, 10); |
|
const serverEntries = entityMaps["server" ]; |
|
const serverEntry = serverEntries.getElementByKey(serverId); |
|
if (serverEntry === undefined) { |
|
callback({ |
|
code: constants_1.Status.NOT_FOUND, |
|
details: 'No server data found for id ' + serverId, |
|
}); |
|
return; |
|
} |
|
callback(null, { server: getServerMessage(serverEntry) }); |
|
} |
|
function GetServers(call, callback) { |
|
const maxResults = parseInt(call.request.max_results, 10) || DEFAULT_MAX_RESULTS; |
|
const startId = parseInt(call.request.start_server_id, 10); |
|
const serverEntries = entityMaps["server" ]; |
|
const resultList = []; |
|
let i; |
|
for (i = serverEntries.lowerBound(startId); !i.equals(serverEntries.end()) && resultList.length < maxResults; i = i.next()) { |
|
resultList.push(getServerMessage(i.pointer[1])); |
|
} |
|
callback(null, { |
|
server: resultList, |
|
end: i.equals(serverEntries.end()), |
|
}); |
|
} |
|
function GetSubchannel(call, callback) { |
|
const subchannelId = parseInt(call.request.subchannel_id, 10); |
|
const subchannelEntry = entityMaps["subchannel" ].getElementByKey(subchannelId); |
|
if (subchannelEntry === undefined) { |
|
callback({ |
|
code: constants_1.Status.NOT_FOUND, |
|
details: 'No subchannel data found for id ' + subchannelId, |
|
}); |
|
return; |
|
} |
|
const resolvedInfo = subchannelEntry.getInfo(); |
|
const listenSocket = []; |
|
resolvedInfo.children.sockets.forEach(el => { |
|
listenSocket.push(socketRefToMessage(el[1].ref)); |
|
}); |
|
const 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: listenSocket, |
|
}; |
|
callback(null, { subchannel: subchannelMessage }); |
|
} |
|
function subchannelAddressToAddressMessage(subchannelAddress) { |
|
var _a; |
|
if ((0, subchannel_address_1.isTcpSubchannelAddress)(subchannelAddress)) { |
|
return { |
|
address: 'tcpip_address', |
|
tcpip_address: { |
|
ip_address: (_a = ipAddressStringToBuffer(subchannelAddress.host)) !== null && _a !== void 0 ? _a : undefined, |
|
port: subchannelAddress.port, |
|
}, |
|
}; |
|
} |
|
else { |
|
return { |
|
address: 'uds_address', |
|
uds_address: { |
|
filename: subchannelAddress.path, |
|
}, |
|
}; |
|
} |
|
} |
|
function GetSocket(call, callback) { |
|
var _a, _b, _c, _d, _e; |
|
const socketId = parseInt(call.request.socket_id, 10); |
|
const socketEntry = entityMaps["socket" ].getElementByKey(socketId); |
|
if (socketEntry === undefined) { |
|
callback({ |
|
code: constants_1.Status.NOT_FOUND, |
|
details: 'No socket data found for id ' + socketId, |
|
}); |
|
return; |
|
} |
|
const resolvedInfo = socketEntry.getInfo(); |
|
const securityMessage = resolvedInfo.security |
|
? { |
|
model: 'tls', |
|
tls: { |
|
cipher_suite: resolvedInfo.security.cipherSuiteStandardName |
|
? 'standard_name' |
|
: 'other_name', |
|
standard_name: (_a = resolvedInfo.security.cipherSuiteStandardName) !== null && _a !== void 0 ? _a : undefined, |
|
other_name: (_b = resolvedInfo.security.cipherSuiteOtherName) !== null && _b !== void 0 ? _b : undefined, |
|
local_certificate: (_c = resolvedInfo.security.localCertificate) !== null && _c !== void 0 ? _c : undefined, |
|
remote_certificate: (_d = resolvedInfo.security.remoteCertificate) !== null && _d !== void 0 ? _d : undefined, |
|
}, |
|
} |
|
: null; |
|
const socketMessage = { |
|
ref: socketRefToMessage(socketEntry.ref), |
|
local: resolvedInfo.localAddress |
|
? subchannelAddressToAddressMessage(resolvedInfo.localAddress) |
|
: null, |
|
remote: resolvedInfo.remoteAddress |
|
? subchannelAddressToAddressMessage(resolvedInfo.remoteAddress) |
|
: null, |
|
remote_name: (_e = resolvedInfo.remoteName) !== null && _e !== void 0 ? _e : 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, callback) { |
|
const serverId = parseInt(call.request.server_id, 10); |
|
const serverEntry = entityMaps["server" ].getElementByKey(serverId); |
|
if (serverEntry === undefined) { |
|
callback({ |
|
code: constants_1.Status.NOT_FOUND, |
|
details: 'No server data found for id ' + serverId, |
|
}); |
|
return; |
|
} |
|
const startId = parseInt(call.request.start_socket_id, 10); |
|
const maxResults = parseInt(call.request.max_results, 10) || DEFAULT_MAX_RESULTS; |
|
const resolvedInfo = serverEntry.getInfo(); |
|
|
|
|
|
|
|
const allSockets = resolvedInfo.sessionChildren.sockets; |
|
const resultList = []; |
|
let i; |
|
for (i = allSockets.lowerBound(startId); !i.equals(allSockets.end()) && resultList.length < maxResults; i = i.next()) { |
|
resultList.push(socketRefToMessage(i.pointer[1].ref)); |
|
} |
|
callback(null, { |
|
socket_ref: resultList, |
|
end: i.equals(allSockets.end()), |
|
}); |
|
} |
|
function getChannelzHandlers() { |
|
return { |
|
GetChannel, |
|
GetTopChannels, |
|
GetServer, |
|
GetServers, |
|
GetSubchannel, |
|
GetSocket, |
|
GetServerSockets, |
|
}; |
|
} |
|
let loadedChannelzDefinition = null; |
|
function getChannelzServiceDefinition() { |
|
if (loadedChannelzDefinition) { |
|
return loadedChannelzDefinition; |
|
} |
|
|
|
|
|
const loaderLoadSync = require('@grpc/proto-loader') |
|
.loadSync; |
|
const loadedProto = loaderLoadSync('channelz.proto', { |
|
keepCase: true, |
|
longs: String, |
|
enums: String, |
|
defaults: true, |
|
oneofs: true, |
|
includeDirs: [`${__dirname}/../../proto`], |
|
}); |
|
const channelzGrpcObject = (0, make_client_1.loadPackageDefinition)(loadedProto); |
|
loadedChannelzDefinition = |
|
channelzGrpcObject.grpc.channelz.v1.Channelz.service; |
|
return loadedChannelzDefinition; |
|
} |
|
function setup() { |
|
(0, admin_1.registerAdminService)(getChannelzServiceDefinition, getChannelzHandlers); |
|
} |
|
|