diff --git a/public/js/controllers/StatsController.js b/public/js/controllers/StatsController.js index 0757b3e25..11e493f67 100755 --- a/public/js/controllers/StatsController.js +++ b/public/js/controllers/StatsController.js @@ -47,6 +47,11 @@ angular.module('BlocksApp').controller('StatsController', function($stateParams, * Created by chenxiangyu on 2016/8/5. */ scope.init = function(dataset, chartid) { + var total = 0; + dataset.forEach(function(d) { + total += d.count; + }); + var svg = d3.select(chartid) .append("g"); @@ -139,7 +144,7 @@ angular.module('BlocksApp').controller('StatsController', function($stateParams, div.style("left", d3.event.pageX + 10 + "px"); div.style("top", d3.event.pageY - 25 + "px"); div.style("display", "inline-block"); - div.html((d.data._id) + "
" + (d.data.count) + " "); + div.html((d.data._id) + "
" + (d.data.count) + "
(" + d3.format(".2%")(d.data.count / total) + ")"); }); slice .on("mouseout", function (d) { diff --git a/routes/stats.js b/routes/stats.js index 0ddcc21bc..dc7fb3dfa 100644 --- a/routes/stats.js +++ b/routes/stats.js @@ -14,55 +14,125 @@ module.exports = function(req, res) { res.status(400).send(); else if (req.body.action=="miners") - getMinerStats(res) + getMinerStats(req, res) else if (req.body.action=="hashrate") getHashrate(res); else if (req.body.action=="hashrates") - getHashrates(res); + getHashrates(req, res); } /** Aggregate miner stats **/ -var getMinerStats = function(res) { - BlockStat.aggregate([ - { $group: { - _id: '$miner', - count: {$sum: 1} } - } - ], function (err, result) { - if (err) { - console.error(err); - res.status(500).send(); - } else { - res.write(JSON.stringify(result)); - res.end(); - } +var getMinerStats = function(req, res) { + var range = 6*60*60; // 6 hours + // check validity of range + if (req.body.range && req.body.range < 60 * 60 * 24 * 7) { + range = parseInt(req.body.range); + if (range < 1800) { // minimal 30 minutes + range = 1800; + } + } + + var timebefore = parseInt((new Date())/1000) - range; + Block.find({ timestamp: { $lte: timebefore } }, "timestamp number") + .lean(true).sort('-number').limit(1).exec(function (err, docs) { + if (err || !docs) { + console.error(err); + res.status(500).send(); + res.end(); + return; + } + var blockNumber = docs[0].number; + console.log('getMinerStats(): blockNumber = ' + blockNumber); + Block.aggregate([ + { $match: { number: { $gte: blockNumber } } }, + { $group: { + _id: '$miner', + timestamp: {$min: '$timestamp' }, + count: {$sum: 1} } + } + ], function (err, result) { + if (err) { + console.error(err); + res.status(500).send(); + } else { + res.write(JSON.stringify(result)); + res.end(); + } + }); }); } /** Aggregate network hashrates **/ -var getHashrates = function(res) { - BlockStat.aggregate([{ - $group: { +var getHashrates = function(req, res) { + // setup default range + //var range = 7 * 24 * 60 * 60; /* 7 days */ + //var range = 14 * 24 * 60 * 60; /* 14 days */ + //var range = 30 * 24 * 60 * 60; /* 1 months */ + //var range = 2 * 30 * 24 * 60 * 60; /* 2 months */ + var range = 6 * 30 * 24 * 60 * 60; /* 6 months */ + if (req.body.days && req.body.days <= 365) { + var days = parseInt(req.body.days); + if (days <= 1) { + days = 1; + } + range = days * 60 * 60 * 24; + } else if (req.body.range && req.body.range < 31536000 /* 60 * 60 * 24 * 365 */) { + range = parseInt(req.body.range); + if (range < 30 * 60) { + range = 30 * 60; /* minimal range */ + } + } + + // select mod + var rngs = [ 30*60, 60*60, 2*60*60, 4*60*60, 6*60*60, + 12*60*60, 24*60*60, 7*24*60*60, 14*24*60*60, 30*24*60*60 + ]; + var mods = [ 1, 1, 2, 10, 10, + 15, 30, 15*60, 30*60, 30*60, + 60*60 + ]; + var i = 0; + rngs.forEach(function(r) { + if (range > r) { + i++; + } + return; + }); + var mod = mods[i]; + + var timestamp = parseInt((new Date())/1000) - range; + + BlockStat.aggregate([ + { $match: { timestamp: { $gte: timestamp } } }, + { $group: { _id: { timestamp: { - $subtract: [ '$timestamp', { $mod: [ '$timestamp', 3600 ] } ] + $subtract: [ '$timestamp', { $mod: [ '$timestamp', mod ] } ] } }, blockTime: { $avg: '$blockTime' }, - difficulty: { $max: '$difficulty' } + difficulty: { $max: '$difficulty' }, + count: { $sum: 1 } + }}, + { $project: { + "_id": 0, + "timestamp": '$_id.timestamp', + "blockTime": 1, + "difficulty": 1, + "count": 1, } - }]).sort('_id.timestamp').exec(function(err, docs) { + }]).sort('timestamp').exec(function(err, docs) { var hashrates = []; docs.forEach(function(doc) { doc.instantHashrate = doc.difficulty / doc.blockTime; - doc.unixtime = doc._id.timestamp; - doc.timestamp = doc._id.timestamp; + doc.unixtime = doc.timestamp; /* FIXME */ + doc.timestamp = doc.timestamp; }); res.write(JSON.stringify({"hashrates": docs})); res.end(); diff --git a/tools/stats.js b/tools/stats.js index 8b9763c22..11154cbc4 100644 --- a/tools/stats.js +++ b/tools/stats.js @@ -7,20 +7,35 @@ var Web3 = require('web3'); var mongoose = require( 'mongoose' ); var BlockStat = require( '../db.js' ).BlockStat; -var updateStats = function() { - var web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545')); +var updateStats = function(range, interval, rescan) { + var web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545')); mongoose.connect(process.env.MONGO_URI || 'mongodb://localhost/blockDB'); mongoose.set('debug', true); var latestBlock = web3.eth.blockNumber; - getStats(web3, latestBlock, null, latestBlock - 1000); + + interval = Math.abs(parseInt(interval)); + if (!range) { + range = 1000; + } + range *= interval; + if (interval >= 10) { + latestBlock -= latestBlock % interval; + } + getStats(web3, latestBlock, null, latestBlock - range, interval, rescan); } -var getStats = function(web3, blockNumber, nextBlock, endBlock) { - if (blockNumber <= endBlock) - process.exit(9); +var getStats = function(web3, blockNumber, nextBlock, endNumber, interval, rescan) { + if (endNumber < 0) + endNumber = 0; + if (blockNumber <= endNumber) { + if (rescan) { + process.exit(9); + } + return; + } if(web3.isConnected()) { @@ -35,9 +50,9 @@ var getStats = function(web3, blockNumber, nextBlock, endBlock) { } else { if (nextBlock) - checkBlockDBExistsThenWrite(web3, blockData, nextBlock.timestamp); + checkBlockDBExistsThenWrite(web3, blockData, nextBlock, endNumber, interval, rescan); else - checkBlockDBExistsThenWrite(web3, blockData, parseInt(Date.now()/1000)); + checkBlockDBExistsThenWrite(web3, blockData, null, endNumber, interval, rescan); } }); } else { @@ -52,9 +67,9 @@ var getStats = function(web3, blockNumber, nextBlock, endBlock) { * if record exists: abort * if record DNE: write a file for the block */ -var checkBlockDBExistsThenWrite = function(web3, blockData, nextTime) { +var checkBlockDBExistsThenWrite = function(web3, blockData, nextBlock, endNumber, interval, rescan) { BlockStat.find({number: blockData.number}, function (err, b) { - if (!b.length) { + if (!b.length && nextBlock) { // calc hashrate, txCount, blocktime, uncleCount var stat = { "number": blockData.number, @@ -64,7 +79,7 @@ var checkBlockDBExistsThenWrite = function(web3, blockData, nextTime) { "gasUsed": blockData.gasUsed, "gasLimit": blockData.gasLimit, "miner": blockData.miner, - "blockTime": nextTime - blockData.timestamp, + "blockTime": (nextBlock.timestamp - blockData.timestamp) / (nextBlock.number - blockData.number), "uncleCount": blockData.uncles.length } new BlockStat(stat).save( function( err, s, count ){ @@ -77,13 +92,20 @@ var checkBlockDBExistsThenWrite = function(web3, blockData, nextTime) { } else { console.log('DB successfully written for block number ' + blockData.number.toString() ); - getStats(web3, blockData.number - 1, blockData); + getStats(web3, blockData.number - interval, blockData, endNumber, interval, rescan); } }); } else { - console.log('Aborting because block number: ' + blockData.number.toString() + - ' already exists in DB.'); - return; + if (rescan || !nextBlock) { + getStats(web3, blockData.number - interval, blockData, endNumber, interval, rescan); + if (nextBlock) { + console.log('WARN: block number: ' + blockData.number.toString() + ' already exists in DB.'); + } + } else { + console.error('Aborting because block number: ' + blockData.number.toString() + + ' already exists in DB.'); + return; + } } }) @@ -95,6 +117,40 @@ var checkBlockDBExistsThenWrite = function(web3, blockData, nextTime) { var minutes = 1; statInterval = minutes * 60 * 1000; -setInterval(function() { - updateStats(); -}, statInterval); +var rescan = false; /* rescan: true - rescan range */ +var range = 1000; +var interval = 100; + +/** + * RESCAN=1000:100000 means interval;range + * + * Usage: + * RESCAN=1000:100000 node tools/stats.js + */ +if (process.env.RESCAN) { + var tmp = process.env.RESCAN.split(/:/); + if (tmp.length > 1) { + interval = Math.abs(parseInt(tmp[0])); + if (tmp[1]) { + range = Math.abs(parseInt(tmp[1])); + } + } + var i = interval; + var j = 0; + for (var j = 0; i >= 10; j++) { + i = parseInt(i / 10); + } + interval = Math.pow(10, j); + console.log('Selected interval = ' + interval); + + rescan = true; +} + +// run +updateStats(range, interval, rescan); + +if (!rescan) { + setInterval(function() { + updateStats(range, interval); + }, statInterval); +}