diff --git a/SPEC/BITSWAP.md b/SPEC/BITSWAP.md index 7971070f5e..678b599507 100644 --- a/SPEC/BITSWAP.md +++ b/SPEC/BITSWAP.md @@ -17,15 +17,15 @@ Bitswap API `callback` must follow `function (err, stats) {}` signature, where `err` is an error if the operation was not successful. `stats` is an Object containing the following keys: -- `provideBufLen` +- `provideBufLen` is an integer. - `wantlist` (array) - `peers` (array) -- `blocksReceived` -- `dataReceived` -- `blocksSent` -- `dataSent` -- `dupBlksReceived` -- `dupDataReceived` +- `blocksReceived` is a [Big Int][1] +- `dataReceived` is a [Big Int][1] +- `blocksSent` is a [Big Int][1] +- `dataSent` is a [Big Int][1] +- `dupBlksReceived` is a [Big Int][1] +- `dupDataReceived` is a [Big Int][1] If no `callback` is passed, a promise is returned. @@ -47,3 +47,5 @@ ipfs.stats.bitswap((err, stats) => console.log(stats)) // dupBlksReceived: 0, // dupDataReceived: 0 } ``` + +[1]: https://github.com/MikeMcl/big.js/ diff --git a/SPEC/REPO.md b/SPEC/REPO.md index 218a3929fc..db04e744fb 100644 --- a/SPEC/REPO.md +++ b/SPEC/REPO.md @@ -42,11 +42,11 @@ Where: `callback` must follow `function (err, stats) {}` signature, where `err` is an Error if the operation was not successful and `stats` is an object containing the following keys: -- `numObjects` -- `repoSize` -- `repoPath` -- `version` -- `storageMax` +- `numObjects` is a [Big Int][1]. +- `repoSize` is a [Big Int][1], in bytes. +- `repoPath` is a string. +- `version` is a string. +- `storageMax` is a [Big Int][1]. If no `callback` is passed, a promise is returned. @@ -81,3 +81,5 @@ ipfs.repo.version((err, version) => console.log(version)) // "6" ``` + +[1]: https://github.com/MikeMcl/big.js/ diff --git a/SPEC/STATS.md b/SPEC/STATS.md index 7bcd5f9dfc..21bbcbc098 100644 --- a/SPEC/STATS.md +++ b/SPEC/STATS.md @@ -11,7 +11,7 @@ Stats API #### `bw` -> Adds an IPFS object to the pinset and also stores it to the IPFS repo. pinset is the set of hashes currently pinned (not gc'able). +> Get IPFS bandwidth information as an object. ##### `Go` **WIP** @@ -25,12 +25,14 @@ Where: - `poll` is used to print bandwidth at an interval. - `interval` is the time interval to wait between updating output, if `poll` is true. -`callback` must follow `function (err, stats) {}` signature, where `err` is an error if the operation was not successful. `stats` is an Object containing the following keys: +`callback` must follow `function (err, stat) {}` signature, where `err` is an Error if the operation was not successful. -- `totalIn` -- `totalOut` -- `rateIn` -- `rateOut` +`stat` is, in both cases, an Object containing the following keys: + +- `totalIn` - is a [Big Int][big], in bytes. +- `totalOut` - is a [Big Int][big], in bytes. +- `rateIn` - is a [Big Int][big], in bytes. +- `rateOut` - is a [Big Int][big], in bytes. If no `callback` is passed, a promise is returned. @@ -39,8 +41,70 @@ If no `callback` is passed, a promise is returned. ```JavaScript ipfs.stats.bw((err, stats) => console.log(stats)) -// { totalIn: 15456, -// totalOut: 15420, -// rateIn: 905.0873512246716, -// rateOut: 893.7400053359125 } +// { totalIn: Big {...}, +// totalOut: Big {...}, +// rateIn: Big {...}, +// rateOut: Big {...} } +``` + +#### `bwPullStream` + +> Get IPFS bandwidth information as a [Pull Stream][ps]. + +##### `Go` **WIP** + +##### `JavaScript` - ipfs.stats.bwPullStream([options]) -> [Pull Stream][ps] + +Options are described on [`ipfs.stats.bw`](#bw). + +**Example:** + +```JavaScript +const pull = require('pull-stream') +const log = require('pull-stream/sinks/log') + +const stream = ipfs.stats.bwReadableStream({ poll: true }) + +pull( + stream, + log() +) + +// { totalIn: Big {...}, +// totalOut: Big {...}, +// rateIn: Big {...}, +// rateOut: Big {...} } +// ... +// Ad infinitum ``` + +#### `bwReadableStream` + +> Get IPFS bandwidth information as a [Readable Stream][rs]. + +##### `Go` **WIP** + +##### `JavaScript` - ipfs.stats.bwReadableStream([options]) -> [Readable Stream][rs] + +Options are described on [`ipfs.stats.bw`](#bw). + +**Examples:** + +```JavaScript +const stream = ipfs.stats.bwReadableStream({ poll: true }) + +stream.on('data', (data) => { + console.log(data) +})) + +// { totalIn: Big {...}, +// totalOut: Big {...}, +// rateIn: Big {...}, +// rateOut: Big {...} } +// ... +// Ad infinitum +``` + +[big]: https://github.com/MikeMcl/big.js/ +[rs]: https://www.npmjs.com/package/readable-stream +[ps]: https://www.npmjs.com/package/pull-stream diff --git a/js/src/repo.js b/js/src/repo.js index e4973212fb..db143a2687 100644 --- a/js/src/repo.js +++ b/js/src/repo.js @@ -5,6 +5,7 @@ const chai = require('chai') const dirtyChai = require('dirty-chai') +const statsTests = require('./utils/stats') const expect = chai.expect chai.use(dirtyChai) @@ -46,26 +47,15 @@ module.exports = (common) => { }) it('.stat', (done) => { - ipfs.repo.stat((err, stat) => { - expect(err).to.not.exist() - expect(stat).to.exist() - expect(stat).to.have.property('numObjects') - expect(stat).to.have.property('repoSize') - expect(stat).to.have.property('repoPath') - expect(stat).to.have.property('version') - expect(stat).to.have.property('storageMax') + ipfs.repo.stat((err, res) => { + statsTests.expectIsRepo(err, res) done() }) }) it('.stat Promise', () => { - return ipfs.repo.stat().then((stat) => { - expect(stat).to.exist() - expect(stat).to.have.property('numObjects') - expect(stat).to.have.property('repoSize') - expect(stat).to.have.property('repoPath') - expect(stat).to.have.property('version') - expect(stat).to.have.property('storageMax') + return ipfs.repo.stat().then((res) => { + statsTests.expectIsRepo(null, res) }) }) diff --git a/js/src/stats.js b/js/src/stats.js index 58703c2e9c..b634ad1c36 100644 --- a/js/src/stats.js +++ b/js/src/stats.js @@ -5,7 +5,9 @@ const chai = require('chai') const dirtyChai = require('dirty-chai') +const statsTests = require('./utils/stats') const expect = chai.expect +const pull = require('pull-stream') chai.use(dirtyChai) module.exports = (common) => { @@ -43,17 +45,7 @@ module.exports = (common) => { } ipfs.stats.bitswap((err, res) => { - expect(err).to.not.exist() - expect(res).to.exist() - expect(res).to.have.a.property('provideBufLen') - expect(res).to.have.a.property('wantlist') - expect(res).to.have.a.property('peers') - expect(res).to.have.a.property('blocksReceived') - expect(res).to.have.a.property('dataReceived') - expect(res).to.have.a.property('blocksSent') - expect(res).to.have.a.property('dataSent') - expect(res).to.have.a.property('dupBlksReceived') - expect(res).to.have.a.property('dupDataReceived') + statsTests.expectIsBitswap(err, res) done() }) }) @@ -65,16 +57,7 @@ module.exports = (common) => { } return ipfs.stats.bitswap().then((res) => { - expect(res).to.exist() - expect(res).to.have.a.property('provideBufLen') - expect(res).to.have.a.property('wantlist') - expect(res).to.have.a.property('peers') - expect(res).to.have.a.property('blocksReceived') - expect(res).to.have.a.property('dataReceived') - expect(res).to.have.a.property('blocksSent') - expect(res).to.have.a.property('dataSent') - expect(res).to.have.a.property('dupBlksReceived') - expect(res).to.have.a.property('dupDataReceived') + statsTests.expectIsBitswap(null, res) }) }) @@ -85,12 +68,7 @@ module.exports = (common) => { } ipfs.stats.bw((err, res) => { - expect(err).to.not.exist() - expect(res).to.exist() - expect(res).to.have.a.property('totalIn') - expect(res).to.have.a.property('totalOut') - expect(res).to.have.a.property('rateIn') - expect(res).to.have.a.property('rateOut') + statsTests.expectIsBandwidth(err, res) done() }) }) @@ -102,14 +80,42 @@ module.exports = (common) => { } return ipfs.stats.bw().then((res) => { - expect(res).to.exist() - expect(res).to.have.a.property('totalIn') - expect(res).to.have.a.property('totalOut') - expect(res).to.have.a.property('rateIn') - expect(res).to.have.a.property('rateOut') + statsTests.expectIsBandwidth(null, res) + }) + }) + + it('.bwReadableStream', (done) => { + if (!withGo) { + console.log('Not supported in js-ipfs yet') + return done() + } + + const stream = ipfs.stats.bwReadableStream() + + stream.once('data', (data) => { + statsTests.expectIsBandwidth(null, data) + stream.destroy() + done() }) }) + it('.bwPullStream', (done) => { + if (!withGo) { + console.log('Not supported in js-ipfs yet') + return done() + } + + const stream = ipfs.stats.bwPullStream() + + pull( + stream, + pull.collect((err, data) => { + statsTests.expectIsBandwidth(err, data[0]) + done() + }) + ) + }) + it('.repo', (done) => { if (!withGo) { console.log('Not supported in js-ipfs yet') @@ -117,13 +123,7 @@ module.exports = (common) => { } ipfs.stats.repo((err, res) => { - expect(err).to.not.exist() - expect(res).to.exist() - expect(res).to.have.a.property('numObjects') - expect(res).to.have.a.property('repoSize') - expect(res).to.have.a.property('repoPath') - expect(res).to.have.a.property('version') - expect(res).to.have.a.property('storageMax') + statsTests.expectIsRepo(err, res) done() }) }) @@ -135,12 +135,7 @@ module.exports = (common) => { } return ipfs.stats.repo().then((res) => { - expect(res).to.exist() - expect(res).to.have.a.property('numObjects') - expect(res).to.have.a.property('repoSize') - expect(res).to.have.a.property('repoPath') - expect(res).to.have.a.property('version') - expect(res).to.have.a.property('storageMax') + statsTests.expectIsRepo(null, res) }) }) }) diff --git a/js/src/utils/stats.js b/js/src/utils/stats.js new file mode 100644 index 0000000000..24440a71b7 --- /dev/null +++ b/js/src/utils/stats.js @@ -0,0 +1,63 @@ +/* eslint-env mocha */ +/* eslint max-nested-callbacks: ["error", 8] */ + +'use strict' + +const Big = require('big.js') +const { expect } = require('chai') + +const isBigInt = (n) => { + return n.constructor.name === 'Big' +} + +module.exports.expectIsBitswap = (err, stats) => { + expect(err).to.not.exist() + expect(stats).to.exist() + expect(stats).to.have.a.property('provideBufLen') + expect(stats).to.have.a.property('wantlist') + expect(stats).to.have.a.property('peers') + expect(stats).to.have.a.property('blocksReceived') + expect(stats).to.have.a.property('dataReceived') + expect(stats).to.have.a.property('blocksSent') + expect(stats).to.have.a.property('dataSent') + expect(stats).to.have.a.property('dupBlksReceived') + expect(stats).to.have.a.property('dupDataReceived') + + expect(stats.provideBufLen).to.a('number') + expect(stats.wantlist).to.be.an('array') + expect(stats.peers).to.be.an('array') + expect(isBigInt(stats.blocksReceived)).to.eql(true) + expect(isBigInt(stats.dataReceived)).to.eql(true) + expect(isBigInt(stats.blocksSent)).to.eql(true) + expect(isBigInt(stats.dataSent)).to.eql(true) + expect(isBigInt(stats.dupBlksReceived)).to.eql(true) + expect(isBigInt(stats.dupDataReceived)).to.eql(true) +} + +module.exports.expectIsBandwidth = (err, stats) => { + expect(err).to.not.exist() + expect(stats).to.exist() + expect(stats).to.have.a.property('totalIn') + expect(stats).to.have.a.property('totalOut') + expect(stats).to.have.a.property('rateIn') + expect(stats).to.have.a.property('rateOut') + expect(isBigInt(stats.totalIn)).to.eql(true) + expect(isBigInt(stats.totalOut)).to.eql(true) + expect(isBigInt(stats.rateIn)).to.eql(true) + expect(isBigInt(stats.rateOut)).to.eql(true) +} + +module.exports.expectIsRepo = (err, res) => { + expect(err).to.not.exist() + expect(res).to.exist() + expect(res).to.have.a.property('numObjects') + expect(res).to.have.a.property('repoSize') + expect(res).to.have.a.property('repoPath') + expect(res).to.have.a.property('version') + expect(res).to.have.a.property('storageMax') + expect(isBigInt(res.numObjects)).to.eql(true) + expect(isBigInt(res.repoSize)).to.eql(true) + expect(isBigInt(res.storageMax)).to.eql(true) + expect(res.repoPath).to.be.a('string') + expect(res.version).to.be.a('string') +} diff --git a/package.json b/package.json index 9995953e0b..5427879daa 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "dependencies": { "aegir": "^12.3.0", "async": "^2.6.0", + "big.js": "^5.0.3", "bl": "^1.2.1", "bs58": "^4.0.1", "chai": "^4.1.2",