diff --git a/package.json b/package.json index 32b2c31e63..9b95528e4c 100644 --- a/package.json +++ b/package.json @@ -74,9 +74,8 @@ "form-data": "^2.3.2", "go-ipfs-dep": "^0.4.13", "hat": "0.0.3", - "interface-ipfs-core": "~0.56.5", "ipfsd-ctl": "~0.30.1", - "left-pad": "^1.2.0", + "interface-ipfs-core": "~0.57.0", "lodash": "^4.17.5", "mocha": "^5.0.4", "ncp": "^2.0.0", diff --git a/src/cli/commands/stats/bw.js b/src/cli/commands/stats/bw.js new file mode 100644 index 0000000000..0eb318fe6f --- /dev/null +++ b/src/cli/commands/stats/bw.js @@ -0,0 +1,48 @@ +'use strict' + +const pull = require('pull-stream') + +module.exports = { + command: 'bw', + + describe: 'Get bandwidth information.', + + builder: { + peer: { + type: 'string', + default: '' + }, + proto: { + type: 'string', + default: '' + }, + poll: { + type: 'boolean', + default: false + }, + interval: { + type: 'string', + default: '1s' + } + }, + + handler (argv) { + const stream = argv.ipfs.stats.bwPullStream({ + peer: argv.peer, + proto: argv.proto, + poll: argv.poll, + interval: argv.interval + }) + + pull( + stream, + pull.drain((chunk) => { + console.log(`bandwidth status + total in: ${chunk.totalIn}B + total out: ${chunk.totalOut}B + rate in: ${chunk.rateIn}B/s + rate out: ${chunk.rateOut}B/s`) + }) + ) + } +} diff --git a/src/core/components/stats.js b/src/core/components/stats.js index 6bc7121301..ad87cf981e 100644 --- a/src/core/components/stats.js +++ b/src/core/components/stats.js @@ -1,8 +1,88 @@ 'use strict' +const promisify = require('promisify-es6') +const Big = require('big.js') +const Pushable = require('pull-pushable') +const human = require('human-to-milliseconds') +const toStream = require('pull-stream-to-stream') + +function bandwidthStats (self, opts) { + return new Promise((resolve, reject) => { + let stats + + if (opts.peer) { + stats = self._libp2pNode.stats.forPeer(opts.peer) + } else if (opts.proto) { + stats = self._libp2pNode.stats.forProtocol(opts.proto) + } else { + stats = self._libp2pNode.stats.global + } + + if (!stats) { + resolve({ + totalIn: new Big(0), + totalOut: new Big(0), + rateIn: new Big(0), + rateOut: new Big(0) + }) + return + } + + resolve({ + totalIn: stats.snapshot.dataReceived, + totalOut: stats.snapshot.dataSent, + rateIn: new Big(stats.movingAverages.dataReceived['60000'].movingAverage() / 60), + rateOut: new Big(stats.movingAverages.dataSent['60000'].movingAverage() / 60) + }) + }) +} + module.exports = function stats (self) { + const _bwPullStream = (opts) => { + opts = opts || {} + let interval = null + let stream = Pushable(true, () => { + if (interval) { + clearInterval(interval) + } + }) + + if (opts.poll) { + human(opts.interval || '1s', (err, value) => { + if (err) throw err + + interval = setInterval(() => { + bandwidthStats(self, opts) + .then((stats) => stream.push(stats)) + .catch((err) => stream.end(err)) + }, value) + }) + } else { + bandwidthStats(self, opts) + .then((stats) => { + stream.push(stats) + stream.end() + }) + .catch((err) => stream.end(err)) + } + + return stream.source + } + return { bitswap: require('./bitswap')(self).stat, - repo: require('./repo')(self).stat + repo: require('./repo')(self).stat, + bw: promisify((opts, callback) => { + if (typeof opts === 'function') { + callback = opts + opts = {} + } + + bandwidthStats(self, opts) + .then((stats) => callback(null, stats)) + .catch((err) => callback(err)) + }), + bwReadableStream: (opts) => toStream.source(_bwPullStream(opts)), + bwPullStream: _bwPullStream } } diff --git a/src/core/components/swarm.js b/src/core/components/swarm.js index 95ebd7484e..c7f6cdbfbf 100644 --- a/src/core/components/swarm.js +++ b/src/core/components/swarm.js @@ -3,6 +3,7 @@ const multiaddr = require('multiaddr') const promisify = require('promisify-es6') const values = require('lodash.values') +const PeerId = require('peer-id') const OFFLINE_ERROR = require('../utils').OFFLINE_ERROR diff --git a/src/http/api/resources/stats.js b/src/http/api/resources/stats.js index 839f92a3b3..7ec7b0cb42 100644 --- a/src/http/api/resources/stats.js +++ b/src/http/api/resources/stats.js @@ -1,7 +1,46 @@ 'use strict' +const { Transform } = require('readable-stream') + +const transformBandwidth = (stat) => { + return { + TotalIn: stat.totalIn, + TotalOut: stat.totalOut, + RateIn: stat.rateIn, + RateOut: stat.rateOut + } +} + exports = module.exports exports.bitswap = require('./bitswap').stat exports.repo = require('./repo').stat + +exports.bw = (request, reply) => { + const ipfs = request.server.app.ipfs + const options = { + peer: request.query.peer, + proto: request.query.proto, + poll: request.query.poll === 'true', + interval: request.query.interval || '1s' + } + + const res = ipfs.stats.bwReadableStream(options) + const output = new Transform({ + writableObjectMode: true, + transform (chunk, encoding, cb) { + this.push(JSON.stringify(transformBandwidth(chunk)) + '\n') + cb() + } + }) + + request.on('disconnect', () => { + res.destroy() + }) + + res.pipe(output) + reply(output) + .header('content-type', 'application/json') + .header('x-chunked-output', '1') +} diff --git a/src/http/api/routes/stats.js b/src/http/api/routes/stats.js index c167ec58c0..35ec768083 100644 --- a/src/http/api/routes/stats.js +++ b/src/http/api/routes/stats.js @@ -20,4 +20,12 @@ module.exports = (server) => { handler: resources.stats.repo } }) + + api.route({ + method: '*', + path: '/api/v0/stats/bw', + config: { + handler: resources.stats.bw + } + }) } diff --git a/test/cli/commands.js b/test/cli/commands.js index d1d0812957..06154cfac6 100644 --- a/test/cli/commands.js +++ b/test/cli/commands.js @@ -4,7 +4,7 @@ const expect = require('chai').expect const runOnAndOff = require('../utils/on-and-off') -const commandCount = 72 +const commandCount = 73 describe('commands', () => runOnAndOff((thing) => { let ipfs