diff --git a/node_js/env.js b/node_js/env.js new file mode 100644 index 00000000..98411270 --- /dev/null +++ b/node_js/env.js @@ -0,0 +1,35 @@ +/** InfluxDB URL */ +const INFLUX_URL = process.env.INFLUX_URL || 'http://localhost:8086' +/** InfluxDB authorization token */ +const INFLUX_TOKEN = process.env.INFLUX_TOKEN || 'my-token' +/** Organization within InfluxDB */ +const INFLUX_ORG = process.env.INFLUX_ORG || 'my-org' +/** InfluxDB bucket */ +const INFLUX_BUCKET = 'iot_center' + +// Defaults when on boarding a fresh new InfluxDB instance +/** InfluxDB user */ +const onboarding_username = 'my-user' +/** InfluxDB password */ +const onboarding_password = 'my-password' + +/** recommended interval for client's to refresh configuration in seconds */ +const configuration_refresh = 3600 + +function logEnvironment() { + console.log(`INFLUX_URL=${INFLUX_URL}`) + console.log(`INFLUX_TOKEN=${INFLUX_TOKEN ? '***' : ''}`) + console.log(`INFLUX_ORG=${INFLUX_ORG}`) + console.log(`INFLUX_BUCKET=${INFLUX_BUCKET}`) +} + +module.exports = { + INFLUX_URL, + INFLUX_TOKEN, + INFLUX_ORG, + onboarding_username, + onboarding_password, + configuration_refresh, + INFLUX_BUCKET, + logEnvironment, +} diff --git a/node_js/monitor.js b/node_js/monitor.js new file mode 100644 index 00000000..27fe5e34 --- /dev/null +++ b/node_js/monitor.js @@ -0,0 +1,75 @@ +const {InfluxDB, Point} = require('@influxdata/influxdb-client') +const { + INFLUX_URL: url, + INFLUX_TOKEN: token, + INFLUX_ORG: org, + INFLUX_BUCKET: bucket, +} = require('./env') +const responseTime = require('response-time') + +// create Influx Write API to report application monitoring data +const writeAPI = new InfluxDB({url, token}).getWriteApi(org, bucket, 'ns', { + defaultTags: { + service: 'iot_center', + host: require('os').hostname(), + }, +}) +// write node resource/cpu/memory usage +function writeProcessUsage() { + function createPoint(measurement, usage) { + const point = new Point(measurement) + for (const key of Object.keys(usage)) { + point.floatField(key, usage[key]) + } + return point + } + + // https://nodejs.org/api/process.html#process_process_memoryusage + writeAPI.writePoint(createPoint('node_memory_usage', process.memoryUsage())) + // https://nodejs.org/api/process.html#process_process_cpuusage_previousvalue + writeAPI.writePoint(createPoint('node_cpu_usage', process.cpuUsage())) + // https://nodejs.org/api/process.html#process_process_resourceusage + writeAPI.writePoint( + createPoint('node_resource_usage', process.resourceUsage()) + ) +} +// write process usage now and then every 10 seconds +writeProcessUsage() +const nodeUsageTimer = setInterval(writeProcessUsage, 10_000).unref() + +// on shutdown +// - clear reporting of node usage +// - flush unwritten points and cancel retries +async function onShutdown() { + clearInterval(nodeUsageTimer) + try { + await writeAPI.close() + } catch (error) { + console.error('ERROR: Application monitoring', error) + } + // eslint-disable-next-line no-process-exit + process.exit(0) +} +process.on('SIGINT', onShutdown) +process.on('SIGTERM', onShutdown) + +// export a monitoring function for express.js response time monitoring +module.exports = function (app) { + app.use( + responseTime((req, res, time) => { + // print out request basics + console.info( + `${req.method} ${req.path} ${res.statusCode} ${ + Math.round(time * 100) / 100 + }ms` + ) + // write response time to InfluxDB + const point = new Point('express_http_server') + .tag('uri', req.path) + .tag('method', req.method) + .tag('status', String(res.statusCode)) + .floatField('response_time', time) + writeAPI.writePoint(point) + }) + ) +} diff --git a/node_js/node_dashboard.png b/node_js/node_dashboard.png new file mode 100644 index 00000000..ccb31f3a Binary files /dev/null and b/node_js/node_dashboard.png differ diff --git a/node_js/node_js.yml b/node_js/node_js.yml new file mode 100644 index 00000000..eea3375d --- /dev/null +++ b/node_js/node_js.yml @@ -0,0 +1,381 @@ +apiVersion: influxdata.com/v2alpha1 +kind: Label +metadata: + name: burfect-shannon-53e001 +spec: + color: '#34bb55' + name: Node.js +--- +apiVersion: influxdata.com/v2alpha1 +kind: Variable +metadata: + name: naughty-wiles-93e003 +spec: + language: flux + name: Node_Service + query: "import \"influxdata/influxdb/v1\"\r\nv1.tagValues(bucket: \"iot_center\", + tag: \"service\")" + type: query +--- +apiVersion: influxdata.com/v2alpha1 +kind: Variable +metadata: + name: rustic-dubinsky-93e001 +spec: + language: flux + name: Node_Host + query: "import \"influxdata/influxdb/v1\"\r\nv1.tagValues(bucket: \"iot_center\", + tag: \"host\")" + type: query +--- +apiVersion: influxdata.com/v2alpha1 +kind: Dashboard +metadata: + name: brave-moser-13e001 +spec: + charts: + - colors: + - hex: '#00C9FF' + id: base + name: laser + type: text + decimalPlaces: 2 + height: 1 + kind: Single_Stat + name: Average Response Time + queries: + - query: |- + from(bucket: "iot_center") + |> range(start: v.timeRangeStart, stop: v.timeRangeStop) + |> filter(fn: (r) => r["_measurement"] == "express_http_server") + |> filter(fn: (r) => r["_field"] == "response_time") + |> filter(fn: (r) => r["host"] == v.Node_Host) + |> filter(fn: (r) => r["service"] == v.Node_Service) + |> group() + |> median() + suffix: ' ms' + width: 2 + - colors: + - hex: '#00C9FF' + id: base + name: laser + type: text + decimalPlaces: 0 + height: 1 + kind: Single_Stat + name: Maximal Response Time + queries: + - query: |- + from(bucket: "iot_center") + |> range(start: v.timeRangeStart, stop: v.timeRangeStop) + |> filter(fn: (r) => r["_measurement"] == "express_http_server") + |> filter(fn: (r) => r["_field"] == "response_time") + |> filter(fn: (r) => r["host"] == v.Node_Host) + |> filter(fn: (r) => r["service"] == v.Node_Service) + |> group() + |> max() + suffix: ' ms' + width: 2 + yPos: 1 + - colors: + - hex: '#00C9FF' + id: base + name: laser + type: text + decimalPlaces: 2 + height: 1 + kind: Single_Stat + name: Current Heap Usage + queries: + - query: |- + from(bucket: "iot_center") + |> range(start: v.timeRangeStart, stop: v.timeRangeStop) + |> filter(fn: (r) => r["_measurement"] == "node_memory_usage") + |> filter(fn: (r) => r["_field"] == "heapUsed") + |> filter(fn: (r) => r["host"] == v.Node_Host) + |> filter(fn: (r) => r["service"] == v.Node_Service) + |> map(fn: (r) => ({ r with _value: float(v: r._value) / 1000000.0 })) + |> group() + |> last() + suffix: ' MB' + width: 2 + yPos: 2 + - colors: + - hex: '#ffffff' + id: base + name: white + type: background + - hex: '#FFB94A' + id: b0871652-7cd0-4c4b-b9e5-b88160c6cd6a + name: pineapple + type: background + value: 5000 + - hex: '#BF3D5E' + id: 80195bb8-f74e-4b1c-8fef-0ff4af37aa34 + name: ruby + type: background + value: 10000 + decimalPlaces: 0 + fieldOptions: + - displayName: time + fieldName: _time + visible: true + - displayName: _start + fieldName: _start + - displayName: _stop + fieldName: _stop + - displayName: _measurement + fieldName: _measurement + - displayName: host + fieldName: host + - displayName: method + fieldName: method + visible: true + - displayName: uri + fieldName: uri + visible: true + - displayName: service + fieldName: service + - displayName: response time [ms] + fieldName: response_time + visible: true + - displayName: status + fieldName: status + visible: true + height: 4 + kind: Table + name: Response Time & Status + queries: + - query: |- + from(bucket: "iot_center") + |> range(start: v.timeRangeStart, stop: v.timeRangeStop) + |> filter(fn: (r) => r["_measurement"] == "express_http_server") + |> filter(fn: (r) => r["_field"] == "response_time") + |> filter(fn: (r) => r["host"] == v.Node_Host) + |> filter(fn: (r) => r["service"] == v.Node_Service) + |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value") + |> group() + tableOptions: + sortBy: _time + verticalTimeAxis: true + timeFormat: MM/DD/YYYY HH:mm:ss.sss + width: 7 + yPos: 3 + - axes: + - name: x + binCount: 30 + colors: + - hex: '#31C0F6' + id: 7ad731fa-b873-4de2-adcb-b790ef3d1bff + name: Nineteen Eighty Four + type: scale + - hex: '#A500A5' + id: c5ff39d8-4821-408e-8faa-652026bdf23e + name: Nineteen Eighty Four + type: scale + - hex: '#FF7E27' + id: f826ced4-0a79-416d-ba90-f465a36bb42e + name: Nineteen Eighty Four + type: scale + fillColumns: + - _start + - _stop + height: 3 + kind: Histogram + legendColorizeRows: true + legendOpacity: 1 + legendOrientationThreshold: 10 + name: Max Response Time Histogram + position: stacked + queries: + - query: |- + from(bucket: "iot_center") + |> range(start: v.timeRangeStart, stop: v.timeRangeStop) + |> filter(fn: (r) => r["_measurement"] == "express_http_server") + |> filter(fn: (r) => r["_field"] == "response_time") + |> filter(fn: (r) => r["host"] == v.Node_Host) + |> filter(fn: (r) => r["service"] == v.Node_Service) + width: 11 + xCol: _value + yPos: 7 + - axes: + - base: "10" + name: x + scale: linear + - base: "10" + name: y + scale: linear + colors: + - hex: '#31C0F6' + id: 25c22b67-3a3c-4a56-8772-880f04244a7a + name: Nineteen Eighty Four + type: scale + - hex: '#A500A5' + id: 9f64e524-568e-4ca1-ad35-53badeef5592 + name: Nineteen Eighty Four + type: scale + - hex: '#FF7E27' + id: b5a254d5-9e9b-479d-9a2f-988961190e1f + name: Nineteen Eighty Four + type: scale + geom: line + height: 3 + hoverDimension: auto + kind: Xy + legendColorizeRows: true + legendOpacity: 1 + legendOrientationThreshold: 10 + name: Array Buffers + position: overlaid + queries: + - query: |- + from(bucket: "iot_center") + |> range(start: v.timeRangeStart, stop: v.timeRangeStop) + |> filter(fn: (r) => r["_measurement"] == "node_memory_usage") + |> filter(fn: (r) => r["_field"] == "arrayBuffers") + |> filter(fn: (r) => r["host"] == v.Node_Host) + |> filter(fn: (r) => r["service"] == v.Node_Service) + |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) + |> yield(name: "mean") + shade: true + width: 11 + xCol: _time + yCol: _value + yPos: 10 + - axes: + - base: "10" + name: x + scale: linear + - base: "10" + name: y + scale: linear + colors: + - hex: '#31C0F6' + id: 25c22b67-3a3c-4a56-8772-880f04244a7a + name: Nineteen Eighty Four + type: scale + - hex: '#A500A5' + id: 9f64e524-568e-4ca1-ad35-53badeef5592 + name: Nineteen Eighty Four + type: scale + - hex: '#FF7E27' + id: b5a254d5-9e9b-479d-9a2f-988961190e1f + name: Nineteen Eighty Four + type: scale + geom: line + height: 3 + hoverDimension: auto + kind: Xy + legendColorizeRows: true + legendOpacity: 1 + legendOrientationThreshold: 10 + name: CPU Usage (User and System) + position: overlaid + queries: + - query: |- + from(bucket: "iot_center") + |> range(start: v.timeRangeStart, stop: v.timeRangeStop) + |> filter(fn: (r) => r["_measurement"] == "node_cpu_usage") + |> filter(fn: (r) => r["_field"] == "system" or r["_field"] == "user") + |> filter(fn: (r) => r["host"] == v.Node_Host) + |> filter(fn: (r) => r["service"] == v.Node_Service) + |> difference(nonNegative: true, columns: ["_value"]) + |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) + |> yield(name: "mean") + shade: true + width: 5 + xCol: _time + xPos: 2 + yCol: _value + - axes: + - base: "10" + name: x + scale: linear + - base: "10" + name: y + scale: linear + colors: + - hex: '#31C0F6' + id: 25c22b67-3a3c-4a56-8772-880f04244a7a + name: Nineteen Eighty Four + type: scale + - hex: '#A500A5' + id: 9f64e524-568e-4ca1-ad35-53badeef5592 + name: Nineteen Eighty Four + type: scale + - hex: '#FF7E27' + id: b5a254d5-9e9b-479d-9a2f-988961190e1f + name: Nineteen Eighty Four + type: scale + geom: line + height: 3 + hoverDimension: auto + kind: Xy + legendColorizeRows: true + legendOpacity: 1 + legendOrientationThreshold: 10 + name: Heap Usage + position: overlaid + queries: + - query: |- + from(bucket: "iot_center") + |> range(start: v.timeRangeStart, stop: v.timeRangeStop) + |> filter(fn: (r) => r["_measurement"] == "node_memory_usage") + |> filter(fn: (r) => r["_field"] == "heapTotal" or r["_field"] == "heapUsed") + |> filter(fn: (r) => r["host"] == v.Node_Host) + |> filter(fn: (r) => r["service"] == v.Node_Service) + |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) + |> yield(name: "mean") + shade: true + width: 4 + xCol: _time + xPos: 7 + yCol: _value + - axes: + - base: "10" + name: x + scale: linear + - base: "10" + name: y + scale: linear + colors: + - hex: '#31C0F6' + id: 25c22b67-3a3c-4a56-8772-880f04244a7a + name: Nineteen Eighty Four + type: scale + - hex: '#A500A5' + id: 9f64e524-568e-4ca1-ad35-53badeef5592 + name: Nineteen Eighty Four + type: scale + - hex: '#FF7E27' + id: b5a254d5-9e9b-479d-9a2f-988961190e1f + name: Nineteen Eighty Four + type: scale + geom: line + height: 4 + hoverDimension: auto + kind: Xy + legendColorizeRows: true + legendOpacity: 1 + legendOrientationThreshold: 10 + name: File System IO + position: overlaid + queries: + - query: |- + from(bucket: "iot_center") + |> range(start: v.timeRangeStart, stop: v.timeRangeStop) + |> filter(fn: (r) => r["_measurement"] == "node_resource_usage") + |> filter(fn: (r) => r["_field"] == "fsRead" or r["_field"] == "fsWrite") + |> filter(fn: (r) => r["host"] == v.Node_Host) + |> filter(fn: (r) => r["service"] == v.Node_Service) + |> difference(nonNegative: true, columns: ["_value"]) + |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) + |> yield(name: "mean") + shade: true + width: 4 + xCol: _time + xPos: 7 + yCol: _value + yPos: 3 + description: Node.js Application Monitoring usng InfluxDB JS Client Library + name: Node.js Application Monitoring diff --git a/node_js/readme.md b/node_js/readme.md new file mode 100644 index 00000000..ff4de1c1 --- /dev/null +++ b/node_js/readme.md @@ -0,0 +1,60 @@ +# Node.js Monitoring Template + +Provided by: [bonitoo.io](.) + +This template provides a simple monitoring of Node.js application. Dashboard is showing basic process metrics and response time of http requests including response code. +The process metrics are generated from `process.memoryUsage()))`, `process.cpuUsage()` and `process.resourceUsage()` functions. + +### Dashboard example + +![Screenshot](node_dashboard.png) + +### Quick Install + +#### InfluxDB UI + +In the InfluxDB UI, go to Settings->Templates and enter this URL: https://github.com/influxdata/community-templates/tree/master/Node.js/Node.js.yml + +#### Influx CLI +If you have your InfluxDB credentials [configured in the CLI](https://v2.docs.influxdata.com/v2.0/reference/cli/influx/config/), you can install this template with: + +``` +influx apply https://github.com/influxdata/community-templates/tree/master/Node.js/Node.js.yml +``` + +## Included Resources + +This template includes the following: + + - 0 Buckets: + - 1 Label: `Node.js` + - 0 Telegraf Configurations: + - 0 Checks: + - 1 Dashboards: `Node.js Application Monitoring` + - 2 Variables: `Node_Service`, and `Node_Host` + +## Setup Instructions + +1. Load the dashboard according to the the paragraph above +1. Copy [monitor.js](https://github.com/influxdata/community-templates/tree/master/Node.js/monitor.js) and [env.js](https://github.com/influxdata/community-templates/tree/master/Node.js/env.js) files +1. Update InfluxDB credentials in `env.js` +1. Register `monitor.js` code - see the following example + +```javascript +const onboardInfluxDB = require('./influxdb/onboarding') +const {logEnvironment, INFLUX_URL} = require('./env') +const monitor = require('./monitor') + +async function startApplication() { + const app = express() + + // monitor application + monitor(app) + + ... +} +``` + +## Contact + +Author: Miroslav Malecha, https://www.bonitoo.io