From cc043f9d9e217aa3e4ef62542169cd660d71b5db Mon Sep 17 00:00:00 2001 From: vince Date: Tue, 3 Apr 2018 13:07:32 +0200 Subject: [PATCH 1/2] feature: add network metrics + test --- src/metrics/network.ts | 151 +++++++++++++++++++++++++ src/services/metrics.ts | 2 + test/fixtures/features/networkChild.ts | 24 ++++ test/fixtures/features/tracingChild.ts | 6 +- test/metrics/network.spec.ts | 38 +++++++ 5 files changed, 217 insertions(+), 4 deletions(-) create mode 100644 src/metrics/network.ts create mode 100644 test/fixtures/features/networkChild.ts create mode 100644 test/metrics/network.spec.ts diff --git a/src/metrics/network.ts b/src/metrics/network.ts new file mode 100644 index 00000000..996b4dca --- /dev/null +++ b/src/metrics/network.ts @@ -0,0 +1,151 @@ +import * as netModule from 'net' +import MetricsFeature from '../features/metrics' +import MetricsInterface from './metricsInterface' +import MetricConfig from '../utils/metricConfig' + +import debug from 'debug' +debug('axm:network') + +export default class NetworkMetric implements MetricsInterface { + private metricFeature: MetricsFeature + + private defaultConf = { + ports: true, + traffic: true + } + + constructor (metricFeature: MetricsFeature) { + this.metricFeature = metricFeature + } + + init (config?) { + config = MetricConfig.getConfig(config, this.defaultConf) + + if (config.traffic) { + this.catchTraffic() + } + + if (config.ports) { + this.catchPorts() + } + } + + destroy () { + debug('NetworkMetric destroyed !') + } + + catchPorts () { + const portsList: Array = [] + let openedPorts = 'N/A' + + this.metricFeature.metric({ + name : 'Open ports', + value : function () { return openedPorts } + }) + + const originalListen = netModule.Server.prototype.listen + + netModule.Server.prototype.listen = function () { + const port = parseInt(arguments[0], 10) + + if (!isNaN(port) && portsList.indexOf(port) === -1) { + portsList.push(port) + openedPorts = portsList.sort().join() + } + + this.once('close', function () { + if (portsList.indexOf(port) > -1) { + portsList.splice(portsList.indexOf(port), 1) + openedPorts = portsList.sort().join() + } + }) + + return originalListen.apply(this, arguments) + } + } + + catchTraffic () { + let download = 0 + let upload = 0 + let up = '0 B/sec' + let down = '0 B/sec' + + const filter = function (bytes) { + let toFixed = 0 + + if (bytes < 1024) { + toFixed = 6 + } else if (bytes < (1024 * 1024)) { + toFixed = 3 + } else if (bytes !== 0) { + toFixed = 2 + } + + bytes = (bytes / (1024 * 1024)).toFixed(toFixed) + + let cutZeros = 0 + + for (let i = (bytes.length - 1); i > 0; --i) { + if (bytes[i] === '.') { + ++cutZeros + break + } + if (bytes[i] !== '0') break + ++cutZeros + } + + if (cutZeros > 0) { + bytes = bytes.slice(0, -(cutZeros)) + } + + return (bytes + ' MB/s') + } + + const interval = setInterval(function () { + up = filter(upload) + down = filter(download) + upload = 0 + download = 0 + }, 999) + + interval.unref() + + this.metricFeature.metric({ + name : 'Network Download', + agg_type : 'sum', + value : function () { return down } + }) + + this.metricFeature.metric({ + name : 'Network Upload', + agg_type : 'sum', + value : function () { return up } + }) + + const originalWrite = netModule.Socket.prototype.write + + netModule.Socket.prototype.write = function (data) { + if (data.length) { + upload += data.length + } + return originalWrite.apply(this, arguments) + } + + const originalRead = netModule.Socket.prototype.read + + netModule.Socket.prototype.read = function () { + + if (!this.monitored) { + this.monitored = true + + this.on('data', function (data) { + if (data.length) { + download += data.length + } + }) + } + + return originalRead.apply(this, arguments) + } + } +} diff --git a/src/services/metrics.ts b/src/services/metrics.ts index 97646a72..c5c02f80 100644 --- a/src/services/metrics.ts +++ b/src/services/metrics.ts @@ -6,6 +6,7 @@ import EventLoopDelayMetric from '../metrics/eventLoopDelay' import MetricConfig from '../utils/metricConfig' import EventLoopHandlesRequestsMetric from '../metrics/eventLoopHandlesRequests' import Transaction from '../metrics/transaction' +import NetworkMetric from '../metrics/network' debug('axm:metricService') @@ -26,6 +27,7 @@ export default class MetricsService { this.services.set('eventLoopDelay', new EventLoopDelayMetric(metricsFeature)) this.services.set('eventLoopActive', new EventLoopHandlesRequestsMetric(metricsFeature)) this.services.set('transaction', new Transaction(metricsFeature)) + this.services.set('network', new NetworkMetric(metricsFeature)) } init (config?, force?) { diff --git a/test/fixtures/features/networkChild.ts b/test/fixtures/features/networkChild.ts new file mode 100644 index 00000000..bfe0700a --- /dev/null +++ b/test/fixtures/features/networkChild.ts @@ -0,0 +1,24 @@ +import Metric from '../../../src/features/metrics' + +const metric = new Metric() +metric.init({network: true}, true) + +const httpModule = require('http') + +let timer + +const server = httpModule.createServer((req, res) => { + res.writeHead(200) + res.end('hey') +}).listen(3002, () => { + timer = setInterval(function () { + httpModule.get('http://localhost:' + server.address().port) + httpModule.get('http://localhost:' + server.address().port + '/toto') + }, 100) +}) + +process.on('SIGINT', function () { + clearInterval(timer) + server.close() + metric.destroy() +}) diff --git a/test/fixtures/features/tracingChild.ts b/test/fixtures/features/tracingChild.ts index 94055e7e..f6b9974e 100644 --- a/test/fixtures/features/tracingChild.ts +++ b/test/fixtures/features/tracingChild.ts @@ -10,13 +10,11 @@ const httpModule = require('http') // test http outbound let timer -app.get('/', function (req, res){ - console.log('home') +app.get('/', function (req, res) { res.send('home') }) -app.get('/toto', function (req, res){ - console.log('toto') +app.get('/toto', function (req, res) { res.send('toto') }) diff --git a/test/metrics/network.spec.ts b/test/metrics/network.spec.ts new file mode 100644 index 00000000..7a5c5772 --- /dev/null +++ b/test/metrics/network.spec.ts @@ -0,0 +1,38 @@ +import { expect, assert } from 'chai' +import 'mocha' + +import SpecUtils from '../fixtures/utils' +import { fork, exec } from 'child_process' + +describe('Network', function () { + this.timeout(5000) + + it('should send network data', (done) => { + const child = fork(SpecUtils.buildTestPath('fixtures/features/networkChild.js')) + + child.on('message', pck => { + + if (pck.type === 'axm:monitor' && pck.data['Network Download'].value !== '0 B/sec') { + + expect(pck.data.hasOwnProperty('Network Download')).to.equal(true) + expect(pck.data['Network Download'].agg_type).to.equal('sum') + expect(pck.data['Network Download'].historic).to.equal(true) + expect(pck.data['Network Download'].type).to.equal('Network Download') + + expect(pck.data.hasOwnProperty('Network Upload')).to.equal(true) + expect(pck.data['Network Upload'].agg_type).to.equal('sum') + expect(pck.data['Network Upload'].historic).to.equal(true) + expect(pck.data['Network Upload'].type).to.equal('Network Upload') + + expect(pck.data.hasOwnProperty('Open ports')).to.equal(true) + expect(pck.data['Open ports'].value).to.equal('3002') + expect(pck.data['Open ports'].agg_type).to.equal('avg') + expect(pck.data['Open ports'].historic).to.equal(true) + expect(pck.data['Open ports'].type).to.equal('Open ports') + + child.kill('SIGINT') + done() + } + }) + }) +}) From 47f20ebab5c46d54e70ba5812e343a1af38ff9b0 Mon Sep 17 00:00:00 2001 From: vince Date: Wed, 4 Apr 2018 11:24:09 +0200 Subject: [PATCH 2/2] feature: improve network configuration --- src/metrics/network.ts | 70 +++++++++++-------- test/fixtures/features/networkChild.ts | 2 +- .../features/networkWithoutDownloadChild.ts | 24 +++++++ test/metrics/network.spec.ts | 22 ++++++ 4 files changed, 88 insertions(+), 30 deletions(-) create mode 100644 test/fixtures/features/networkWithoutDownloadChild.ts diff --git a/src/metrics/network.ts b/src/metrics/network.ts index 996b4dca..f30e2607 100644 --- a/src/metrics/network.ts +++ b/src/metrics/network.ts @@ -10,7 +10,7 @@ export default class NetworkMetric implements MetricsInterface { private metricFeature: MetricsFeature private defaultConf = { - ports: true, + ports: false, traffic: true } @@ -22,7 +22,7 @@ export default class NetworkMetric implements MetricsInterface { config = MetricConfig.getConfig(config, this.defaultConf) if (config.traffic) { - this.catchTraffic() + this.catchTraffic(config.traffic) } if (config.ports) { @@ -64,7 +64,7 @@ export default class NetworkMetric implements MetricsInterface { } } - catchTraffic () { + catchTraffic (config) { let download = 0 let upload = 0 let up = '0 B/sec' @@ -110,42 +110,54 @@ export default class NetworkMetric implements MetricsInterface { interval.unref() - this.metricFeature.metric({ - name : 'Network Download', - agg_type : 'sum', - value : function () { return down } - }) + if (config === true || config.download === true) { + this.metricFeature.metric({ + name: 'Network Download', + agg_type: 'sum', + value: function () { + return down + } + }) + } - this.metricFeature.metric({ - name : 'Network Upload', - agg_type : 'sum', - value : function () { return up } - }) + if (config === true || config.upload === true) { + this.metricFeature.metric({ + name: 'Network Upload', + agg_type: 'sum', + value: function () { + return up + } + }) + } - const originalWrite = netModule.Socket.prototype.write + if (config === true || config.upload === true) { + const originalWrite = netModule.Socket.prototype.write - netModule.Socket.prototype.write = function (data) { - if (data.length) { - upload += data.length + netModule.Socket.prototype.write = function (data) { + if (data.length) { + upload += data.length + } + return originalWrite.apply(this, arguments) } - return originalWrite.apply(this, arguments) } - const originalRead = netModule.Socket.prototype.read + if (config === true || config.download === true) { + const originalRead = netModule.Socket.prototype.read - netModule.Socket.prototype.read = function () { + netModule.Socket.prototype.read = function () { - if (!this.monitored) { - this.monitored = true + if (!this.monitored) { + this.monitored = true - this.on('data', function (data) { - if (data.length) { - download += data.length - } - }) - } + this.on('data', function (data) { + if (data.length) { + download += data.length + } + }) + } - return originalRead.apply(this, arguments) + return originalRead.apply(this, arguments) + } } } } diff --git a/test/fixtures/features/networkChild.ts b/test/fixtures/features/networkChild.ts index bfe0700a..04d57fdd 100644 --- a/test/fixtures/features/networkChild.ts +++ b/test/fixtures/features/networkChild.ts @@ -1,7 +1,7 @@ import Metric from '../../../src/features/metrics' const metric = new Metric() -metric.init({network: true}, true) +metric.init({network: {ports: true}}, true) const httpModule = require('http') diff --git a/test/fixtures/features/networkWithoutDownloadChild.ts b/test/fixtures/features/networkWithoutDownloadChild.ts new file mode 100644 index 00000000..ce46dc96 --- /dev/null +++ b/test/fixtures/features/networkWithoutDownloadChild.ts @@ -0,0 +1,24 @@ +import Metric from '../../../src/features/metrics' + +const metric = new Metric() +metric.init({network: {traffic: {upload: true}}}, true) + +const httpModule = require('http') + +let timer + +const server = httpModule.createServer((req, res) => { + res.writeHead(200) + res.end('hey') +}).listen(3002, () => { + timer = setInterval(function () { + httpModule.get('http://localhost:' + server.address().port) + httpModule.get('http://localhost:' + server.address().port + '/toto') + }, 100) +}) + +process.on('SIGINT', function () { + clearInterval(timer) + server.close() + metric.destroy() +}) diff --git a/test/metrics/network.spec.ts b/test/metrics/network.spec.ts index 7a5c5772..9068f92a 100644 --- a/test/metrics/network.spec.ts +++ b/test/metrics/network.spec.ts @@ -35,4 +35,26 @@ describe('Network', function () { } }) }) + + it('should only send upload data', (done) => { + const child = fork(SpecUtils.buildTestPath('fixtures/features/networkWithoutDownloadChild.js')) + + child.on('message', pck => { + + if (pck.type === 'axm:monitor' && pck.data['Network Upload'].value !== '0 B/sec') { + + expect(pck.data.hasOwnProperty('Network Download')).to.equal(false) + + expect(pck.data.hasOwnProperty('Network Upload')).to.equal(true) + expect(pck.data['Network Upload'].agg_type).to.equal('sum') + expect(pck.data['Network Upload'].historic).to.equal(true) + expect(pck.data['Network Upload'].type).to.equal('Network Upload') + + expect(pck.data.hasOwnProperty('Open ports')).to.equal(false) + + child.kill('SIGINT') + done() + } + }) + }) })