UNPKG

@grpc/grpc-js

Version:

gRPC Library for Node - pure JS implementation

323 lines 11.7 kB
"use strict"; /* * Copyright 2025 gRPC authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ Object.defineProperty(exports, "__esModule", { value: true }); exports.OrcaOobMetricsSubchannelWrapper = exports.GRPC_METRICS_HEADER = exports.ServerMetricRecorder = exports.PerRequestMetricRecorder = void 0; exports.createOrcaClient = createOrcaClient; exports.createMetricsReader = createMetricsReader; const make_client_1 = require("./make-client"); const duration_1 = require("./duration"); const channel_credentials_1 = require("./channel-credentials"); const subchannel_interface_1 = require("./subchannel-interface"); const constants_1 = require("./constants"); const backoff_timeout_1 = require("./backoff-timeout"); const connectivity_state_1 = require("./connectivity-state"); const loadedOrcaProto = null; function loadOrcaProto() { if (loadedOrcaProto) { return loadedOrcaProto; } /* The purpose of this complexity is to avoid loading @grpc/proto-loader at * runtime for users who will not use/enable ORCA. */ const loaderLoadSync = require('@grpc/proto-loader') .loadSync; const loadedProto = loaderLoadSync('xds/service/orca/v3/orca.proto', { keepCase: true, longs: String, enums: String, defaults: true, oneofs: true, includeDirs: [ `${__dirname}/../../proto/xds`, `${__dirname}/../../proto/protoc-gen-validate` ], }); return (0, make_client_1.loadPackageDefinition)(loadedProto); } /** * ORCA metrics recorder for a single request */ class PerRequestMetricRecorder { constructor() { this.message = {}; } /** * Records a request cost metric measurement for the call. * @param name * @param value */ recordRequestCostMetric(name, value) { if (!this.message.request_cost) { this.message.request_cost = {}; } this.message.request_cost[name] = value; } /** * Records a request cost metric measurement for the call. * @param name * @param value */ recordUtilizationMetric(name, value) { if (!this.message.utilization) { this.message.utilization = {}; } this.message.utilization[name] = value; } /** * Records an opaque named metric measurement for the call. * @param name * @param value */ recordNamedMetric(name, value) { if (!this.message.named_metrics) { this.message.named_metrics = {}; } this.message.named_metrics[name] = value; } /** * Records the CPU utilization metric measurement for the call. * @param value */ recordCPUUtilizationMetric(value) { this.message.cpu_utilization = value; } /** * Records the memory utilization metric measurement for the call. * @param value */ recordMemoryUtilizationMetric(value) { this.message.mem_utilization = value; } /** * Records the memory utilization metric measurement for the call. * @param value */ recordApplicationUtilizationMetric(value) { this.message.application_utilization = value; } /** * Records the queries per second measurement. * @param value */ recordQpsMetric(value) { this.message.rps_fractional = value; } /** * Records the errors per second measurement. * @param value */ recordEpsMetric(value) { this.message.eps = value; } serialize() { const orcaProto = loadOrcaProto(); return orcaProto.xds.data.orca.v3.OrcaLoadReport.serialize(this.message); } } exports.PerRequestMetricRecorder = PerRequestMetricRecorder; const DEFAULT_REPORT_INTERVAL_MS = 30000; class ServerMetricRecorder { constructor() { this.message = {}; this.serviceImplementation = { StreamCoreMetrics: call => { const reportInterval = call.request.report_interval ? (0, duration_1.durationToMs)((0, duration_1.durationMessageToDuration)(call.request.report_interval)) : DEFAULT_REPORT_INTERVAL_MS; const reportTimer = setInterval(() => { call.write(this.message); }, reportInterval); call.on('cancelled', () => { clearInterval(reportTimer); }); } }; } putUtilizationMetric(name, value) { if (!this.message.utilization) { this.message.utilization = {}; } this.message.utilization[name] = value; } setAllUtilizationMetrics(metrics) { this.message.utilization = Object.assign({}, metrics); } deleteUtilizationMetric(name) { var _a; (_a = this.message.utilization) === null || _a === void 0 ? true : delete _a[name]; } setCpuUtilizationMetric(value) { this.message.cpu_utilization = value; } deleteCpuUtilizationMetric() { delete this.message.cpu_utilization; } setApplicationUtilizationMetric(value) { this.message.application_utilization = value; } deleteApplicationUtilizationMetric() { delete this.message.application_utilization; } setQpsMetric(value) { this.message.rps_fractional = value; } deleteQpsMetric() { delete this.message.rps_fractional; } setEpsMetric(value) { this.message.eps = value; } deleteEpsMetric() { delete this.message.eps; } addToServer(server) { const serviceDefinition = loadOrcaProto().xds.service.orca.v3.OpenRcaService.service; server.addService(serviceDefinition, this.serviceImplementation); } } exports.ServerMetricRecorder = ServerMetricRecorder; function createOrcaClient(channel) { const ClientClass = loadOrcaProto().xds.service.orca.v3.OpenRcaService; return new ClientClass('unused', channel_credentials_1.ChannelCredentials.createInsecure(), { channelOverride: channel }); } exports.GRPC_METRICS_HEADER = 'endpoint-load-metrics-bin'; const PARSED_LOAD_REPORT_KEY = 'grpc_orca_load_report'; /** * Create an onCallEnded callback for use in a picker. * @param listener The listener to handle metrics, whenever they are provided. * @param previousOnCallEnded The previous onCallEnded callback to propagate * to, if applicable. * @returns */ function createMetricsReader(listener, previousOnCallEnded) { return (code, details, metadata) => { let parsedLoadReport = metadata.getOpaque(PARSED_LOAD_REPORT_KEY); if (parsedLoadReport) { listener(parsedLoadReport); } else { const serializedLoadReport = metadata.get(exports.GRPC_METRICS_HEADER); if (serializedLoadReport.length > 0) { const orcaProto = loadOrcaProto(); parsedLoadReport = orcaProto.xds.data.orca.v3.OrcaLoadReport.deserialize(serializedLoadReport[0]); listener(parsedLoadReport); metadata.setOpaque(PARSED_LOAD_REPORT_KEY, parsedLoadReport); } } if (previousOnCallEnded) { previousOnCallEnded(code, details, metadata); } }; } const DATA_PRODUCER_KEY = 'orca_oob_metrics'; class OobMetricsDataWatcher { constructor(metricsListener, intervalMs) { this.metricsListener = metricsListener; this.intervalMs = intervalMs; this.dataProducer = null; } setSubchannel(subchannel) { const producer = subchannel.getOrCreateDataProducer(DATA_PRODUCER_KEY, createOobMetricsDataProducer); this.dataProducer = producer; producer.addDataWatcher(this); } destroy() { var _a; (_a = this.dataProducer) === null || _a === void 0 ? void 0 : _a.removeDataWatcher(this); } getInterval() { return this.intervalMs; } onMetricsUpdate(metrics) { this.metricsListener(metrics); } } class OobMetricsDataProducer { constructor(subchannel) { this.subchannel = subchannel; this.dataWatchers = new Set(); this.orcaSupported = true; this.metricsCall = null; this.currentInterval = Infinity; this.backoffTimer = new backoff_timeout_1.BackoffTimeout(() => this.updateMetricsSubscription()); this.subchannelStateListener = () => this.updateMetricsSubscription(); const channel = subchannel.getChannel(); this.client = createOrcaClient(channel); subchannel.addConnectivityStateListener(this.subchannelStateListener); } addDataWatcher(dataWatcher) { this.dataWatchers.add(dataWatcher); this.updateMetricsSubscription(); } removeDataWatcher(dataWatcher) { var _a; this.dataWatchers.delete(dataWatcher); if (this.dataWatchers.size === 0) { this.subchannel.removeDataProducer(DATA_PRODUCER_KEY); (_a = this.metricsCall) === null || _a === void 0 ? void 0 : _a.cancel(); this.metricsCall = null; this.client.close(); this.subchannel.removeConnectivityStateListener(this.subchannelStateListener); } else { this.updateMetricsSubscription(); } } updateMetricsSubscription() { var _a; if (this.dataWatchers.size === 0 || !this.orcaSupported || this.subchannel.getConnectivityState() !== connectivity_state_1.ConnectivityState.READY) { return; } const newInterval = Math.min(...Array.from(this.dataWatchers).map(watcher => watcher.getInterval())); if (!this.metricsCall || newInterval !== this.currentInterval) { (_a = this.metricsCall) === null || _a === void 0 ? void 0 : _a.cancel(); this.currentInterval = newInterval; const metricsCall = this.client.streamCoreMetrics({ report_interval: (0, duration_1.msToDuration)(newInterval) }); this.metricsCall = metricsCall; metricsCall.on('data', (report) => { this.dataWatchers.forEach(watcher => { watcher.onMetricsUpdate(report); }); }); metricsCall.on('error', (error) => { this.metricsCall = null; if (error.code === constants_1.Status.UNIMPLEMENTED) { this.orcaSupported = false; return; } if (error.code === constants_1.Status.CANCELLED) { return; } this.backoffTimer.runOnce(); }); } } } class OrcaOobMetricsSubchannelWrapper extends subchannel_interface_1.BaseSubchannelWrapper { constructor(child, metricsListener, intervalMs) { super(child); this.addDataWatcher(new OobMetricsDataWatcher(metricsListener, intervalMs)); } getWrappedSubchannel() { return this.child; } } exports.OrcaOobMetricsSubchannelWrapper = OrcaOobMetricsSubchannelWrapper; function createOobMetricsDataProducer(subchannel) { return new OobMetricsDataProducer(subchannel); } //# sourceMappingURL=orca.js.map