Node JS version
This commit is contained in:
492
node_modules/cassandra-driver/lib/insights-client.js
generated
vendored
Normal file
492
node_modules/cassandra-driver/lib/insights-client.js
generated
vendored
Normal file
@@ -0,0 +1,492 @@
|
||||
/*
|
||||
* Copyright DataStax, Inc.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const utils = require('./utils');
|
||||
const promiseUtils = require('./promise-utils');
|
||||
const types = require('./types');
|
||||
const requests = require('./requests');
|
||||
const { ExecutionOptions } = require('./execution-options');
|
||||
const packageInfo = require('../package.json');
|
||||
const VersionNumber = require('./types/version-number');
|
||||
const { NoAuthProvider } = require('./auth');
|
||||
|
||||
let kerberosModule;
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line
|
||||
kerberosModule = require('kerberos');
|
||||
}
|
||||
catch (err) {
|
||||
// Kerberos is an optional dependency
|
||||
}
|
||||
|
||||
const minDse6Version = new VersionNumber(6, 0, 5);
|
||||
const minDse51Version = new VersionNumber(5, 1, 13);
|
||||
const dse600Version = new VersionNumber(6, 0, 0);
|
||||
const rpc = "CALL InsightsRpc.reportInsight(?)";
|
||||
const maxStatusErrorLogs = 5;
|
||||
|
||||
/**
|
||||
* Contains methods and functionality to send events to DSE Insights.
|
||||
*/
|
||||
class InsightsClient {
|
||||
|
||||
/**
|
||||
* Creates a new instance of the {@link InsightsClient} using the driver {@link Client}.
|
||||
* @param {Client} client
|
||||
* @param {Object} [options]
|
||||
* @param {Number} [options.statusEventDelay]
|
||||
* @param {Function} [options.errorCallback]
|
||||
*/
|
||||
constructor(client, options) {
|
||||
this._client = client;
|
||||
this._sessionId = types.Uuid.random().toString();
|
||||
this._enabled = false;
|
||||
this._closed = false;
|
||||
this._firstTimeout = null;
|
||||
this._recurrentTimeout = null;
|
||||
this._statusErrorLogs = 0;
|
||||
|
||||
options = options || {};
|
||||
|
||||
this._statusEventDelay = options.statusEventDelay || 300000;
|
||||
this._errorCallback = options.errorCallback || utils.noop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the insights client in the background by sending the startup event and scheduling status events at
|
||||
* regular intervals.
|
||||
* @returns {undefined}
|
||||
*/
|
||||
init() {
|
||||
this._enabled = this._client.options.monitorReporting.enabled && this._dseSupportsInsights();
|
||||
if (!this._enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
promiseUtils.toBackground(this._init());
|
||||
}
|
||||
|
||||
async _init() {
|
||||
try {
|
||||
await this._sendStartupEvent();
|
||||
|
||||
if (this._closed) {
|
||||
// The client was shutdown
|
||||
return;
|
||||
}
|
||||
|
||||
// Send the status event the first time with a delay containing some random portion
|
||||
// Initial delay should be statusEventDelay - (0 to 10%)
|
||||
const firstDelay = Math.floor(this._statusEventDelay - 0.1 * this._statusEventDelay * Math.random());
|
||||
// Schedule the first timer
|
||||
this._firstTimeout = setTimeout(() => {
|
||||
// Send the first status event, the promise will never be rejected
|
||||
this._sendStatusEvent();
|
||||
// The following status events are sent at regular intervals
|
||||
this._recurrentTimeout = setInterval(() => this._sendStatusEvent(), this._statusEventDelay);
|
||||
}, firstDelay);
|
||||
} catch (err) {
|
||||
if (this._closed) {
|
||||
// Sending failed because the Client was shutdown
|
||||
return;
|
||||
}
|
||||
// We shouldn't try to recover
|
||||
this._client.log('verbose', `Insights startup message could not be sent (${err})`, err);
|
||||
this._errorCallback(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the startup event.
|
||||
* @returns {Promise}
|
||||
* @private
|
||||
*/
|
||||
async _sendStartupEvent() {
|
||||
const message = await this._getStartupMessage();
|
||||
const request = new requests.QueryRequest(rpc, [message], ExecutionOptions.empty());
|
||||
await this._client.controlConnection.query(request, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the status event.
|
||||
* @returns {Promise} A promise that is never rejected.
|
||||
* @private
|
||||
*/
|
||||
async _sendStatusEvent() {
|
||||
const request = new requests.QueryRequest(rpc, [ this._getStatusEvent() ], ExecutionOptions.empty());
|
||||
|
||||
try {
|
||||
await this._client.controlConnection.query(request, false);
|
||||
} catch (err) {
|
||||
if (this._closed) {
|
||||
// Sending failed because the Client was shutdown
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._statusErrorLogs < maxStatusErrorLogs) {
|
||||
this._client.log('warning', `Insights status message could not be sent (${err})`, err);
|
||||
this._statusErrorLogs++;
|
||||
}
|
||||
|
||||
this._errorCallback(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the minimum server version for all nodes in the cluster.
|
||||
* @private
|
||||
*/
|
||||
_dseSupportsInsights() {
|
||||
if (this._client.hosts.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this._client.hosts.values().reduce((acc, host) => {
|
||||
if (!acc) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
const versionArr = host.getDseVersion();
|
||||
|
||||
if (versionArr.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const version = new VersionNumber(...versionArr);
|
||||
|
||||
return version.compare(minDse6Version) >= 0 ||
|
||||
(version.compare(dse600Version) < 0 && version.compare(minDse51Version) >= 0);
|
||||
|
||||
}, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<String>} Returns a json string with the startup message.
|
||||
* @private
|
||||
*/
|
||||
async _getStartupMessage() {
|
||||
const cc = this._client.controlConnection;
|
||||
const options = this._client.options;
|
||||
|
||||
|
||||
const appInfo = await this._getAppInfo(options);
|
||||
const message = {
|
||||
metadata: {
|
||||
name: 'driver.startup',
|
||||
insightMappingId: 'v1',
|
||||
insightType: 'EVENT',
|
||||
timestamp: Date.now(),
|
||||
tags: { language: 'nodejs' }
|
||||
},
|
||||
data: {
|
||||
driverName: packageInfo.description,
|
||||
driverVersion: packageInfo.version,
|
||||
clientId: options.id,
|
||||
sessionId: this._sessionId,
|
||||
applicationName: appInfo.applicationName,
|
||||
applicationVersion: appInfo.applicationVersion,
|
||||
applicationNameWasGenerated: appInfo.applicationNameWasGenerated,
|
||||
contactPoints: mapToObject(cc.getResolvedContactPoints()),
|
||||
dataCenters: this._getDataCenters(),
|
||||
initialControlConnection: cc.host ? cc.host.address : undefined,
|
||||
protocolVersion: cc.protocolVersion,
|
||||
localAddress: cc.getLocalAddress(),
|
||||
hostName: os.hostname(),
|
||||
executionProfiles: getExecutionProfiles(this._client),
|
||||
poolSizeByHostDistance: {
|
||||
local: options.pooling.coreConnectionsPerHost[types.distance.local],
|
||||
remote: options.pooling.coreConnectionsPerHost[types.distance.remote]
|
||||
},
|
||||
heartbeatInterval: options.pooling.heartBeatInterval,
|
||||
compression: 'NONE',
|
||||
reconnectionPolicy: getPolicyInfo(options.policies.reconnection),
|
||||
ssl: {
|
||||
enabled: !!options.sslOptions,
|
||||
certValidation: options.sslOptions ? !!options.sslOptions.rejectUnauthorized : undefined
|
||||
},
|
||||
authProvider: {
|
||||
type: !(options.authProvider instanceof NoAuthProvider) ? getConstructor(options.authProvider) : undefined,
|
||||
},
|
||||
otherOptions: {
|
||||
coalescingThreshold: options.socketOptions.coalescingThreshold,
|
||||
},
|
||||
platformInfo: {
|
||||
os: {
|
||||
name: os.platform(),
|
||||
version: os.release(),
|
||||
arch: os.arch()
|
||||
},
|
||||
cpus: {
|
||||
length: os.cpus().length,
|
||||
model: os.cpus()[0].model
|
||||
},
|
||||
runtime: {
|
||||
node: process.versions['node'],
|
||||
v8: process.versions['v8'],
|
||||
uv: process.versions['uv'],
|
||||
openssl: process.versions['openssl'],
|
||||
kerberos: kerberosModule ? kerberosModule.version : undefined
|
||||
}
|
||||
},
|
||||
configAntiPatterns: this._getConfigAntiPatterns(),
|
||||
periodicStatusInterval: Math.floor(this._statusEventDelay / 1000)
|
||||
}
|
||||
};
|
||||
|
||||
return JSON.stringify(message);
|
||||
}
|
||||
|
||||
_getConfigAntiPatterns() {
|
||||
const options = this._client.options;
|
||||
const result = {};
|
||||
|
||||
if (options.sslOptions && !options.sslOptions.rejectUnauthorized) {
|
||||
result.sslWithoutCertValidation =
|
||||
'Client-to-node encryption is enabled but server certificate validation is disabled';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of data centers the driver connects to.
|
||||
* Whether the driver connects to a certain host is determined by the host distance (local and remote hosts)
|
||||
* and the pooling options (whether connection length for remote hosts is greater than 0).
|
||||
* @returns {Array}
|
||||
* @private
|
||||
*/
|
||||
_getDataCenters() {
|
||||
const remoteConnectionsLength = this._client.options.pooling.coreConnectionsPerHost[types.distance.remote];
|
||||
const dataCenters = new Set();
|
||||
|
||||
this._client.hosts.values().forEach(h => {
|
||||
const distance = this._client.profileManager.getDistance(h);
|
||||
if (distance === types.distance.local || (distance === types.distance.remote && remoteConnectionsLength > 0)) {
|
||||
dataCenters.add(h.datacenter);
|
||||
}
|
||||
});
|
||||
|
||||
return Array.from(dataCenters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to obtain the application name and version from
|
||||
* @param {DseClientOptions} options
|
||||
* @returns {Promise}
|
||||
* @private
|
||||
*/
|
||||
async _getAppInfo(options) {
|
||||
if (typeof options.applicationName === 'string') {
|
||||
return Promise.resolve({
|
||||
applicationName: options.applicationName,
|
||||
applicationVersion: options.applicationVersion,
|
||||
applicationNameWasGenerated: false
|
||||
});
|
||||
}
|
||||
|
||||
let readPromise = Promise.resolve();
|
||||
|
||||
if (require.main && require.main.filename) {
|
||||
const packageInfoPath = path.dirname(require.main.filename);
|
||||
readPromise = this._readPackageInfoFile(packageInfoPath);
|
||||
}
|
||||
|
||||
const text = await readPromise;
|
||||
let applicationName = 'Default Node.js Application';
|
||||
let applicationVersion;
|
||||
|
||||
if (text) {
|
||||
try {
|
||||
const packageInfo = JSON.parse(text);
|
||||
if (packageInfo.name) {
|
||||
applicationName = packageInfo.name;
|
||||
applicationVersion = packageInfo.version;
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
// The package.json file could not be parsed
|
||||
// Use the default name
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
applicationName,
|
||||
applicationVersion,
|
||||
applicationNameWasGenerated: true
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {Promise<string>} A Promise that will never be rejected
|
||||
*/
|
||||
_readPackageInfoFile(packageInfoPath) {
|
||||
return new Promise(resolve => {
|
||||
fs.readFile(path.join(packageInfoPath, 'package.json'), 'utf8', (err, data) => {
|
||||
// Swallow error
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {String} Returns a json string with the startup message.
|
||||
* @private
|
||||
*/
|
||||
_getStatusEvent() {
|
||||
const cc = this._client.controlConnection;
|
||||
const options = this._client.options;
|
||||
const state = this._client.getState();
|
||||
const connectedNodes = {};
|
||||
|
||||
state.getConnectedHosts().forEach(h => {
|
||||
connectedNodes[h.address] = {
|
||||
connections: state.getOpenConnections(h),
|
||||
inFlightQueries: state.getInFlightQueries(h)
|
||||
};
|
||||
});
|
||||
|
||||
const message = {
|
||||
metadata: {
|
||||
name: 'driver.status',
|
||||
insightMappingId: 'v1',
|
||||
insightType: 'EVENT',
|
||||
timestamp: Date.now(),
|
||||
tags: { language: 'nodejs' }
|
||||
},
|
||||
data: {
|
||||
clientId: options.id,
|
||||
sessionId: this._sessionId,
|
||||
controlConnection: cc.host ? cc.host.address : undefined,
|
||||
connectedNodes
|
||||
}
|
||||
};
|
||||
|
||||
return JSON.stringify(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans any timer used internally and sets the client as closed.
|
||||
*/
|
||||
shutdown() {
|
||||
if (!this._enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._closed = true;
|
||||
|
||||
if (this._firstTimeout !== null) {
|
||||
clearTimeout(this._firstTimeout);
|
||||
}
|
||||
|
||||
if (this._recurrentTimeout !== null) {
|
||||
clearInterval(this._recurrentTimeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = InsightsClient;
|
||||
|
||||
function mapToObject(map) {
|
||||
const result = {};
|
||||
map.forEach((value, key) => result[key] = value);
|
||||
return result;
|
||||
}
|
||||
|
||||
function getPolicyInfo(policy) {
|
||||
if (!policy) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const options = policy.getOptions && policy.getOptions();
|
||||
|
||||
return {
|
||||
type: policy.constructor.name,
|
||||
options: (options instanceof Map) ? mapToObject(options) : utils.emptyObject
|
||||
};
|
||||
}
|
||||
|
||||
function getConsistencyString(c) {
|
||||
if (typeof c !== 'number') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return types.consistencyToString[c];
|
||||
}
|
||||
|
||||
function getConstructor(instance) {
|
||||
return instance ? instance.constructor.name : undefined;
|
||||
}
|
||||
|
||||
function getExecutionProfiles(client) {
|
||||
const executionProfiles = {};
|
||||
|
||||
const defaultProfile = client.profileManager.getDefault();
|
||||
setExecutionProfileProperties(client, executionProfiles, defaultProfile, defaultProfile);
|
||||
|
||||
client.profileManager.getAll()
|
||||
.filter(p => p !== defaultProfile)
|
||||
.forEach(profile => setExecutionProfileProperties(client, executionProfiles, profile, defaultProfile));
|
||||
|
||||
return executionProfiles;
|
||||
}
|
||||
|
||||
function setExecutionProfileProperties(client, parent, profile, defaultProfile) {
|
||||
const output = parent[profile.name] = {};
|
||||
setExecutionProfileItem(output, profile, defaultProfile, 'readTimeout');
|
||||
setExecutionProfileItem(output, profile, defaultProfile, 'loadBalancing', getPolicyInfo);
|
||||
setExecutionProfileItem(output, profile, defaultProfile, 'retry', getPolicyInfo);
|
||||
setExecutionProfileItem(output, profile, defaultProfile, 'consistency', getConsistencyString);
|
||||
setExecutionProfileItem(output, profile, defaultProfile, 'serialConsistency', getConsistencyString);
|
||||
|
||||
if (profile === defaultProfile) {
|
||||
// Speculative execution policy is included in the profiles as some drivers support
|
||||
// different spec exec policy per profile, in this case is fixed for all profiles
|
||||
output.speculativeExecution = getPolicyInfo(client.options.policies.speculativeExecution);
|
||||
}
|
||||
|
||||
if (profile.graphOptions) {
|
||||
output.graphOptions = {};
|
||||
const defaultGraphOptions = defaultProfile.graphOptions || utils.emptyObject;
|
||||
setExecutionProfileItem(output.graphOptions, profile.graphOptions, defaultGraphOptions, 'language');
|
||||
setExecutionProfileItem(output.graphOptions, profile.graphOptions, defaultGraphOptions, 'name');
|
||||
setExecutionProfileItem(output.graphOptions, profile.graphOptions, defaultGraphOptions, 'readConsistency',
|
||||
getConsistencyString);
|
||||
setExecutionProfileItem(output.graphOptions, profile.graphOptions, defaultGraphOptions, 'source');
|
||||
setExecutionProfileItem(output.graphOptions, profile.graphOptions, defaultGraphOptions, 'writeConsistency',
|
||||
getConsistencyString);
|
||||
|
||||
if (Object.keys(output.graphOptions).length === 0) {
|
||||
// Properties that are undefined will not be included in the JSON
|
||||
output.graphOptions = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setExecutionProfileItem(output, profile, defaultProfile, prop, valueGetter) {
|
||||
const value = profile[prop];
|
||||
valueGetter = valueGetter || (x => x);
|
||||
|
||||
if ((profile === defaultProfile && value !== undefined) || value !== defaultProfile[prop]) {
|
||||
output[prop] = valueGetter(value);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user