diff --git a/src/globals.js b/src/globals.js index 19afd3e..1980a52 100755 --- a/src/globals.js +++ b/src/globals.js @@ -701,6 +701,19 @@ Configuration File: this.logger.info( `CONFIG: Influxdb retention policy duration: ${this.config.get('Butler-SOS.influxdbConfig.v2Config.retentionDuration')}` ); + } else if (this.config.get('Butler-SOS.influxdbConfig.version') === 3) { + this.logger.info( + `CONFIG: Influxdb organisation: ${this.config.get('Butler-SOS.influxdbConfig.v3Config.org')}` + ); + this.logger.info( + `CONFIG: Influxdb database: ${this.config.get('Butler-SOS.influxdbConfig.v3Config.database')}` + ); + this.logger.info( + `CONFIG: Influxdb bucket name: ${this.config.get('Butler-SOS.influxdbConfig.v3Config.bucket')}` + ); + this.logger.info( + `CONFIG: Influxdb retention policy duration: ${this.config.get('Butler-SOS.influxdbConfig.v3Config.retentionDuration')}` + ); } else { this.logger.error( `CONFIG: Influxdb version ${this.config.get('Butler-SOS.influxdbConfig.version')} is not supported!` @@ -863,13 +876,28 @@ Configuration File: const token = this.config.get('Butler-SOS.influxdbConfig.v2Config.token'); try { - this.influx = new InfluxDB2({ url, token }); + this.influx = new InfluxDB({ url, token }); } catch (err) { this.logger.error( `INFLUXDB2 INIT: Error creating InfluxDB 2 client: ${this.getErrorMessage(err)}` ); this.logger.error(`INFLUXDB2 INIT: Exiting.`); } + } else if (this.config.get('Butler-SOS.influxdbConfig.version') === 3) { + // Set up Influxdb v3 client (uses same client library as v2) + const url = `http://${this.config.get('Butler-SOS.influxdbConfig.host')}:${this.config.get( + 'Butler-SOS.influxdbConfig.port' + )}`; + const token = this.config.get('Butler-SOS.influxdbConfig.v3Config.token'); + + try { + this.influx = new InfluxDB({ url, token }); + } catch (err) { + this.logger.error( + `INFLUXDB3 INIT: Error creating InfluxDB 3 client: ${this.getErrorMessage(err)}` + ); + this.logger.error(`INFLUXDB3 INIT: Exiting.`); + } } else { this.logger.error( `CONFIG: Influxdb version ${this.config.get('Butler-SOS.influxdbConfig.version')} is not supported!` @@ -1114,6 +1142,78 @@ Configuration File: } }); } + } else if (this.config.get('Butler-SOS.influxdbConfig.version') === 3) { + // Get config + const org = this.config.get('Butler-SOS.influxdbConfig.v3Config.org'); + const database = this.config.get('Butler-SOS.influxdbConfig.v3Config.database'); + const bucketName = this.config.get('Butler-SOS.influxdbConfig.v3Config.bucket'); + const description = this.config.get('Butler-SOS.influxdbConfig.v3Config.description'); + const token = this.config.get('Butler-SOS.influxdbConfig.v3Config.token'); + const retentionDuration = this.config.get( + 'Butler-SOS.influxdbConfig.v3Config.retentionDuration' + ); + + if ( + this.influx && + this.config.get('Butler-SOS.influxdbConfig.enable') === true && + org?.length > 0 && + database?.length > 0 && + bucketName?.length > 0 && + token?.length > 0 && + retentionDuration?.length > 0 + ) { + enableInfluxdb = true; + } + + if (enableInfluxdb) { + // For InfluxDB v3, we use the database directly + this.logger.info( + `INFLUXDB3: Using organization "${org}" with database "${database}"` + ); + + // Create array of per-server writeAPI objects for v3 + // Each object has two properties: host and writeAPI, where host can be used as key later on + this.serverList.forEach((server) => { + // Get per-server tags + const tags = getServerTags(this.logger, server); + + // advanced write options for InfluxDB v3 + const writeOptions = { + /* default tags to add to every point */ + defaultTags: tags, + + /* maximum time in millis to keep points in an unflushed batch, 0 means don't periodically flush */ + flushInterval: 5000, + + /* the count of internally-scheduled retries upon write failure, the delays between write attempts follow an exponential backoff strategy if there is no Retry-After HTTP header */ + maxRetries: 2, // do not retry writes + + // ... there are more write options that can be customized, see + // https://influxdata.github.io/influxdb-client-js/influxdb-client.writeoptions.html and + // https://influxdata.github.io/influxdb-client-js/influxdb-client.writeretryoptions.html + }; + + try { + // For InfluxDB v3, we use database instead of bucket + const serverWriteApi = this.influx.getWriteApi( + org, + database, + 'ns', + writeOptions + ); + + // Save to global variable, using serverName as key + this.influxWriteApi.push({ + serverName: server.serverName, + writeAPI: serverWriteApi, + }); + } catch (err) { + this.logger.error( + `INFLUXDB3: Error getting write API: ${this.getErrorMessage(err)}` + ); + } + }); + } } } diff --git a/src/lib/config-schemas/destinations.js b/src/lib/config-schemas/destinations.js index e3a1141..74951e5 100644 --- a/src/lib/config-schemas/destinations.js +++ b/src/lib/config-schemas/destinations.js @@ -316,6 +316,19 @@ export const destinationsSchema = { }, port: { type: 'number' }, version: { type: 'number' }, + v3Config: { + type: 'object', + properties: { + org: { type: 'string' }, + bucket: { type: 'string' }, + database: { type: 'string' }, + description: { type: 'string' }, + token: { type: 'string' }, + retentionDuration: { type: 'string' }, + }, + required: ['org', 'bucket', 'database', 'description', 'token', 'retentionDuration'], + additionalProperties: false, + }, v2Config: { type: 'object', properties: { diff --git a/src/lib/post-to-influxdb.js b/src/lib/post-to-influxdb.js index 5282860..4d52b9e 100755 --- a/src/lib/post-to-influxdb.js +++ b/src/lib/post-to-influxdb.js @@ -546,6 +546,113 @@ export async function postHealthMetricsToInfluxdb(serverName, host, body, server `HEALTH METRICS: Error saving health data to InfluxDB v2! ${globals.getErrorMessage(err)}` ); } + } else if (globals.config.get('Butler-SOS.influxdbConfig.version') === 3) { + // Only write to InfluxDB if the global influxWriteApi object has been initialized + if (!globals.influxWriteApi) { + globals.logger.warn( + 'HEALTH METRICS: Influxdb write API object not initialized. Data will not be sent to InfluxDB' + ); + return; + } + + // Find writeApi for the server specified by serverName + const writeApi = globals.influxWriteApi.find( + (element) => element.serverName === serverName + ); + + // Ensure that the writeApi object was found + if (!writeApi) { + globals.logger.warn( + `HEALTH METRICS: Influxdb write API object not found for host ${host}. Data will not be sent to InfluxDB` + ); + return; + } + + // Create a new point with the data to be written to InfluxDB v3 + const points = [ + new Point('sense_server') + .stringField('version', body.version) + .stringField('started', body.started) + .stringField('uptime', formattedTime), + + new Point('mem') + .floatField('comitted', body.mem.committed) + .floatField('allocated', body.mem.allocated) + .floatField('free', body.mem.free), + + new Point('apps') + .intField('active_docs_count', body.apps.active_docs.length) + .intField('loaded_docs_count', body.apps.loaded_docs.length) + .intField('in_memory_docs_count', body.apps.in_memory_docs.length) + .stringField( + 'active_docs', + globals.config.get('Butler-SOS.influxdbConfig.includeFields.activeDocs') + ? body.apps.active_docs + : '' + ) + .stringField( + 'active_docs_names', + globals.config.get('Butler-SOS.appNames.enableAppNameExtract') && + globals.config.get('Butler-SOS.influxdbConfig.includeFields.activeDocs') + ? activeSessionDocNames + : '' + ) + .stringField( + 'loaded_docs', + globals.config.get('Butler-SOS.influxdbConfig.includeFields.loadedDocs') + ? body.apps.loaded_docs + : '' + ) + .stringField( + 'loaded_docs_names', + globals.config.get('Butler-SOS.appNames.enableAppNameExtract') && + globals.config.get('Butler-SOS.influxdbConfig.includeFields.loadedDocs') + ? loadedSessionDocNames + : '' + ) + .stringField( + 'in_memory_docs', + globals.config.get('Butler-SOS.influxdbConfig.includeFields.inMemoryDocs') + ? body.apps.in_memory_docs + : '' + ) + .stringField( + 'in_memory_docs_names', + globals.config.get('Butler-SOS.appNames.enableAppNameExtract') && + globals.config.get('Butler-SOS.influxdbConfig.includeFields.inMemoryDocs') + ? inMemorySessionDocNames + : '' + ) + .intField('calls', body.apps.calls) + .intField('selections', body.apps.selections), + + new Point('cpu').intField('total', body.cpu.total), + + new Point('session') + .intField('active', body.session.active) + .intField('total', body.session.total), + + new Point('users') + .intField('active', body.users.active) + .intField('total', body.users.total), + + new Point('cache') + .intField('hits', body.cache.hits) + .intField('lookups', body.cache.lookups) + .intField('added', body.cache.added) + .intField('replaced', body.cache.replaced) + .intField('bytes_added', body.cache.bytes_added), + ]; + + // Write to InfluxDB + try { + const res = await writeApi.writeAPI.writePoints(points); + globals.logger.debug(`HEALTH METRICS: Wrote data to InfluxDB v3`); + } catch (err) { + globals.logger.error( + `HEALTH METRICS: Error saving health data to InfluxDB v3! ${globals.getErrorMessage(err)}` + ); + } } }