Merge pull request #111 from cubedro/develop

Stability improvements and refactoring
This commit is contained in:
Marian OANCΞA 2015-04-29 12:15:37 +03:00
commit 80d1cb60d7
8 changed files with 423 additions and 298 deletions

View File

@ -1,3 +1,7 @@
language: node_js language: node_js
node_js: node_js:
- "0.12" - "0.12"
- "0.11"
env:
global:
secure: Qdkmi13nqcXskABBhbuGZmRakh4orOW1dalkSCyUbzGuYf0OdYF8ttNVuJa0WSWI4KhNXhtz2p3I8dM95wL++LCVqXFmIvZtX8Nca36KlNxIPNXPjn0odayh+c4pgrhrbz8TDmDXl9IPuZmNz8HHtN7xmIn6YDcm13wA3gTmfwo=

260
app.js
View File

@ -1,3 +1,4 @@
var _ = require('lodash');
var express = require('express'); var express = require('express');
var app = express(); var app = express();
var path = require('path'); var path = require('path');
@ -7,8 +8,8 @@ var askedForHistory = false;
var askedForHistoryTime = 0; var askedForHistoryTime = 0;
var Primus = require('primus'), var Primus = require('primus'),
api, api,
client; client;
var WS_SECRET = process.env.WS_SECRET || "eth-net-stats-has-a-secret"; var WS_SECRET = process.env.WS_SECRET || "eth-net-stats-has-a-secret";
@ -18,18 +19,18 @@ var Nodes = new Collection();
var server = require('http').createServer(app); var server = require('http').createServer(app);
api = new Primus(server, { api = new Primus(server, {
transformer: 'websockets', transformer: 'websockets',
pathname: '/api', pathname: '/api',
parser: 'JSON' parser: 'JSON'
}); });
api.use('emit', require('primus-emit')); api.use('emit', require('primus-emit'));
api.use('spark-latency', require('primus-spark-latency')); api.use('spark-latency', require('primus-spark-latency'));
var client = new Primus(server, { var client = new Primus(server, {
transformer: 'websockets', transformer: 'websockets',
pathname: '/primus', pathname: '/primus',
parser: 'JSON' parser: 'JSON'
}); });
var clientLatency = 0; var clientLatency = 0;
@ -37,118 +38,151 @@ var clientLatency = 0;
client.use('emit', require('primus-emit')); client.use('emit', require('primus-emit'));
api.on('connection', function(spark) { api.on('connection', function(spark) {
console.log('Latency: ', spark.latency); console.log('Latency: ', spark.latency);
console.log(spark.id); console.log(spark.id);
console.log(spark.address); console.log(spark.address);
console.log(spark.query); console.log(spark.query);
spark.on('hello', function(data) spark.on('hello', function(data)
{ {
console.log('Latency: ', spark.latency); console.log('Latency: ', spark.latency);
console.log('Got hello data from ', spark.id); console.log('Got hello data from ', spark.id);
console.log(data); console.log(data);
if(typeof data.secret === 'undefined' || data.secret !== WS_SECRET) if( _.isUndefined(data.secret) || data.secret !== WS_SECRET )
{ {
spark.end(undefined, { reconnect: false }); spark.end(undefined, { reconnect: false });
return false; return false;
} }
if(typeof data.id !== 'undefined' && typeof data.info !== 'undefined') if( !_.isUndefined(data.id) && !_.isUndefined(data.info) )
{ {
data.ip = spark.address.ip; data.ip = spark.address.ip;
data.spark = spark.id; data.spark = spark.id;
data.latency = spark.latency; data.latency = spark.latency;
var info = Nodes.add(data); var info = Nodes.add( data );
spark.emit('ready'); spark.emit('ready');
client.write({action: 'add', data: info}); client.write({
client.write({action: 'charts', data: Nodes.getCharts()}); action: 'add',
} data: info
}); });
spark.on('update', function(data) client.write({
{ action: 'charts',
if(typeof data.id !== 'undefined' && typeof data.stats !== 'undefined') data: Nodes.getCharts()
{ });
data.stats.latency = spark.latency; }
});
var stats = Nodes.update(data.id, data.stats); spark.on('update', function(data)
if(stats !== false) {
{ if( !_.isUndefined(data.id) && !_.isUndefined(data.stats) )
client.write({action: 'update', data: stats}); {
client.write({action: 'charts', data: Nodes.getCharts()}); data.stats.latency = spark.latency;
}
if(Nodes.getHistory().requiresUpdate() && Nodes.canNodeUpdate(data.id) && (!askedForHistory || (new Date()).getTime() - askedForHistoryTime > 120000)) var stats = Nodes.update(data.id, data.stats);
{
var range = Nodes.getHistory().getHistoryRequestInterval();
console.log("asked " + data.id + " for history");
console.log('interval', range);
spark.emit('history', range);
askedForHistory = true;
askedForHistoryTime = (new Date()).getTime();
}
}
});
spark.on('history', function(data){ if(stats !== false)
console.log("got history from " + data.id); {
client.write({action: 'charts', data: Nodes.addHistory(data.id, data.history)}); client.write({
askedForHistory = false; action: 'update',
}); data: stats
});
spark.on('node-ping', function(data) client.write({
{ action: 'charts',
spark.emit('node-pong'); data: Nodes.getCharts()
}); });
}
}
});
spark.on('latency', function(data) spark.on('history', function(data)
{ {
if(typeof data.id !== 'undefined') console.log("got history from " + data.id);
{
var stats = Nodes.updateLatency(data.id, data.latency);
client.write({action: 'latency', data: stats}); client.write({
action: 'charts',
data: Nodes.addHistory(data.id, data.history)
});
if(Nodes.getHistory().requiresUpdate() && Nodes.canNodeUpdate(data.id) && (!askedForHistory || (new Date()).getTime() - askedForHistoryTime > 120000)) askedForHistory = false;
{
var range = Nodes.getHistory().getHistoryRequestInterval();
console.log("asked " + data.id + " for history");
console.log('interval', range);
spark.emit('history', range);
askedForHistory = true;
askedForHistoryTime = (new Date()).getTime();
}
}
});
spark.on('end', function(data) client.write({
{ action: 'charts',
var stats = Nodes.inactive(spark.id); data: Nodes.getCharts()
});
});
client.write({action: 'inactive', data: stats}); spark.on('node-ping', function(data)
}); {
spark.emit('node-pong');
});
spark.on('latency', function(data)
{
if( !_.isUndefined(data.id) )
{
var stats = Nodes.updateLatency(data.id, data.latency);
client.write({
action: 'latency',
data: stats
});
if( Nodes.requiresUpdate(data.id) && (!askedForHistory || _.now() - askedForHistoryTime > 200000) )
{
var range = Nodes.getHistory().getHistoryRequestRange();
console.log("asked " + data.id + " for history: " + range.min + " - " + range.max);
spark.emit('history', range);
askedForHistory = true;
askedForHistoryTime = _.now();
}
}
});
spark.on('end', function(data)
{
var stats = Nodes.inactive(spark.id);
client.write({
action: 'inactive',
data: stats
});
});
}); });
client.on('connection', function(spark) { client.on('connection', function(clientSpark)
spark.on('ready', function(data){ {
spark.emit('init', {nodes: Nodes.all()}); clientSpark.on('ready', function(data)
{
clientSpark.emit('init', { nodes: Nodes.all() });
spark.write({action: 'charts', data: Nodes.getCharts()}); clientSpark.write({
}); action: 'charts',
data: Nodes.getCharts()
});
});
spark.on('client-pong', function(data) { clientSpark.on('client-pong', function(data)
var latency = Math.ceil(((new Date()).getTime() - clientLatency)/2); {
spark.emit('client-latency', { latency: latency }); var latency = Math.ceil( (_.now() - clientLatency) / 2 );
});
clientSpark.emit('client-latency', { latency: latency });
});
}); });
var latencyTimeout = setInterval(function(){ var latencyTimeout = setInterval( function ()
clientLatency = (new Date()).getTime(); {
client.write({action: 'client-ping'}); clientLatency = _.now();
client.write({ action: 'client-ping' });
}, 5000); }, 5000);
// view engine setup // view engine setup
@ -165,29 +199,29 @@ app.get('/', function(req, res) {
// catch 404 and forward to error handler // catch 404 and forward to error handler
app.use(function(req, res, next) { app.use(function(req, res, next) {
var err = new Error('Not Found'); var err = new Error('Not Found');
err.status = 404; err.status = 404;
next(err); next(err);
}); });
// error handlers // error handlers
if (app.get('env') === 'development') { if (app.get('env') === 'development') {
app.use(function(err, req, res, next) { app.use(function(err, req, res, next) {
res.status(err.status || 500); res.status(err.status || 500);
res.render('error', { res.render('error', {
message: err.message, message: err.message,
error: err error: err
}); });
}); });
} }
// production error handler // production error handler
app.use(function(err, req, res, next) { app.use(function(err, req, res, next) {
res.status(err.status || 500); res.status(err.status || 500);
res.render('error', { res.render('error', {
message: err.message, message: err.message,
error: {} error: {}
}); });
}); });
server.listen(process.env.PORT || 3000); server.listen(process.env.PORT || 3000);

View File

@ -4,9 +4,8 @@ var Node = require('./node');
var Collection = function Collection() var Collection = function Collection()
{ {
this._list = []; this._items = [];
this._history = new Blockchain(); this._blockchain = new Blockchain();
this._bestBlock = null;
return this; return this;
} }
@ -26,12 +25,12 @@ Collection.prototype.update = function(id, stats)
if(!node) if(!node)
return false; return false;
var block = this._history.add(stats.block, id); var block = this._blockchain.add(stats.block, id);
if(! block) if(! block)
return false; return false;
var propagationHistory = this._history.getNodePropagation(id); var propagationHistory = this._blockchain.getNodePropagation(id);
stats.block.arrived = block.arrived; stats.block.arrived = block.arrived;
stats.block.received = block.received; stats.block.received = block.received;
@ -47,8 +46,11 @@ Collection.prototype.addHistory = function(id, blocks)
if(!node) if(!node)
return false; return false;
for (var i = blocks.length - 1; i >= 0; i--) { blocks = blocks.reverse();
this._history.add(blocks[i], id);
for (var i = 0; i <= blocks.length - 1; i++)
{
this._blockchain.add(blocks[i], id);
}; };
return this.getCharts(); return this.getCharts();
@ -78,7 +80,7 @@ Collection.prototype.inactive = function(id)
Collection.prototype.getIndex = function(search) Collection.prototype.getIndex = function(search)
{ {
return _.findIndex(this._list, search); return _.findIndex(this._items, search);
} }
Collection.prototype.getNode = function(search) Collection.prototype.getNode = function(search)
@ -86,15 +88,15 @@ Collection.prototype.getNode = function(search)
var index = this.getIndex(search); var index = this.getIndex(search);
if(index >= 0) if(index >= 0)
return this._list[index]; return this._items[index];
return false; return false;
} }
Collection.prototype.getNodeByIndex = function(index) Collection.prototype.getNodeByIndex = function(index)
{ {
if(this._list[index]) if(this._items[index])
return this._list[index]; return this._items[index];
return false; return false;
} }
@ -103,7 +105,7 @@ Collection.prototype.getIndexOrNew = function(search, data)
{ {
var index = this.getIndex(search); var index = this.getIndex(search);
return (index >= 0 ? index : this._list.push(new Node(data)) - 1); return (index >= 0 ? index : this._items.push(new Node(data)) - 1);
} }
Collection.prototype.getNodeOrNew = function(search, data) Collection.prototype.getNodeOrNew = function(search, data)
@ -113,27 +115,27 @@ Collection.prototype.getNodeOrNew = function(search, data)
Collection.prototype.all = function() Collection.prototype.all = function()
{ {
return this._list; return this._items;
} }
Collection.prototype.blockPropagationChart = function() Collection.prototype.blockPropagationChart = function()
{ {
return this._history.getBlockPropagation(); return this._blockchain.getBlockPropagation();
} }
Collection.prototype.getUncleCount = function() Collection.prototype.getUncleCount = function()
{ {
return this._history.getUncleCount(); return this._blockchain.getUncleCount();
} }
Collection.prototype.getCharts = function() Collection.prototype.getCharts = function()
{ {
return this._history.getCharts(); return this._blockchain.getCharts();
} }
Collection.prototype.getHistory = function() Collection.prototype.getHistory = function()
{ {
return this._history; return this._blockchain;
} }
Collection.prototype.canNodeUpdate = function(id) Collection.prototype.canNodeUpdate = function(id)
@ -145,13 +147,17 @@ Collection.prototype.canNodeUpdate = function(id)
if(node.canUpdate()) if(node.canUpdate())
{ {
var diff = this._history.bestBlockNumber() - node.getBlockNumber(); var diff = this._blockchain.bestBlockNumber() - node.getBlockNumber();
if(diff <= 0) return (diff <= 0);
return true;
} }
return false; return false;
} }
Collection.prototype.requiresUpdate = function(id)
{
return ( this.canNodeUpdate(id) && this._blockchain.requiresUpdate() );
}
module.exports = Collection; module.exports = Collection;

View File

@ -33,26 +33,30 @@ var History = function History(data)
History.prototype.add = function(block, id) History.prototype.add = function(block, id)
{ {
if(typeof block !== 'undefined' && typeof block.number !== 'undefined' && typeof block.uncles !== 'undefined' && typeof block.transactions !== 'undefined' && typeof block.difficulty !== 'undefined') if( !_.isUndefined(block) && !_.isUndefined(block.number) && !_.isUndefined(block.uncles) && !_.isUndefined(block.transactions) && !_.isUndefined(block.difficulty) && block.number > 0 )
{ {
var historyBlock = this.search(block.number); var historyBlock = this.search(block.number);
var now = (new Date()).getTime(); var now = _.now();
block.arrived = now; block.arrived = now;
block.received = now; block.received = now;
block.propagation = 0; block.propagation = 0;
if(historyBlock) if( historyBlock )
{ {
var propIndex = _.findIndex(historyBlock.propagTimes, {node: id}); var propIndex = _.findIndex( historyBlock.propagTimes, { node: id } );
if(propIndex === -1) if( propIndex === -1 )
{ {
block.arrived = historyBlock.block.arrived; block.arrived = historyBlock.block.arrived;
block.received = now; block.received = now;
block.propagation = now - historyBlock.block.received; block.propagation = now - historyBlock.block.received;
historyBlock.propagTimes.push({node: id, received: now, propagation: block.propagation}); historyBlock.propagTimes.push({
node: id,
received: now,
propagation: block.propagation
});
} }
else else
{ {
@ -65,26 +69,32 @@ History.prototype.add = function(block, id)
{ {
var prevBlock = this.prevMaxBlock(block.number); var prevBlock = this.prevMaxBlock(block.number);
if(prevBlock) if( prevBlock )
{ {
block.time = block.arrived - prevBlock.block.arrived; block.time = block.arrived - prevBlock.block.arrived;
if(block.number < this.bestBlock().height) if(block.number < this.bestBlock().height)
block.time = (block.timestamp - prevBlock.block.timestamp)*1000; block.time = (block.timestamp - prevBlock.block.timestamp) * 1000;
} }
else else
{ {
block.time = 0; block.time = 0;
} }
var item = { var item = {
height: block.number, height: block.number,
block: block, block: block,
propagTimes: [] propagTimes: []
} }
if(this._items.length === 0 || block.number >= (this.bestBlock().height - MAX_HISTORY + 1)) if( this._items.length === 0 || block.number >= (this.bestBlockNumber() - MAX_HISTORY + 1) )
{ {
item.propagTimes.push({node: id, received: now, propagation: block.propagation}); item.propagTimes.push({
node: id,
received: now,
propagation: block.propagation
});
this._save(item); this._save(item);
} }
} }
@ -99,16 +109,17 @@ History.prototype._save = function(block)
{ {
this._items.unshift(block); this._items.unshift(block);
if(this._items.length > MAX_HISTORY){ if(this._items.length > MAX_HISTORY)
{
this._items.pop(); this._items.pop();
} }
this._items = _.sortByOrder(this._items, 'height', false); this._items = _.sortByOrder( this._items, 'height', false );
} }
History.prototype.search = function(number) History.prototype.search = function(number)
{ {
var index = _.findIndex(this._items, {height: number}); var index = _.findIndex( this._items, { height: number } );
if(index < 0) if(index < 0)
return false; return false;
@ -118,7 +129,7 @@ History.prototype.search = function(number)
History.prototype.prevMaxBlock = function(number) History.prototype.prevMaxBlock = function(number)
{ {
var index = _.findIndex(this._items, function(item) { var index = _.findIndex(this._items, function (item) {
return item.height < number; return item.height < number;
}); });
@ -137,7 +148,7 @@ History.prototype.bestBlockNumber = function()
{ {
var best = this.bestBlock(); var best = this.bestBlock();
if(typeof best.height !== 'undefined') if( !_.isUndefined(best.height) )
return best.height; return best.height;
return 0; return 0;
@ -145,23 +156,22 @@ History.prototype.bestBlockNumber = function()
History.prototype.getNodePropagation = function(id) History.prototype.getNodePropagation = function(id)
{ {
var propagation = new Array(MAX_PEER_PROPAGATION); var propagation = new Array( MAX_PEER_PROPAGATION );
var bestBlock = this.bestBlock().height; var bestBlock = this.bestBlockNumber();
_.fill(propagation, -1); _.fill(propagation, -1);
var sorted = _(this._items) var sorted = _( this._items )
.sortByOrder('height', false) .sortByOrder( 'height', false )
.slice(0, MAX_PEER_PROPAGATION) .slice( 0, MAX_PEER_PROPAGATION )
.reverse() .reverse()
.forEach(function(n, key) .forEach(function (item, key)
{ {
var index = MAX_PEER_PROPAGATION - 1 - bestBlock + n.height; var index = MAX_PEER_PROPAGATION - 1 - bestBlock + item.height;
if(index > 0) if(index > 0)
{ {
propagation[index] = _.result(_.find(n.propagTimes, 'node', id), 'propagation', -1); propagation[index] = _.result(_.find(item.propagTimes, 'node', id), 'propagation', -1);
} }
}) })
.value(); .value();
@ -174,9 +184,9 @@ History.prototype.getBlockPropagation = function()
var propagation = []; var propagation = [];
var avgPropagation = 0; var avgPropagation = 0;
_.forEach(this._items, function(n, key) _.forEach(this._items, function (n, key)
{ {
_.forEach(n.propagTimes, function(p, i) _.forEach(n.propagTimes, function (p, i)
{ {
var prop = _.result(p, 'propagation', -1); var prop = _.result(p, 'propagation', -1);
@ -187,57 +197,69 @@ History.prototype.getBlockPropagation = function()
if(propagation.length > 0) if(propagation.length > 0)
{ {
var avgPropagation = Math.round(_.sum(propagation) / propagation.length); var avgPropagation = Math.round( _.sum(propagation) / propagation.length );
} }
var x = d3.scale.linear() var x = d3.scale.linear()
.domain([MIN_PROPAGATION_RANGE, MAX_PROPAGATION_RANGE]) .domain([ MIN_PROPAGATION_RANGE, MAX_PROPAGATION_RANGE ])
.interpolate(d3.interpolateRound); .interpolate( d3.interpolateRound );
var data = d3.layout.histogram() var data = d3.layout.histogram()
.frequency(false) .frequency( false )
.bins(x.ticks(MAX_BINS)) .bins( x.ticks(MAX_BINS) )
(propagation); ( propagation );
var freqCum = 0; var freqCum = 0;
var histogram = data.map(function(val) { var histogram = data.map(function (val) {
freqCum += val.length; freqCum += val.length;
var cumPercent = (freqCum / Math.max(1, propagation.length)); var cumPercent = ( freqCum / Math.max(1, propagation.length) );
return {x: val.x, dx: val.dx, y: val.y, frequency: val.length, cumulative: freqCum, cumpercent: cumPercent};
return {
x: val.x,
dx: val.dx,
y: val.y,
frequency: val.length,
cumulative: freqCum,
cumpercent: cumPercent
};
}); });
return {histogram: histogram, avg: avgPropagation}; return {
histogram: histogram,
avg: avgPropagation
};
} }
History.prototype.getUncleCount = function() History.prototype.getUncleCount = function()
{ {
var uncles = _(this._items) var uncles = _( this._items )
.sortByOrder('height', false) .sortByOrder( 'height', false )
.map(function(item) .map(function (item)
{ {
return item.block.uncles.length; return item.block.uncles.length;
}) })
.value(); .value();
var uncleBins = _.fill(Array(MAX_BINS), 0); var uncleBins = _.fill( Array(MAX_BINS), 0 );
var sumMapper = function(array, key) { var sumMapper = function (array, key)
{
uncleBins[key] = _.sum(array); uncleBins[key] = _.sum(array);
return _.sum(array); return _.sum(array);
}; };
_.map(_.chunk(uncles, MAX_UNCLES_PER_BIN), sumMapper); _.map(_.chunk( uncles, MAX_UNCLES_PER_BIN ), sumMapper);
return uncleBins; return uncleBins;
} }
History.prototype.getBlockTimes = function() History.prototype.getBlockTimes = function()
{ {
var blockTimes = _(this._items) var blockTimes = _( this._items )
.sortByOrder('height', false) .sortByOrder( 'height', false )
.slice(0, MAX_BINS) .slice(0, MAX_BINS)
.reverse() .reverse()
.map(function(item) .map(function (item)
{ {
return item.block.time; return item.block.time;
}) })
@ -248,11 +270,11 @@ History.prototype.getBlockTimes = function()
History.prototype.getDifficulty = function() History.prototype.getDifficulty = function()
{ {
var difficultyHistory = _(this._items) var difficultyHistory = _( this._items )
.sortByOrder('height', false) .sortByOrder( 'height', false )
.slice(0, MAX_BINS) .slice(0, MAX_BINS)
.reverse() .reverse()
.map(function(item) .map(function (item)
{ {
return item.block.difficulty; return item.block.difficulty;
}) })
@ -263,11 +285,11 @@ History.prototype.getDifficulty = function()
History.prototype.getTransactionsCount = function() History.prototype.getTransactionsCount = function()
{ {
var txCount = _(this._items) var txCount = _( this._items )
.sortByOrder('height', false) .sortByOrder( 'height', false )
.slice(0, MAX_BINS) .slice(0, MAX_BINS)
.reverse() .reverse()
.map(function(item) .map(function (item)
{ {
return item.block.transactions.length; return item.block.transactions.length;
}) })
@ -278,11 +300,11 @@ History.prototype.getTransactionsCount = function()
History.prototype.getGasSpending = function() History.prototype.getGasSpending = function()
{ {
var gasSpending = _(this._items) var gasSpending = _( this._items )
.sortByOrder('height', false) .sortByOrder( 'height', false )
.slice(0, MAX_BINS) .slice(0, MAX_BINS)
.reverse() .reverse()
.map(function(item) .map(function (item)
{ {
return item.block.gasUsed; return item.block.gasUsed;
}) })
@ -293,85 +315,113 @@ History.prototype.getGasSpending = function()
History.prototype.getAvgHashrate = function() History.prototype.getAvgHashrate = function()
{ {
if(this._items.length === 0) if( _.isEmpty(this._items) )
return 0; return 0;
var difficultyHistory = _(this._items) var difficultyHistory = _( this._items )
.map(function(item) .map(function (item)
{ {
return item.block.difficulty; return item.block.difficulty;
}) })
.value(); .value();
var avgDifficulty = _.sum(difficultyHistory)/difficultyHistory.length; var avgDifficulty = _.sum(difficultyHistory) / difficultyHistory.length;
var blocktimeHistory = _(this._items) var blocktimeHistory = _( this._items )
.map(function(item) .map(function (item)
{ {
return item.block.time; return item.block.time;
}) })
.value(); .value();
var avgBlocktime = _.sum(blocktimeHistory)/blocktimeHistory.length; var avgBlocktime = _.sum(blocktimeHistory) / blocktimeHistory.length;
return avgDifficulty/1000 * 12 * (12/avgBlocktime); return avgDifficulty / 1000 * 12 * ( 12 / avgBlocktime );
}
History.prototype.getMinersCount = function()
{
var miners = _( this._items )
.sortByOrder( 'height', false )
.slice(0, MAX_BINS)
.map(function (item)
{
return item.block.miner;
})
.value();
var minerCount = [];
_.forEach( _.countBy(miners), function (cnt, miner)
{
minerCount.push({ miner: miner, name: false, blocks: cnt });
});
return _(minerCount)
.sortByOrder( 'blocks', false )
.slice(0, 5)
.value();
} }
History.prototype.getCharts = function() History.prototype.getCharts = function()
{ {
var chartHistory = _(this._items) var chartHistory = _( this._items )
.sortByOrder('height', false) .sortByOrder( 'height', false )
.slice(0, MAX_BINS) .slice(0, MAX_BINS)
.reverse() .reverse()
.map(function(item) .map(function (item)
{ {
var chart = { return {
height: item.height, height: item.height,
blocktime: item.block.time/1000, blocktime: item.block.time / 1000,
difficulty: item.block.difficulty, difficulty: item.block.difficulty,
uncles: item.block.uncles.length, uncles: item.block.uncles.length,
transactions: item.block.transactions.length, transactions: item.block.transactions.length,
gasSpending: item.block.gasUsed gasSpending: item.block.gasUsed,
} miner: item.block.miner
return chart; };
}) })
.value(); .value();
var chart = { return {
height: _.pluck(chartHistory, 'height'), height : _.pluck( chartHistory, 'height' ),
blocktime: _.pluck(chartHistory, 'blocktime'), blocktime : _.pluck( chartHistory, 'blocktime' ),
avgBlocktime: _.sum(_.pluck(chartHistory, 'blocktime')) / (chartHistory.length === 0 ? 1 : chartHistory.length), avgBlocktime : _.sum(_.pluck( chartHistory, 'blocktime' )) / (chartHistory.length === 0 ? 1 : chartHistory.length),
difficulty: _.pluck(chartHistory, 'difficulty'), difficulty : _.pluck( chartHistory, 'difficulty' ),
uncles: _.pluck(chartHistory, 'uncles'), uncles : _.pluck( chartHistory, 'uncles' ),
transactions: _.pluck(chartHistory, 'transactions'), transactions : _.pluck( chartHistory, 'transactions' ),
gasSpending: _.pluck(chartHistory, 'gasSpending'), gasSpending : _.pluck( chartHistory, 'gasSpending' ),
propagation: this.getBlockPropagation(), miners : this.getMinersCount(),
uncleCount: this.getUncleCount(), propagation : this.getBlockPropagation(),
avgHashrate: this.getAvgHashrate() uncleCount : this.getUncleCount(),
} avgHashrate : this.getAvgHashrate()
};
return chart;
}
History.prototype.history = function()
{
return this._items;
} }
History.prototype.requiresUpdate = function() History.prototype.requiresUpdate = function()
{ {
return ! (this._items.length === MAX_HISTORY); return ( this._items.length < MAX_HISTORY && !_.isEmpty(this._items) );
} }
History.prototype.getHistoryRequestInterval = function() History.prototype.getHistoryRequestRange = function()
{ {
if(this._items.length === 0) if( _.isEmpty(this._items) )
return null; return false;
var max = _.min(this._items, 'height').height - 1; var blocks = _.pluck( this._items, 'height' );
var min = max - Math.min(50, (MAX_HISTORY - this._items.length + 1)) + 1; var best = _.max( blocks );
var range = _.range( _.max([ 0, best - MAX_HISTORY ]), best + 1);
return {max: max, min: min}; var missing = _.difference( range, blocks );
var max = _.max(missing);
var min = max - Math.min( 50, (MAX_HISTORY - this._items.length + 1) ) + 1;
return {
max: max,
min: min,
list: _( missing ).reverse().slice(0, 50).reverse().value()
};
} }
module.exports = History; module.exports = History;

View File

@ -20,93 +20,103 @@ var Node = function Node(data)
number: 0, number: 0,
gasLimit: 0, gasLimit: 0,
timestamp: 0, timestamp: 0,
time: 0,
arrival: 0, arrival: 0,
propagation: 0, propagation: 0,
received: 0, received: 0,
transactions: [], transactions: [],
uncles: [] uncles: []
}, },
blocktimeAvg: 0,
propagationAvg: 0, propagationAvg: 0,
blockTimes: [],
difficulty: [],
latency: 0, latency: 0,
uptime: 0, uptime: 0,
lastUpdate: 0 lastUpdate: 0
}; };
this.history = new Array(MAX_HISTORY); this.history = new Array(MAX_HISTORY);
this.uptime = { this.uptime = {
started: null, started: null,
history: [] history: []
}; };
this.init(data);
return this;
}
Node.prototype.init = function(data)
{
_.fill(this.history, -1); _.fill(this.history, -1);
if(this.id === null) { if( this.id === null )
this.uptime.started = (new Date()).getTime(); {
this.uptime.started = _.now();
this.setState(true); this.setState(true);
} }
if(typeof data.id !== 'undefined') if( !_.isUndefined(data.id) )
this.id = data.id; this.id = data.id;
if(typeof data.info !== 'undefined') { this.setInfo(data);
this.info = data.info;
if(typeof data.info.canUpdateHistory === 'undefined') if( !_.isUndefined(data.latency) )
data.info.canUpdateHistory = false;
}
if(typeof data.ip !== 'undefined'){
if(data.ip === '::ffff:127.0.0.1')
data.ip = '84.117.82.122';
this.info.ip = data.ip;
this.setGeo(data.ip);
}
if(typeof data.spark !== 'undefined')
this.spark = data.spark;
if(typeof data.latency !== 'undefined')
this.stats.latency = data.latency; this.stats.latency = data.latency;
return this; return this;
} }
Node.prototype.setGeo = function(ip)
{
this.geo = geoip.lookup(ip);
}
Node.prototype.setInfo = function(data) Node.prototype.setInfo = function(data)
{ {
if(typeof data.info !== 'undefined') { if( !_.isUndefined(data.info) )
{
this.info = data.info; this.info = data.info;
if(typeof data.info.canUpdateHistory === 'undefined') if( _.isUndefined(data.info.canUpdateHistory) )
data.info.canUpdateHistory = false; data.info.canUpdateHistory = false;
} }
if(typeof data.ip !== 'undefined'){ if( !_.isUndefined(data.ip) )
{
if(data.ip === '::ffff:127.0.0.1')
data.ip = '84.117.82.122';
this.info.ip = data.ip; this.info.ip = data.ip;
this.setGeo(data.ip); this.setGeo();
} }
if(typeof data.spark !== 'undefined') if( !_.isUndefined(data.spark) )
this.spark = data.spark; this.spark = data.spark;
if(this.uptime.history.length > 0 && this.uptime.history[this.uptime.history.length - 1].status == 'down') var uptimeCnt = this.uptime.history.length;
if( uptimeCnt > 0 && this.uptime.history[uptimeCnt - 1].status === 'down' )
this.setState(true); this.setState(true);
return this;
}
Node.prototype.setGeo = function()
{
this.geo = geoip.lookup(this.info.ip);
return this;
} }
Node.prototype.getInfo = function() Node.prototype.getInfo = function()
{ {
return {id: this.id, info: this.info, geo: this.geo, stats: this.stats, history: this.history}; return {
id: this.id,
info: this.info,
geo: this.geo,
stats: this.stats,
history: this.history
};
} }
Node.prototype.setStats = function(stats, history) Node.prototype.setStats = function(stats, history)
{ {
if(typeof stats !== 'undefined' && typeof stats.block !== 'undefined' && typeof stats.block.number !== 'undefined') if( !_.isUndefined(stats) && !_.isUndefined(stats.block) && !_.isUndefined(stats.block.number) )
{ {
this.history = history; this.history = history;
@ -115,7 +125,7 @@ Node.prototype.setStats = function(stats, history)
}); });
if(positives.length > 0) if(positives.length > 0)
stats.propagationAvg = Math.round(_.sum(positives)/positives.length); stats.propagationAvg = Math.round( _.sum(positives) / positives.length );
else else
stats.propagationAvg = 0; stats.propagationAvg = 0;
@ -129,11 +139,14 @@ Node.prototype.setStats = function(stats, history)
Node.prototype.setLatency = function(latency) Node.prototype.setLatency = function(latency)
{ {
if(typeof latency !== 'undefined') if( !_.isUndefined(latency) )
{ {
this.stats.latency = latency; this.stats.latency = latency;
return { id: this.id, latency: latency }; return {
id: this.id,
latency: latency
};
} }
return false; return false;
@ -141,13 +154,20 @@ Node.prototype.setLatency = function(latency)
Node.prototype.getStats = function() Node.prototype.getStats = function()
{ {
return {id: this.id, stats: this.stats, history: this.history}; return {
id: this.id,
stats: this.stats,
history: this.history
};
} }
Node.prototype.setState = function(active) Node.prototype.setState = function(active)
{ {
this.stats.active = active; this.stats.active = active;
this.uptime.history.push({status: (active ? 'up' : 'down'), time: (new Date()).getTime()}); this.uptime.history.push({
status: (active ? 'up' : 'down'),
time: _.now()
});
} }
Node.prototype.getBlockNumber = function() Node.prototype.getBlockNumber = function()

View File

@ -130,6 +130,13 @@ span.small-title span.small {
font-size: 50px; font-size: 50px;
line-height: 55px; line-height: 55px;
letter-spacing: -4px; letter-spacing: -4px;
word-spacing: nowrap !important;
}
.big-info .big-details-holder {
position: absolute;
top: 15px;
left: 99px;
} }
.big-info.chart { .big-info.chart {

View File

@ -94,8 +94,8 @@ function StatsCtrl($scope, $filter, socket, _, toastr) {
function socketAction(action, data) function socketAction(action, data)
{ {
console.log('Action: ', action); // console.log('Action: ', action);
console.log('Data: ', data); // console.log('Data: ', data);
switch(action) { switch(action) {
case "init": case "init":
@ -155,16 +155,19 @@ function StatsCtrl($scope, $filter, socket, _, toastr) {
break; break;
case "charts": case "charts":
$scope.lastBlocksTime = data.blocktime;
$scope.avgBlockTime = data.avgBlocktime; $scope.avgBlockTime = data.avgBlocktime;
$scope.avgHashrate = data.avgHashrate; $scope.avgHashrate = data.avgHashrate;
$scope.lastBlocksTime = data.blocktime;
$scope.difficultyChart = data.difficulty; $scope.difficultyChart = data.difficulty;
$scope.transactionDensity = data.transactions;
$scope.gasSpending = data.gasSpending;
$scope.blockPropagationChart = data.propagation.histogram; $scope.blockPropagationChart = data.propagation.histogram;
$scope.blockPropagationAvg = data.propagation.avg; $scope.blockPropagationAvg = data.propagation.avg;
$scope.uncleCountChart = data.uncleCount; $scope.uncleCountChart = data.uncleCount;
$scope.uncleCount = data.uncleCount[0] + data.uncleCount[1]; $scope.uncleCount = data.uncleCount[0] + data.uncleCount[1];
$scope.transactionDensity = data.transactions;
$scope.gasSpending = data.gasSpending;
$scope.miners = data.miners;
getMinersNames();
jQuery('.spark-blocktimes').sparkline($scope.lastBlocksTime, {type: 'bar', tooltipSuffix: ' s'}); jQuery('.spark-blocktimes').sparkline($scope.lastBlocksTime, {type: 'bar', tooltipSuffix: ' s'});
jQuery('.spark-difficulty').sparkline($scope.difficultyChart, {type: 'bar'}); jQuery('.spark-difficulty').sparkline($scope.difficultyChart, {type: 'bar'});
@ -225,6 +228,23 @@ function StatsCtrl($scope, $filter, socket, _, toastr) {
}); });
} }
function getMinersNames()
{
if($scope.miners.length > 0)
{
_.forIn($scope.miners, function(value, key)
{
if(value.name !== false)
return;
var name = _.result(_.find(_.pluck($scope.nodes, 'info'), 'coinbase', value.miner), 'name');
if(typeof name !== 'undefined')
$scope.miners[key].name = name;
});
}
}
function addNewNode(data) function addNewNode(data)
{ {
var index = findIndex({id: data.id}); var index = findIndex({id: data.id});
@ -267,22 +287,6 @@ function StatsCtrl($scope, $filter, socket, _, toastr) {
$scope.lastBlock = $scope.bestStats.block.received; $scope.lastBlock = $scope.bestStats.block.received;
$scope.lastDifficulty = $scope.bestStats.block.difficulty; $scope.lastDifficulty = $scope.bestStats.block.difficulty;
if(typeof $scope.bestStats.miners !== 'undefined') {
$scope.miners = $scope.bestStats.miners;
console.log($scope.miners);
_.forIn($scope.miners, function(value, key)
{
if(value.name !== false)
return;
var name = _.result(_.find(_.pluck($scope.nodes, 'info'), 'coinbase', value.miner), 'name');
if(typeof name !== 'undefined')
$scope.miners[key].name = name;
});
}
} }
$scope.upTimeTotal = _.reduce($scope.nodes, function(total, node) { $scope.upTimeTotal = _.reduce($scope.nodes, function(total, node) {

View File

@ -7,7 +7,7 @@ block content
div.big-info.bestblock.text-info div.big-info.bestblock.text-info
div.pull-left.icon-full-width div.pull-left.icon-full-width
i.icon-block i.icon-block
div.pull-left div.big-details-holder
span.small-title best block span.small-title best block
span.big-details {{'#'}}{{ bestBlock | number}} span.big-details {{'#'}}{{ bestBlock | number}}
div.clearfix div.clearfix
@ -15,7 +15,7 @@ block content
div.big-info.uncleCount.text-info div.big-info.uncleCount.text-info
div.pull-left.icon-full-width div.pull-left.icon-full-width
i.icon-uncle i.icon-uncle
div.pull-left div.big-details-holder
span.small-title uncles #[&nbsp;] span.small-title uncles #[&nbsp;]
span.small (current / last 50) span.small (current / last 50)
span.big-details {{ bestStats.block.uncles.length }}/{{ uncleCount }} span.big-details {{ bestStats.block.uncles.length }}/{{ uncleCount }}
@ -24,7 +24,7 @@ block content
div.big-info.blocktime(class="{{ lastBlock | timeClass : true }}") div.big-info.blocktime(class="{{ lastBlock | timeClass : true }}")
div.pull-left.icon-full-width div.pull-left.icon-full-width
i.icon-time i.icon-time
div.pull-left div.big-details-holder
span.small-title last block span.small-title last block
span.big-details {{ lastBlock | blockTimeFilter }} span.big-details {{ lastBlock | blockTimeFilter }}
div.clearfix div.clearfix
@ -32,7 +32,7 @@ block content
div.big-info.avgblocktime(class="{{ avgBlockTime | avgTimeClass }}") div.big-info.avgblocktime(class="{{ avgBlockTime | avgTimeClass }}")
div.pull-left.icon-full-width div.pull-left.icon-full-width
i.icon-gas i.icon-gas
div.pull-left div.big-details-holder
span.small-title avg block time span.small-title avg block time
span.big-details {{ avgBlockTime | avgTimeFilter }} span.big-details {{ avgBlockTime | avgTimeFilter }}
div.clearfix div.clearfix
@ -40,7 +40,7 @@ block content
div.big-info.difficulty.text-orange div.big-info.difficulty.text-orange
div.pull-left.icon-full-width div.pull-left.icon-full-width
i.icon-hashrate i.icon-hashrate
div.pull-left div.big-details-holder
span.small-title avg network hashrate span.small-title avg network hashrate
span.big-details {{ avgHashrate | number : 1 }} MH/s span.big-details {{ avgHashrate | number : 1 }} MH/s
div.clearfix div.clearfix
@ -48,7 +48,7 @@ block content
div.big-info.difficulty.text-danger div.big-info.difficulty.text-danger
div.pull-left.icon-full-width div.pull-left.icon-full-width
i.icon-difficulty i.icon-difficulty
div.pull-left div.big-details-holder
span.small-title difficulty span.small-title difficulty
span.big-details {{ lastDifficulty | number }} span.big-details {{ lastDifficulty | number }}
div.clearfix div.clearfix