Skip to content

Commit

Permalink
Added more detailed individual miner stats and block maturity display
Browse files Browse the repository at this point in the history
  • Loading branch information
zone117x committed May 21, 2014
1 parent a5034bd commit e5555f6
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 111 deletions.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,14 @@ Comes with lightweight example front-end script which uses the pool's AJAX API.
* Current block height
* Network hashrate
* Pool hashrate
* Each miners' hashrate
* Each miners' individual stats (hashrate, shares submitted, total paid, etc)
* Blocks found (pending, confirmed, and orphaned)
* An easily extendable, responsive, light-weight front-end using API to display data
* Worker login validation (make sure miners are using proper wallet addresses for mining)


#### Under Development

* More stats for individual worker (total shares submitted, total paid out)
* Stratum protocol (push-based TCP sockets) to help reduce server load

### Community / Support
Expand Down
72 changes: 52 additions & 20 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
#yourAddressDisplay > span {
font-family: monospace;
}
#yourHashrateHolder, #yourAddressDisplay{
.yourStats{
display: none;
}
#addressError{
Expand All @@ -82,19 +82,26 @@
font-size: 0.9em;
}

#blocksCountHolder > h4{
.blocksStatHolder > h4{
display: inline-block;
margin-right: 10px;
}
#blocksCountHolder > span{
.blocksStatHolder > span{
border-radius: 5px;
padding: 5px 10px;
border: 1px solid #e5e5e5;
margin-right: 10px;
}
#blocksCountHolder > span > span{
.blocksStatHolder > span > span{

}


#blocks_rows > tr > td:nth-child(4){
font-family: monospace;
font-size: 0.75em;
}

#page_getting_started > .stats:first-of-type{
margin-bottom: 15px;
}
Expand Down Expand Up @@ -173,7 +180,7 @@

pageRouter();

$('#networkLastBlockFound,#poolLastBlockFound').timeago();
$('#networkLastBlockFound,#poolLastBlockFound,#yourLastShare').timeago();

var $miningPorts = $('#miningPorts');
var miningPortTemplate = $miningPorts.html();
Expand All @@ -191,8 +198,7 @@
}

$('#addressError').hide();
$('#yourHashrateHolder').hide();
$('#yourAddressDisplay').hide();
$('.yourStats').hide();
$(this).html('<i class="fa fa-refresh fa-spin"></i> Searching...');
if (addressEventSource) addressEventSource.close();
addressEventSource = new EventSource(api + '/stats_address?address=' + address);
Expand All @@ -201,13 +207,17 @@
var data = JSON.parse(e.data);
if (data.stats){
$('#addressError').hide();
$('#yourHashrateHolder').show().children("span").text(data.stats + '/sec');
$('#yourAddressDisplay').show().children("span").text(address);
$('#yourAddressDisplay').text(address);
$('#yourLastShare').timeago('update', new Date(parseInt(data.stats.lastShare) * 1000).toISOString());
$('#yourHashrateHolder').text((data.stats.hashrate || '0 H') + '/sec');
$('#yourHashes').text(data.stats.hashes || '0');
$('#yourPaid').text(data.stats.paid);

$('.yourStats').show();
docCookies.setItem('mining_address', address, Infinity);
return;
}
$('#yourHashrateHolder').hide();
$('#yourAddressDisplay').hide();
$('.yourStats').hide();
$('#addressError').text(data.error).show();
});
addressEventSource.addEventListener('open', function(e){
Expand Down Expand Up @@ -276,13 +286,24 @@

$('#emailLink').attr('href', 'mailto:' + stats.config.email).text(stats.config.email);

renderBlocks(stats.pool.blocks);
$('#blocksMaturityCount').text(stats.config.depth);

renderBlocks(stats.pool.blocks, stats.config.depth, stats.network.height);

$('#blocks_rows').find('tr[class=""]').each(function(){
var height = parseInt(this.children[0].innerText);
this.children[1].innerText = getMaturity(stats.config.depth, stats.network.height, height);
});

}

function getMaturity(depth, chainHeight, blockHeight){
return '' + depth - (chainHeight - blockHeight) + ' to go';
}

var lastBlocksJson = '';

function renderBlocks(blocksResults){
function renderBlocks(blocksResults, depth, chainHeight){
var blocks = [];
for (var status in blocksResults){
var blockArray = blocksResults[status];
Expand Down Expand Up @@ -316,7 +337,13 @@

for (var i = 0; i < blocks.length; i++){
var block = blocks[i];
rows += ('<tr title="' + block[0] + '" class="' + blockStatusClasses[block[0]] + '"><td>' + block[1] + '</td><td>' + block[2] + '</td><td>' + block[3] + '</td><td>' + formatDate(block[4]) + '</td></tr>');
rows += ('<tr title="' + block[0] + '" class="' + blockStatusClasses[block[0]] + '">' +
'<td>' + block[1] + '</td>' +
'<td>' + (block[0] === 'pending' ? getMaturity(depth, chainHeight, blocks[1]) : '') + '</td>' +
'<td>' + block[2] + '</td>' +
'<td>' + block[3] + '</td>' +
'<td>' + formatDate(block[4]) + '</td>' +
'</tr>');
}

$('#blocks_rows').empty().append(rows);
Expand Down Expand Up @@ -443,25 +470,30 @@ <h3>Your Stats</h3>
<input class="form-control" id="yourStatsInput" type="text">
<button class="btn btn-default" id="lookUp"><i class="fa fa-search"></i> Lookup</button>
<div id="addressError"></div>
<div id="yourAddressDisplay"><i class="fa fa-key"></i> Address: <span></span></div>
<div id="yourHashrateHolder"><i class="fa fa-tachometer"></i> Hash Rate: <span></span></div>
<div class="yourStats"><i class="fa fa-key"></i> Address: <span id="yourAddressDisplay"></span></div>
<div class="yourStats"><i class="fa fa-clock-o"></i> Last Share Submitted: <span id="yourLastShare"></span></div>
<div class="yourStats"><i class="fa fa-tachometer"></i> Hash Rate: <span id="yourHashrateHolder"></span></div>
<div class="yourStats"><i class="fa fa-cloud-upload"></i> Total Hashes Submitted: <span id="yourHashes"></span></div>
<div class="yourStats"><i class="fa fa-money"></i> Total Paid: <span id="yourPaid"></span></div>
</div>

</div>

<div class="page" id="page_pool_blocks">
<div id="blocksCountHolder">
<div class="blocksStatHolder">
<h4>Block Candidates</h4>
<span>Pending: <span id="blocksCountPending"></span></span>
<span class="bg-success">Unlocked: <span id="blocksCountUnlocked"></span></span>
<span class="bg-danger">Orphaned: <span id="blocksCountOrphaned"></span></span>
<span><span id="blocksCountPending"></span> Pending Maturity</span>
<span class="bg-success"><span id="blocksCountUnlocked"></span> Unlocked</span>
<span class="bg-danger"><span id="blocksCountOrphaned"></span> Orphaned</span>
<span class="bg-info">Maturity requires <span id="blocksMaturityCount"></span> blocks</span>
</div>
<hr>
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th><i class="fa fa-bars"></i> Height</th>
<th title="How many more blocks network must mine before this block is matured"><i class="fa fa-link"></i> Maturity</th>
<th><i class="fa fa-unlock-alt"></i> Difficulty</th>
<th><i class="fa fa-paw"></i> Block Hash</th>
<th><i class="fa fa-clock-o"></i> Time Found</th>
Expand Down
101 changes: 66 additions & 35 deletions lib/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ var redisCommands = [
['hgetall', config.coin + ':stats']
];

var coinDecimals = config.coinUnits.toString().length - 1;

var currentStats = "";
var currentStatsCompressed = "";

Expand Down Expand Up @@ -129,13 +131,18 @@ function collectStats(){
fee: config.payments.poolFee,
email: config.email,
coin: config.coin,
coinUnits: config.coinUnits,
symbol: config.symbol,
easyminerDownload: config.easyminerDownload,
irc: config.irc
irc: config.irc,
depth: config.payments.depth
});
}
}, function(error, results){
if (!error){
if (error){
log('error', 'Error collecting all stats');
}
else{
currentStats = JSON.stringify(results);
zlib.deflateRaw(currentStats, function(error, result){
currentStatsCompressed = result;
Expand Down Expand Up @@ -170,27 +177,72 @@ function broadcastLiveStats(){
}
//});

var redisCommands = [];
for (var address in addressConnections){
var res = addressConnections[address];
var stats = minerStats[address];
if (!stats) res.end();
else res.write('data: ' + JSON.stringify({stats: stats}) + '\n\n');
redisCommands.push(['hgetall', config.coin + ':workers:' + address]);
}
redisClient.multi(redisCommands).exec(function(error, replies){

var addresses = Object.keys(addressConnections);



for (var i = 0; i < addresses.length; i++){
var address = addresses[i];
var stats = replies[i];
var res = addressConnections[address];
res.write('data: ' + formatMinerStats(stats, address) + '\n\n');
}
});
}

function formatMinerStats(redisData, address){

collectStats();
redisData.hashrate = minerStats[address];

var server = http.createServer(function(request, response){
var paid = ((redisData.paid || 0) / config.coinUnits).toFixed(coinDecimals);
redisData.paid = paid + ' ' + config.symbol;

return JSON.stringify({stats: redisData});
}


function handleMinerStats(urlParts, response){
response.writeHead(200, {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
response.write('\n');
var address = urlParts.query.address;

redisClient.hgetall(config.coin + ':workers:' + address, function(error, stats){
if (!stats){
var error = JSON.stringify({error: 'not found'});
var statData = 'data: ' + error + '\n\n';
response.end(statData);
return;
}

response.write('data: ' + formatMinerStats(stats, address) + '\n\n');
addressConnections[address] = response;
response.on("close", function() {
delete addressConnections[address];
});
});

}

var origin = (request.headers.origin || "*");

collectStats();

var server = http.createServer(function(request, response){

if (request.method.toUpperCase() === "OPTIONS"){

response.writeHead("204", "No Content", {
"access-control-allow-origin": origin,
"access-control-allow-origin": '*',
"access-control-allow-methods": "GET, POST, PUT, DELETE, OPTIONS",
"access-control-allow-headers": "content-type, accept",
"access-control-max-age": 10, // Seconds.
Expand All @@ -201,14 +253,13 @@ var server = http.createServer(function(request, response){
}



var urlParts = url.parse(request.url, true);

switch(urlParts.pathname){
case '/stats':
var reply = currentStatsCompressed;
response.writeHead("200", {
'Access-Control-Allow-Origin': origin,
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json',
'Content-Encoding': 'deflate',
'Content-Length': reply.length
Expand All @@ -217,7 +268,7 @@ var server = http.createServer(function(request, response){
break;
case '/live_stats':
response.writeHead(200, {
'Access-Control-Allow-Origin': origin,
'Access-Control-Allow-Origin': '*',
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
//'Content-Encoding': 'deflate',
Expand All @@ -231,31 +282,11 @@ var server = http.createServer(function(request, response){
});
break;
case '/stats_address':
response.writeHead(200, {
'Access-Control-Allow-Origin': origin,
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
response.write('\n');
var address = urlParts.query.address;
var stats = minerStats[address];

if (!stats){
var error = JSON.stringify({error: 'not found'});
var statData = 'data: ' + error + '\n\n';
response.end(statData);
return;
}
response.write('data: ' + JSON.stringify({stats:stats}) + '\n\n');
addressConnections[address] = response;
response.on("close", function() {
delete addressConnections[address];
});
handleMinerStats(urlParts, response);
break;
default:
response.writeHead(404, {
'Access-Control-Allow-Origin': origin
'Access-Control-Allow-Origin': '*'
});
response.end('Invalid API call');
break;
Expand Down
Loading

0 comments on commit e5555f6

Please sign in to comment.