channelz.ts 27 KB

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