2014-04-10 18:33:41 -07:00
|
|
|
var zlib = require('zlib');
|
|
|
|
|
2014-03-19 13:24:29 -07:00
|
|
|
var redis = require('redis');
|
|
|
|
var async = require('async');
|
|
|
|
|
2014-04-10 18:33:41 -07:00
|
|
|
|
2014-03-19 13:24:29 -07:00
|
|
|
var os = require('os');
|
|
|
|
|
2014-03-30 03:16:02 -07:00
|
|
|
var algos = require('stratum-pool/lib/algoProperties.js');
|
|
|
|
|
2014-03-19 13:24:29 -07:00
|
|
|
|
|
|
|
module.exports = function(logger, portalConfig, poolConfigs){
|
|
|
|
|
|
|
|
var _this = this;
|
|
|
|
|
2014-03-26 14:08:34 -07:00
|
|
|
var logSystem = 'Stats';
|
|
|
|
|
2014-03-19 13:24:29 -07:00
|
|
|
var redisClients = [];
|
2014-04-10 18:33:41 -07:00
|
|
|
var redisStats;
|
|
|
|
|
|
|
|
this.statHistory = [];
|
|
|
|
this.statPoolHistory = [];
|
|
|
|
|
|
|
|
this.stats = {};
|
|
|
|
this.statsString = '';
|
|
|
|
|
|
|
|
setupStatsRedis();
|
|
|
|
gatherStatHistory();
|
2014-03-19 13:24:29 -07:00
|
|
|
|
2014-03-26 14:08:34 -07:00
|
|
|
var canDoStats = true;
|
|
|
|
|
2014-03-19 13:24:29 -07:00
|
|
|
Object.keys(poolConfigs).forEach(function(coin){
|
2014-03-26 14:08:34 -07:00
|
|
|
|
|
|
|
if (!canDoStats) return;
|
|
|
|
|
2014-03-19 13:24:29 -07:00
|
|
|
var poolConfig = poolConfigs[coin];
|
2014-03-26 14:08:34 -07:00
|
|
|
|
|
|
|
if (!poolConfig.shareProcessing || !poolConfig.shareProcessing.internal){
|
|
|
|
logger.error(logSystem, coin, 'Cannot do stats without internal share processing setup');
|
|
|
|
canDoStats = false;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-03-19 13:24:29 -07:00
|
|
|
var internalConfig = poolConfig.shareProcessing.internal;
|
|
|
|
var redisConfig = internalConfig.redis;
|
|
|
|
|
|
|
|
for (var i = 0; i < redisClients.length; i++){
|
|
|
|
var client = redisClients[i];
|
|
|
|
if (client.client.port === redisConfig.port && client.client.host === redisConfig.host){
|
|
|
|
client.coins.push(coin);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
redisClients.push({
|
|
|
|
coins: [coin],
|
|
|
|
client: redis.createClient(redisConfig.port, redisConfig.host)
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
2014-04-10 18:33:41 -07:00
|
|
|
function setupStatsRedis(){
|
|
|
|
redisStats = redis.createClient(portalConfig.website.stats.redis.port, portalConfig.website.stats.redis.host);
|
|
|
|
redisStats.on('error', function(err){
|
|
|
|
logger.error(logSystem, 'Historics', 'Redis for stats had an error ' + JSON.stringify(err));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function gatherStatHistory(){
|
|
|
|
|
|
|
|
var retentionTime = (((Date.now() / 1000) - portalConfig.website.stats.historicalRetention) | 0).toString();
|
|
|
|
|
|
|
|
redisStats.zrangebyscore(['statHistory', retentionTime, '+inf'], function(err, replies){
|
|
|
|
if (err) {
|
|
|
|
logger.error(logSystem, 'Historics', 'Error when trying to grab historical stats ' + JSON.stringify(err));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
for (var i = 0; i < replies.length; i++){
|
|
|
|
_this.statHistory.push(JSON.parse(replies[i]));
|
|
|
|
}
|
|
|
|
_this.statHistory = _this.statHistory.sort(function(a, b){
|
|
|
|
return a.time - b.time;
|
|
|
|
});
|
|
|
|
_this.statHistory.forEach(function(stats){
|
|
|
|
addStatPoolHistory(stats);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function addStatPoolHistory(stats){
|
|
|
|
var data = {
|
|
|
|
time: stats.time,
|
|
|
|
pools: {}
|
|
|
|
};
|
|
|
|
for (var pool in stats.pools){
|
|
|
|
data.pools[pool] = {
|
|
|
|
hashrate: stats.pools[pool].hashrate,
|
2014-04-11 16:26:45 -07:00
|
|
|
workerCount: stats.pools[pool].workerCount,
|
2014-04-10 18:33:41 -07:00
|
|
|
blocks: stats.pools[pool].blocks
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_this.statPoolHistory.push(data);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-04-11 16:26:45 -07:00
|
|
|
|
2014-03-19 13:24:29 -07:00
|
|
|
|
2014-03-25 15:21:17 -07:00
|
|
|
this.getGlobalStats = function(callback){
|
2014-03-19 13:24:29 -07:00
|
|
|
|
2014-04-10 18:33:41 -07:00
|
|
|
var statGatherTime = Date.now() / 1000 | 0;
|
|
|
|
|
2014-03-22 12:58:51 -07:00
|
|
|
var allCoinStats = {};
|
2014-03-19 13:24:29 -07:00
|
|
|
|
|
|
|
async.each(redisClients, function(client, callback){
|
2014-04-10 18:33:41 -07:00
|
|
|
var windowTime = (((Date.now() / 1000) - portalConfig.website.stats.hashrateWindow) | 0).toString();
|
2014-03-19 13:24:29 -07:00
|
|
|
var redisCommands = [];
|
|
|
|
|
|
|
|
|
2014-03-22 12:58:51 -07:00
|
|
|
var redisComamndTemplates = [
|
|
|
|
['zremrangebyscore', '_hashrate', '-inf', '(' + windowTime],
|
|
|
|
['zrangebyscore', '_hashrate', windowTime, '+inf'],
|
|
|
|
['hgetall', '_stats'],
|
|
|
|
['scard', '_blocksPending'],
|
|
|
|
['scard', '_blocksConfirmed'],
|
|
|
|
['scard', '_blocksOrphaned']
|
|
|
|
];
|
|
|
|
|
|
|
|
var commandsPerCoin = redisComamndTemplates.length;
|
|
|
|
|
|
|
|
client.coins.map(function(coin){
|
|
|
|
redisComamndTemplates.map(function(t){
|
|
|
|
var clonedTemplates = t.slice(0);
|
2014-04-10 18:33:41 -07:00
|
|
|
clonedTemplates[1] = coin + clonedTemplates[1];
|
2014-03-22 12:58:51 -07:00
|
|
|
redisCommands.push(clonedTemplates);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2014-04-02 14:42:50 -07:00
|
|
|
|
2014-03-19 13:24:29 -07:00
|
|
|
client.client.multi(redisCommands).exec(function(err, replies){
|
|
|
|
if (err){
|
2014-04-10 18:33:41 -07:00
|
|
|
logger.error(logSystem, 'Global', 'error with getting global stats ' + JSON.stringify(err));
|
2014-03-19 13:24:29 -07:00
|
|
|
callback(err);
|
|
|
|
}
|
|
|
|
else{
|
|
|
|
for(var i = 0; i < replies.length; i += commandsPerCoin){
|
2014-03-22 12:58:51 -07:00
|
|
|
var coinName = client.coins[i / commandsPerCoin | 0];
|
2014-03-19 13:24:29 -07:00
|
|
|
var coinStats = {
|
2014-03-22 12:58:51 -07:00
|
|
|
name: coinName,
|
2014-03-25 11:52:11 -07:00
|
|
|
symbol: poolConfigs[coinName].coin.symbol.toUpperCase(),
|
2014-03-22 12:58:51 -07:00
|
|
|
algorithm: poolConfigs[coinName].coin.algorithm,
|
2014-03-19 13:24:29 -07:00
|
|
|
hashrates: replies[i + 1],
|
2014-04-05 17:14:07 -07:00
|
|
|
poolStats: replies[i + 2] != null ? replies[i + 2] : { validShares: 0, validBlocks: 0, invalidShares: 0 },
|
2014-03-22 12:58:51 -07:00
|
|
|
blocks: {
|
|
|
|
pending: replies[i + 3],
|
|
|
|
confirmed: replies[i + 4],
|
|
|
|
orphaned: replies[i + 5]
|
|
|
|
}
|
2014-03-19 13:24:29 -07:00
|
|
|
};
|
2014-03-22 12:58:51 -07:00
|
|
|
allCoinStats[coinStats.name] = (coinStats);
|
2014-03-19 13:24:29 -07:00
|
|
|
}
|
|
|
|
callback();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}, function(err){
|
|
|
|
if (err){
|
2014-04-10 18:33:41 -07:00
|
|
|
logger.error(logSystem, 'Global', 'error getting all stats' + JSON.stringify(err));
|
2014-03-19 13:24:29 -07:00
|
|
|
callback();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var portalStats = {
|
2014-04-10 18:33:41 -07:00
|
|
|
time: statGatherTime,
|
2014-03-19 13:24:29 -07:00
|
|
|
global:{
|
|
|
|
workers: 0,
|
|
|
|
hashrate: 0
|
|
|
|
},
|
2014-04-02 16:36:36 -07:00
|
|
|
algos: {},
|
2014-03-19 13:24:29 -07:00
|
|
|
pools: allCoinStats
|
|
|
|
};
|
|
|
|
|
2014-03-22 12:58:51 -07:00
|
|
|
Object.keys(allCoinStats).forEach(function(coin){
|
|
|
|
var coinStats = allCoinStats[coin];
|
2014-03-19 13:24:29 -07:00
|
|
|
coinStats.workers = {};
|
|
|
|
coinStats.shares = 0;
|
|
|
|
coinStats.hashrates.forEach(function(ins){
|
|
|
|
var parts = ins.split(':');
|
2014-03-30 03:16:02 -07:00
|
|
|
var workerShares = parseFloat(parts[0]);
|
2014-03-19 13:24:29 -07:00
|
|
|
coinStats.shares += workerShares;
|
|
|
|
var worker = parts[1];
|
|
|
|
if (worker in coinStats.workers)
|
2014-04-10 18:33:41 -07:00
|
|
|
coinStats.workers[worker] += workerShares;
|
2014-03-19 13:24:29 -07:00
|
|
|
else
|
2014-04-10 18:33:41 -07:00
|
|
|
coinStats.workers[worker] = workerShares;
|
2014-03-19 13:24:29 -07:00
|
|
|
});
|
2014-03-30 03:16:02 -07:00
|
|
|
var shareMultiplier = algos[coinStats.algorithm].multiplier || 0;
|
2014-04-10 18:33:41 -07:00
|
|
|
var hashratePre = shareMultiplier * coinStats.shares / portalConfig.website.stats.hashrateWindow;
|
2014-04-03 19:55:35 -07:00
|
|
|
coinStats.hashrate = hashratePre | 0;
|
2014-04-10 18:33:41 -07:00
|
|
|
coinStats.workerCount = Object.keys(coinStats.workers).length;
|
|
|
|
portalStats.global.workers += coinStats.workerCount;
|
2014-04-02 14:42:50 -07:00
|
|
|
|
2014-04-02 16:36:36 -07:00
|
|
|
/* algorithm specific global stats */
|
2014-04-10 18:33:41 -07:00
|
|
|
var algo = coinStats.algorithm;
|
2014-04-02 16:36:36 -07:00
|
|
|
if (!portalStats.algos.hasOwnProperty(algo)){
|
|
|
|
portalStats.algos[algo] = {
|
|
|
|
workers: 0,
|
|
|
|
hashrate: 0,
|
|
|
|
hashrateString: null
|
|
|
|
};
|
2014-04-10 18:33:41 -07:00
|
|
|
}
|
2014-04-02 14:42:50 -07:00
|
|
|
portalStats.algos[algo].hashrate += coinStats.hashrate;
|
|
|
|
portalStats.algos[algo].workers += Object.keys(coinStats.workers).length;
|
|
|
|
|
2014-03-30 16:04:54 -07:00
|
|
|
delete coinStats.hashrates;
|
|
|
|
delete coinStats.shares;
|
2014-04-02 16:36:36 -07:00
|
|
|
coinStats.hashrateString = _this.getReadableHashRateString(coinStats.hashrate);
|
|
|
|
});
|
|
|
|
|
|
|
|
Object.keys(portalStats.algos).forEach(function(algo){
|
2014-04-02 19:04:39 -07:00
|
|
|
var algoStats = portalStats.algos[algo];
|
|
|
|
algoStats.hashrateString = _this.getReadableHashRateString(algoStats.hashrate);
|
2014-03-19 13:24:29 -07:00
|
|
|
});
|
2014-03-22 12:58:51 -07:00
|
|
|
|
2014-03-19 13:24:29 -07:00
|
|
|
_this.stats = portalStats;
|
2014-03-22 12:58:51 -07:00
|
|
|
_this.statsString = JSON.stringify(portalStats);
|
2014-04-10 18:33:41 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_this.statHistory.push(portalStats);
|
|
|
|
addStatPoolHistory(portalStats);
|
|
|
|
|
|
|
|
var retentionTime = (((Date.now() / 1000) - portalConfig.website.stats.historicalRetention) | 0);
|
|
|
|
|
|
|
|
for (var i = 0; i < _this.statHistory.length; i++){
|
|
|
|
if (retentionTime < _this.statHistory[i].time){
|
|
|
|
if (i > 0) {
|
|
|
|
_this.statHistory = _this.statHistory.slice(i);
|
|
|
|
_this.statPoolHistory = _this.statPoolHistory.slice(i);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
redisStats.multi([
|
|
|
|
['zadd', 'statHistory', statGatherTime, _this.statsString],
|
|
|
|
['zremrangebyscore', 'statHistory', '-inf', '(' + retentionTime]
|
|
|
|
]).exec(function(err, replies){
|
|
|
|
if (err)
|
|
|
|
logger.error(logSystem, 'Historics', 'Error adding stats to historics ' + JSON.stringify(err));
|
|
|
|
});
|
2014-03-19 13:24:29 -07:00
|
|
|
callback();
|
|
|
|
});
|
|
|
|
|
|
|
|
};
|
2014-04-02 16:36:36 -07:00
|
|
|
|
|
|
|
this.getReadableHashRateString = function(hashrate){
|
|
|
|
var i = -1;
|
2014-04-02 19:04:39 -07:00
|
|
|
var byteUnits = [ ' KH', ' MH', ' GH', ' TH', ' PH' ];
|
2014-04-02 16:36:36 -07:00
|
|
|
do {
|
|
|
|
hashrate = hashrate / 1024;
|
2014-04-02 19:04:39 -07:00
|
|
|
i++;
|
2014-04-02 16:36:36 -07:00
|
|
|
} while (hashrate > 1024);
|
|
|
|
return hashrate.toFixed(2) + byteUnits[i];
|
|
|
|
};
|
|
|
|
|
2014-03-19 13:24:29 -07:00
|
|
|
};
|