Skip to content
This repository has been archived by the owner on Feb 12, 2020. It is now read-only.

Commit

Permalink
feat(breaking change): easy pollers
Browse files Browse the repository at this point in the history
  • Loading branch information
hacdias committed Jan 21, 2018
1 parent 98babed commit c109b95
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 91 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
node_modules
package-lock.json
yarn.lock
dist
dist
coverage
.nyc_output
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,15 @@ npm install --save ipfs-stats

#### Properties

- `poller.stats` retrieves the current stats. It should contain the fields `bw`, `node`, `peers` and `repo`.
- `poller.stats` retrieves the current stats. It should contain the fields `bw`, `id`, `peers` and `repo`.

#### Methods

- `poller.start([opts])` tells the poller to start polling the `opts`.
- `poller.stop([opts])` tells the poller to stop polling the `opts`.

`opts` is an Array of strings. Default is `['peers', 'node']`.
`opts` is an Array of strings. Default is `['bw', 'id', 'peers', 'repo']`. Beware that the `id` poller only runs once because
the ID stats are the same throughout the lifespan of the daemon.


#### Events
Expand Down
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "ipfs-stats",
"version": "1.1.4",
"description": "IPFS Stats Poller",
"main": "index.js",
"main": "./src/index.js",
"repository": {
"type": "git",
"url": "git+https://github.com/hacdias/ipfs-stats.git"
Expand Down Expand Up @@ -30,15 +30,16 @@
},
"devDependencies": {
"aegir": "^12.3.0",
"chai": "^4.1.2",
"go-ipfs-dep": "^0.4.13",
"ipfsd-ctl": "^0.27.1",
"pre-commit": "^1.2.2"
},
"scripts": {
"lint": "aegir lint",
"release": "aegir release",
"build": "aegir build",
"test": "aegir test",
"test:node": "aegir test --target node",
"test:browser": "aegir test --target browser",
"test": "aegir test --target node",
"coverage": "aegir coverage",
"coverage-publish": "aegir coverage --upload"
},
Expand Down
176 changes: 92 additions & 84 deletions index.js → src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,16 @@ const EventEmitter = require('events').EventEmitter
const debug = require('debug')('stats-poller')
const LocationsPoller = require('ipfs-locations')

const allOptions = ['peers', 'node']
const allOptions = ['id', 'bw', 'repo', 'peers']

function makePoller (obj) {
return {
running: false,
stop: false,
do: obj.do,
then: obj.then
}
}

/**
* It's a Stats Poller.
Expand All @@ -25,20 +34,28 @@ module.exports = class StatsPoller extends EventEmitter {
this.statsCache = {}
this.locations = new LocationsPoller(ipfs)

this.poll = {
peers: false,
node: false
}

this.pollScheduled = {
peers: false,
node: false
// Configure the pollers!
this.pollers = {
id: makePoller({
do: () => this.ipfs.id(),
then: (done, s) => { this._handleId(s, done) }
}),
bw: makePoller({
do: () => this.ipfs.stats.bw(),
then: (done, s) => { done(s) }
}),
repo: makePoller({
do: () => this.ipfs.repo.stat(),
then: (done, s) => { done(s) }
}),
peers: makePoller({
do: () => this.ipfs.swarm.peers(),
then: (done, s) => { this._handlePeers(s, done) }
})
}

debug('Fetching self ID')
this.ipfs.id()
.then((id) => { this._handleId(id) })
.catch(this._error.bind(this))
// Run the ID poller.
this.start('id')
}

/**
Expand All @@ -61,71 +78,72 @@ module.exports = class StatsPoller extends EventEmitter {
* @param {Function} fn
* @return {Void}
*/
_pollManager (name, fn) {
const next = () => {
debug('Polling %s stats', name)
this.pollScheduled[name] = true
fn(done)
_pollManager (name) {
if (this.pollers[name].running) {
return
}

const done = () => {
setTimeout(() => {
process()
}, this.frequency)
}

const process = () => {
if (!this.poll[name]) {
const execute = () => {
// Stop it if that's what we want.
if (this.pollers[name].stop) {
debug('Stopped polling %s stats', name)
this.pollScheduled[name] = false
this.pollers[name].stop = false
this.pollers[name].running = false
return
}

next()
// Run!
debug('Polling %s stats', name)
this.pollers[name].running = true
this.pollers[name].do.call(this)
.then(this.pollers[name].then.bind(this, done))
.catch(this._error.bind(this))
}

next()
}

/**
* Poll node stats.
* @private
* @param {Function} done
* @return {Void}
*/
_pollNodeStats (done) {
Promise.all([
this.ipfs.stats.bw(),
this.ipfs.repo.stat()
]).then(([bw, repo]) => {
this.statsCache.bw = bw
this.statsCache.repo = repo
const done = (stats) => {
if (stats) this.statsCache[name] = stats
this.emit('change', this.statsCache)

done()
}).catch(this._error.bind(this))
// Schedule the next polling.
setTimeout(() => {
execute()
}, this.frequency)
}

execute()
}

/**
* Poll peers.
* Handle the raw ID.
* @private
* @param {Object} raw - Raw ID
* @param {Function} done
* @return {Void}
*/
_pollPeerStats (done) {
this.ipfs.swarm.peers().then((peers) => {
this._handlePeers(peers)
done()
}).catch(this._error.bind(this))
_handleId (raw, done) {
this.statsCache.id = raw
this.statsCache.id.addresses.sort()
this.statsCache.id.location = 'Unknown'

this.locations.get(raw.addresses)
.then((location) => {
this.statsCache.id.location = location && location.formatted
this.emit('change', this.statsCache)
})
.catch((e) => { this._error(e) })

this.stop('id')
done()
}

/**
* Handle the Peers.
* @private
* @param {Object} raw - Raw Peers
* @param {Function} done
* @return {Void}
*/
_handlePeers (raw) {
_handlePeers (raw, done) {
const peers = []
raw = raw.sort((a, b) => a.peer.toB58String() > b.peer.toB58String())

Expand All @@ -142,29 +160,7 @@ module.exports = class StatsPoller extends EventEmitter {
peers.push(peer)
})

this.statsCache.peers = peers
this.emit('change', this.statsCache)
}

/**
* Handle the raw ID.
* @private
* @param {Object} raw - Raw ID
* @return {Void}
*/
_handleId (raw) {
this.statsCache.node = raw
this.statsCache.node.addresses.sort()
this.statsCache.node.location = 'Unknown'

this.locations.get(raw.addresses)
.then((location) => {
this.statsCache.node.location = location && location.formatted
this.emit('change', this.statsCache)
})
.catch((e) => { this._error(e) })

this.emit('change', this.statsCache)
done(peers)
}

/**
Expand All @@ -180,7 +176,17 @@ module.exports = class StatsPoller extends EventEmitter {
* @return {Void}
*/
stop (opts = allOptions) {
opts.forEach(what => { this.poll[what] = false })
if (typeof opts === 'string') {
opts = [opts]
}

opts.forEach(what => {
if (!this.pollers[what]) {
throw new Error(`${what} poller does not exist`)
}

this.pollers[what].stop = true
})
}

/**
Expand All @@ -189,14 +195,16 @@ module.exports = class StatsPoller extends EventEmitter {
* @return {Void}
*/
start (opts = allOptions) {
opts.forEach(what => { this.poll[what] = true })

if (this.poll.node && !this.pollScheduled.node) {
this._pollManager('node', this._pollNodeStats.bind(this))
if (typeof opts === 'string') {
opts = [opts]
}

if (this.poll.peers && !this.pollScheduled.peers) {
this._pollManager('peers', this._pollPeerStats.bind(this))
}
opts.forEach(what => {
if (!this.pollers[what]) {
throw new Error(`${what} poller does not exist`)
}

this._pollManager(what)
})
}
}
94 changes: 94 additions & 0 deletions test/index.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/* eslint-env mocha */
'use strict'

const DaemonFactory = require('ipfsd-ctl')
const df = DaemonFactory.create()
const expect = require('chai').expect
const StatsPoller = require('../src/index')

describe('stats poller', () => {
let ipfsd
let poller

before(function (done) {
// CI takes longer to instantiate the daemon,
// so we need to increase the timeout for the
// before step
this.timeout(60 * 1000)

df.spawn({disposable: true}, (err, node) => {
expect(err).to.be.null // eslint-disable-line no-unused-expressions
ipfsd = node

poller = new StatsPoller(node.api, 500)
done()
})
})

after(function (done) {
this.timeout(15 * 1000)
ipfsd.stop(done)
})

it('id stats', function (done) {
this.timeout(5000)

const assert = () => {
try {
expect(poller.stats).to.have.property('id')
done()
} catch (e) {}
}

setTimeout(() => { assert() }, 500)
})

it('peer stats', function (done) {
this.timeout(5000)

poller.start('peers')

const assert = () => {
try {
expect(poller.stats).to.have.property('peers')
expect(poller.stats.peers).to.be.an('array')
poller.stop('peers')
done()
} catch (e) {}
}

setTimeout(() => { assert() }, 500)
})

it('bandwidth stats', function (done) {
this.timeout(5000)

poller.start('bw')

const assert = () => {
try {
expect(poller.stats).to.have.property('bw')
poller.stop('bw')
done()
} catch (e) {}
}

setTimeout(() => { assert() }, 500)
})

it('repo stats', function (done) {
this.timeout(5000)

poller.start('repo')

const assert = () => {
try {
expect(poller.stats).to.have.property('repo')
poller.stop('repo')
done()
} catch (e) {}
}

setTimeout(() => { assert() }, 500)
})
})

0 comments on commit c109b95

Please sign in to comment.