@google-cloud/spanner
Version:
Cloud Spanner Client Library for Node.js
119 lines • 5.25 kB
JavaScript
;
// 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
//
// 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.CloudMonitoringMetricsExporter = exports.MAX_BATCH_EXPORT_SIZE = void 0;
const core_1 = require("@opentelemetry/core");
const monitoring_1 = require("@google-cloud/monitoring");
const transform_1 = require("./transform");
const grpc_js_1 = require("@grpc/grpc-js");
const constants_1 = require("./constants");
// Stackdriver Monitoring v3 only accepts up to 200 TimeSeries per
// CreateTimeSeries call.
exports.MAX_BATCH_EXPORT_SIZE = 200;
/**
* Format and sends metrics information to Google Cloud Monitoring.
*/
class CloudMonitoringMetricsExporter {
_projectId;
_lastExported = new Date(0);
_client;
constructor({ auth }) {
this._client = new monitoring_1.MetricServiceClient({ auth: auth });
// Start this async process as early as possible. It will be
// awaited on the first export because constructors are synchronous
this._projectId = auth.getProjectId().catch(err => {
console.error(err);
});
}
/**
* Implementation for {@link PushMetricExporter.export}.
* Calls the async wrapper method {@link _exportAsync} and
* assures no rejected promises bubble up to the caller.
*
* @param metrics Metrics to be sent to the Google Cloud 3Monitoring backend
* @param resultCallback result callback to be called on finish
*/
export(metrics, resultCallback) {
// Do not export metrics if we've already exported within the last 30s
const now = new Date();
if (now.getTime() - this._lastExported.getTime() <=
constants_1.MIN_EXPORT_FREQUENCY_MS) {
return;
}
this._lastExported = now;
this._exportAsync(metrics).then(resultCallback, err => {
console.error(err.message);
resultCallback({ code: core_1.ExportResultCode.FAILED, error: err });
});
}
async shutdown() { }
async forceFlush() { }
/**
* Asnyc wrapper for the {@link export} implementation.
* Writes the current values of all exported {@link MetricRecord}s
* to the Google Cloud Monitoring backend.
*
* @param resourceMetrics Metrics to be sent to the Google Cloud Monitoring backend
*/
async _exportAsync(resourceMetrics) {
if (this._projectId instanceof Promise) {
this._projectId = await this._projectId;
}
if (!this._projectId) {
const error = new Error('expecting a non-blank ProjectID');
console.error(error.message);
return { code: core_1.ExportResultCode.FAILED, error };
}
const timeSeriesList = (0, transform_1.transformResourceMetricToTimeSeriesArray)(resourceMetrics);
let failure = {
sendFailed: false,
};
await Promise.all(this._partitionList(timeSeriesList, exports.MAX_BATCH_EXPORT_SIZE).map(async (batchedTimeSeries) => this._sendTimeSeries(batchedTimeSeries))).catch(e => {
const error = e;
if (error.code === grpc_js_1.status.PERMISSION_DENIED) {
console.warn(`Need monitoring metric writer permission on project ${this._projectId}. Follow https://cloud.google.com/spanner/docs/view-manage-client-side-metrics#access-client-side-metrics to set up permissions`);
}
const err = asError(e);
err.message = `Send TimeSeries failed: ${err.message}`;
failure = { sendFailed: true, error: err };
console.error(`ERROR: ${err.message}`);
});
return failure.sendFailed
? {
code: core_1.ExportResultCode.FAILED,
error: failure.error,
}
: { code: core_1.ExportResultCode.SUCCESS };
}
async _sendTimeSeries(timeSeries) {
if (timeSeries.length === 0) {
return Promise.resolve();
}
// TODO: Use createServiceTimeSeries when it is available
await this._client.createTimeSeries({
name: `projects/${this._projectId}`,
timeSeries: timeSeries,
});
}
/** Returns the minimum number of arrays of max size chunkSize, partitioned from the given array. */
_partitionList(list, chunkSize) {
return Array.from({ length: Math.ceil(list.length / chunkSize) }, (_, i) => list.slice(i * chunkSize, (i + 1) * chunkSize));
}
}
exports.CloudMonitoringMetricsExporter = CloudMonitoringMetricsExporter;
function asError(error) {
return error instanceof Error ? error : new Error(String(error));
}
//# sourceMappingURL=spanner-metrics-exporter.js.map