@google-cloud/spanner
Version:
Cloud Spanner Client Library for Node.js
373 lines • 14.9 kB
JavaScript
"use strict";
// Copyright 2025 Google LLC
//
// 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
//
// https://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.MetricsTracer = void 0;
const grpc_js_1 = require("@grpc/grpc-js");
const metrics_tracer_factory_1 = require("./metrics-tracer-factory");
const constants_1 = require("./constants");
const __1 = require("..");
/**
* MetricAttemptTracer tracks the start time and status of a single gRPC attempt.
*
* This class is used to record the timestamp when an attempt begins and to store
* the status code of the attempt upon completion. It is to be used
* by MetricsTracer to monitor and report metrics for each individual gRPC call attempt.
*/
class MetricAttemptTracer {
_startTime;
status;
constructor() {
this._startTime = new Date(Date.now());
this.status = grpc_js_1.status[grpc_js_1.status.UNKNOWN];
}
/**
* Returns the start time of the attempt.
*/
get startTime() {
return this._startTime;
}
}
/**
* MetricOperationTracer tracks the lifecycle and metadata of a single gRPC spanner operation,
* which may consist of multiple attempts.
*
* This class is responsible for:
* - Recording the start time of the operation.
* - Tracking the number of attempts made for the operation.
* - Holding a reference to the current attempt's tracer (MetricAttemptTracer).
* - Storing the final status code of the operation.
*
* Usage:
* - Call `start()` to reset the operation's start time.
* - Call `createNewAttempt()` to begin tracking a new attempt within the operation.
* - Access `currentAttempt` to retrieve the current MetricAttemptTracer instance.
* - Access `attemptCount` to get the number of attempts made so far.
* - Access `startTime` to get the operation's start time.
* - Set or read `status` to track the operation's final status code.
*/
class MetricOperationTracer {
_attemptCount;
_startTime;
_currentAttempt;
constructor() {
this._attemptCount = 0;
this._startTime = new Date(Date.now());
this._currentAttempt = null;
}
/**
* Returns the number of attempts made for this operation.
*/
get attemptCount() {
return this._attemptCount;
}
/**
* Returns the current MetricAttemptTracer instance for the ongoing attempt.
*/
get currentAttempt() {
return this._currentAttempt;
}
/**
* Returns the start time of the operation.
*/
get startTime() {
return this._startTime;
}
/**
* Increments the attempt count and creates a new MetricAttemptTracer
* for tracking the next attempt within this operation.
*/
createNewAttempt() {
this._attemptCount += 1;
this._currentAttempt = new MetricAttemptTracer();
}
}
/**
* MetricsTracer is responsible for recording and managing metrics related to
* gRPC Spanner operations and attempts counters, and latencies,
* as well as Google Front End (GFE)/AFE metrics such as latency and connectivity errors.
*
* This class provides methods to record the start and completion of operations
* and attempts, extract GFE/AFE latency from response headers.
* It also handles setting of required Spanner metric attributes to
* be later consumed by the SpannerMetricsExporter.
*/
class MetricsTracer {
_instrumentAttemptCounter;
_instrumentAttemptLatency;
_instrumentOperationCounter;
_instrumentOperationLatency;
_instrumentGfeConnectivityErrorCount;
_instrumentGfeLatency;
_instrumentAfeConnectivityErrorCount;
_instrumentAfeLatency;
enabled;
_database;
_instance;
_projectId;
_methodName;
_request;
/**
* The current MetricOperationTracer instance tracking the ongoing operation.
*/
currentOperation = null;
/**
* Stores client and resource attributes for labeling metrics.
*/
_clientAttributes = {};
/*
* The current GFE latency associated with this tracer.
*/
gfeLatency = null;
/*
* The current AFE latency associated with this tracer.
*/
afeLatency = null;
/**
* Constructs a new MetricsTracer.
*
* @param _instrumentAttemptCounter Counter for attempt count metrics.
* @param _instrumentAttemptLatency Histogram for attempt latency metrics.
* @param _instrumentOperationCounter Counter for operation count metrics.
* @param _instrumentOperationLatency Histogram for operation latency metrics.
* @param _instrumentGfeConnectivityErrorCount Counter for GFE connectivity errors.
* @param _instrumentGfeLatency Histogram for GFE latency metrics.
* @param _instrumentAfeConnectivityErrorCount Counter for AFE connectivity errors.
* @param _instrumentAfeLatency Histogram for AFE latency metrics.
* @param enabled Whether metrics recording is enabled.
*/
constructor(_instrumentAttemptCounter, _instrumentAttemptLatency, _instrumentOperationCounter, _instrumentOperationLatency, _instrumentGfeConnectivityErrorCount, _instrumentGfeLatency, _instrumentAfeConnectivityErrorCount, _instrumentAfeLatency, enabled, _database, _instance, _projectId, _methodName, _request) {
this._instrumentAttemptCounter = _instrumentAttemptCounter;
this._instrumentAttemptLatency = _instrumentAttemptLatency;
this._instrumentOperationCounter = _instrumentOperationCounter;
this._instrumentOperationLatency = _instrumentOperationLatency;
this._instrumentGfeConnectivityErrorCount = _instrumentGfeConnectivityErrorCount;
this._instrumentGfeLatency = _instrumentGfeLatency;
this._instrumentAfeConnectivityErrorCount = _instrumentAfeConnectivityErrorCount;
this._instrumentAfeLatency = _instrumentAfeLatency;
this.enabled = enabled;
this._database = _database;
this._instance = _instance;
this._projectId = _projectId;
this._methodName = _methodName;
this._request = _request;
this._clientAttributes[constants_1.METRIC_LABEL_KEY_DATABASE] = _database;
this._clientAttributes[constants_1.METRIC_LABEL_KEY_METHOD] = _methodName;
this._clientAttributes[constants_1.MONITORED_RES_LABEL_KEY_INSTANCE] = _instance;
}
/**
* Returns the difference in milliseconds between two Date objects.
* @param start The start time.
* @param end The end time.
* @returns The time difference in milliseconds.
*/
_getMillisecondTimeDifference(start, end) {
return end.valueOf() - start.valueOf();
}
/**
* Gets the current client and resource attributes for metrics.
*/
get clientAttributes() {
return this._clientAttributes;
}
/**
* Gets the attempt counter OTEL instrument.
*/
get instrumentAttemptCounter() {
return this._instrumentAttemptCounter;
}
/**
* Gets the attempt latency histogram OTEL instrument.
*/
get instrumentAttemptLatency() {
return this._instrumentAttemptLatency;
}
/**
* Gets the operation counter OTEL instrument.
*/
get instrumentOperationCounter() {
return this._instrumentOperationCounter;
}
/**
* Gets the operation latency histogram OTEL instrument.
*/
get instrumentOperationLatency() {
return this._instrumentOperationLatency;
}
/**
* Records the start of a new attempt within the current operation.
* Increments the attempt count and creates a new MetricAttemptTracer.
*/
recordAttemptStart() {
if (!this.enabled)
return;
this.currentOperation.createNewAttempt();
}
/**
* Records the completion of the current attempt, including its status and latency.
* These statuses code are defined in grpc.status
* @param status The status code of the attempt (default: Status.OK).
*/
recordAttemptCompletion(statusCode = grpc_js_1.status.OK) {
if (!this.enabled)
return;
this.currentOperation.currentAttempt.status = grpc_js_1.status[statusCode];
const attemptAttributes = this._createAttemptOtelAttributes();
const endTime = new Date(Date.now());
const attemptLatencyMilliseconds = this._getMillisecondTimeDifference(this.currentOperation.currentAttempt.startTime, endTime);
this.instrumentAttemptLatency?.record(attemptLatencyMilliseconds, attemptAttributes);
this.instrumentAttemptCounter?.add(1, attemptAttributes);
}
/**
* Records the start of a new operation, resetting the operation tracer and start time.
*/
recordOperationStart() {
if (!this.enabled)
return;
if (this.currentOperation !== null) {
return; // Don't re-start an already started operation
}
this.currentOperation = new MetricOperationTracer();
}
/**
* Records the completion of the current operation, including its status,
* latency, and attempt count. Also clears the current tracer from the factory.
*/
recordOperationCompletion() {
if (!this.enabled || !this.currentOperation)
return;
const endTime = new Date(Date.now());
const operationAttributes = this._createOperationOtelAttributes();
const operationLatencyMilliseconds = this._getMillisecondTimeDifference(this.currentOperation.startTime, endTime);
this.instrumentOperationCounter?.add(1, operationAttributes);
this.instrumentOperationLatency?.record(operationLatencyMilliseconds, operationAttributes);
metrics_tracer_factory_1.MetricsTracerFactory.getInstance(this._projectId).clearCurrentTracer(this._request);
}
/**
* Extracts the GFE latency value (in milliseconds) from a 'server-timing' header string.
* Returns null if the header is missing or does not contain a valid latency value.
*
* @param header The 'server-timing' header string.
* @returns The extracted GFE latency in milliseconds, or null if not found.
*/
extractGfeLatency(header) {
const regex = /gfet4t7; dur=([0-9]+).*/;
if (header === undefined)
return null;
const match = header.match(regex);
if (!match)
return null;
return Number(match[1]);
}
/**
* Extracts the AFE latency value (in milliseconds) from a 'server-timing' header string.
* Returns null if the header is missing or does not contain a valid latency value.
*
* @param header The 'server-timing' header string.
* @returns The extracted AFE latency in milliseconds, or null if not found.
*/
extractAfeLatency(header) {
if (!__1.Spanner.isAFEServerTimingEnabled())
return null;
const regex = /afe; dur=([0-9]+).*/;
if (header === undefined)
return null;
const match = header.match(regex);
if (!match)
return null;
return Number(match[1]);
}
/**
* Records the provided GFE latency.
* @param latency The GFE latency in milliseconds.
*/
recordGfeLatency(statusCode) {
if (!this.enabled)
return;
if (!this.gfeLatency) {
console.error('ERROR: Attempted to record GFE metric with no latency value.');
return;
}
const attributes = { ...this._clientAttributes };
attributes[constants_1.METRIC_LABEL_KEY_STATUS] = grpc_js_1.status[statusCode];
this._instrumentGfeLatency?.record(this.gfeLatency, attributes);
this.gfeLatency = null; // Reset latency value
}
/**
* Increments the GFE connectivity error count metric.
*/
recordGfeConnectivityErrorCount(statusCode) {
if (!this.enabled)
return;
const attributes = { ...this._clientAttributes };
attributes[constants_1.METRIC_LABEL_KEY_STATUS] = grpc_js_1.status[statusCode];
this._instrumentGfeConnectivityErrorCount?.add(1, attributes);
}
/**
* Increments the AFE connectivity error count metric.
*/
recordAfeConnectivityErrorCount(statusCode) {
if (!this.enabled || !__1.Spanner.isAFEServerTimingEnabled())
return;
const attributes = { ...this._clientAttributes };
attributes[constants_1.METRIC_LABEL_KEY_STATUS] = grpc_js_1.status[statusCode];
this._instrumentAfeConnectivityErrorCount?.add(1, attributes);
}
/**
* Records the provided AFE latency.
* @param latency The AFE latency in milliseconds.
*/
recordAfeLatency(statusCode) {
if (!this.enabled || !__1.Spanner.isAFEServerTimingEnabled())
return;
if (!this.afeLatency) {
console.error('ERROR: Attempted to record AFE metric with no latency value.');
return;
}
const attributes = { ...this._clientAttributes };
attributes[constants_1.METRIC_LABEL_KEY_STATUS] = grpc_js_1.status[statusCode];
this._instrumentAfeLatency?.record(this.afeLatency, attributes);
this.afeLatency = null; // Reset latency value
}
/**
* Creates and returns a set of OTEL attributes for operation-level metrics.
* @returns The operation attributes object.
*/
_createOperationOtelAttributes() {
if (!this.enabled)
return {};
const attributes = { ...this._clientAttributes };
attributes[constants_1.METRIC_LABEL_KEY_STATUS] =
this.currentOperation.currentAttempt?.status ?? grpc_js_1.status[grpc_js_1.status.UNKNOWN];
return attributes;
}
/**
* Creates and returns a set of OTEL attributes for attempt-level metrics.
* The overall operation status is set at this time based on the last
* attempt's status.
* @returns The attempt attributes object.
*/
_createAttemptOtelAttributes() {
if (!this.enabled)
return {};
const attributes = { ...this._clientAttributes };
if (this.currentOperation?.currentAttempt === null)
return attributes;
attributes[constants_1.METRIC_LABEL_KEY_STATUS] =
this.currentOperation.currentAttempt.status;
return attributes;
}
}
exports.MetricsTracer = MetricsTracer;
//# sourceMappingURL=metrics-tracer.js.map