Sammy Libre
8 years ago
13 changed files with 590 additions and 57 deletions
@ -0,0 +1,115 @@ |
|||||||
|
package stratum |
||||||
|
|
||||||
|
import ( |
||||||
|
"encoding/json" |
||||||
|
"net/http" |
||||||
|
"sync/atomic" |
||||||
|
"time" |
||||||
|
|
||||||
|
"../util" |
||||||
|
) |
||||||
|
|
||||||
|
func (s *StratumServer) StatsIndex(w http.ResponseWriter, r *http.Request) { |
||||||
|
w.Header().Set("Content-Type", "application/json; charset=UTF-8") |
||||||
|
w.WriteHeader(http.StatusOK) |
||||||
|
|
||||||
|
hashrate, hashrate24h, totalOnline, miners := s.collectMinersStats() |
||||||
|
stats := map[string]interface{}{ |
||||||
|
"miners": miners, |
||||||
|
"hashrate": hashrate, |
||||||
|
"hashrate24h": hashrate24h, |
||||||
|
"totalMiners": len(miners), |
||||||
|
"totalOnline": totalOnline, |
||||||
|
"timedOut": len(miners) - totalOnline, |
||||||
|
"now": util.MakeTimestamp(), |
||||||
|
} |
||||||
|
|
||||||
|
stats["luck"] = s.getLuckStats() |
||||||
|
|
||||||
|
if t := s.currentBlockTemplate(); t != nil { |
||||||
|
stats["height"] = t.Height |
||||||
|
stats["diff"] = t.Difficulty |
||||||
|
roundShares := atomic.LoadInt64(&s.roundShares) |
||||||
|
stats["variance"] = roundShares / t.Difficulty |
||||||
|
stats["template"] = true |
||||||
|
} |
||||||
|
json.NewEncoder(w).Encode(stats) |
||||||
|
} |
||||||
|
|
||||||
|
func (s *StratumServer) collectMinersStats() (float64, float64, int, []interface{}) { |
||||||
|
now := util.MakeTimestamp() |
||||||
|
var result []interface{} |
||||||
|
totalhashrate := float64(0) |
||||||
|
totalhashrate24h := float64(0) |
||||||
|
totalOnline := 0 |
||||||
|
window24h := 24 * time.Hour |
||||||
|
|
||||||
|
for m := range s.miners.Iter() { |
||||||
|
stats := make(map[string]interface{}) |
||||||
|
lastBeat := m.Val.getLastBeat() |
||||||
|
hashrate := m.Val.hashrate(s.estimationWindow) |
||||||
|
hashrate24h := m.Val.hashrate(window24h) |
||||||
|
totalhashrate += hashrate |
||||||
|
totalhashrate24h += hashrate24h |
||||||
|
stats["name"] = m.Key |
||||||
|
stats["hashrate"] = hashrate |
||||||
|
stats["hashrate24h"] = hashrate24h |
||||||
|
stats["lastBeat"] = lastBeat |
||||||
|
stats["validShares"] = atomic.LoadUint64(&m.Val.validShares) |
||||||
|
stats["invalidShares"] = atomic.LoadUint64(&m.Val.invalidShares) |
||||||
|
stats["accepts"] = atomic.LoadUint64(&m.Val.accepts) |
||||||
|
stats["rejects"] = atomic.LoadUint64(&m.Val.rejects) |
||||||
|
if !s.config.Frontend.HideIP { |
||||||
|
stats["ip"] = m.Val.IP |
||||||
|
} |
||||||
|
|
||||||
|
if now-lastBeat > (int64(s.timeout/2) / 1000000) { |
||||||
|
stats["warning"] = true |
||||||
|
} |
||||||
|
if now-lastBeat > (int64(s.timeout) / 1000000) { |
||||||
|
stats["timeout"] = true |
||||||
|
} else { |
||||||
|
totalOnline++ |
||||||
|
} |
||||||
|
result = append(result, stats) |
||||||
|
} |
||||||
|
return totalhashrate, totalhashrate24h, totalOnline, result |
||||||
|
} |
||||||
|
|
||||||
|
func (s *StratumServer) getLuckStats() map[string]interface{} { |
||||||
|
now := util.MakeTimestamp() |
||||||
|
var variance float64 |
||||||
|
var totalVariance float64 |
||||||
|
var blocksCount int |
||||||
|
var totalBlocksCount int |
||||||
|
|
||||||
|
s.blocksMu.Lock() |
||||||
|
defer s.blocksMu.Unlock() |
||||||
|
|
||||||
|
for k, v := range s.blockStats { |
||||||
|
if k >= now-int64(s.luckWindow) { |
||||||
|
blocksCount++ |
||||||
|
variance += v |
||||||
|
} |
||||||
|
if k >= now-int64(s.luckLargeWindow) { |
||||||
|
totalBlocksCount++ |
||||||
|
totalVariance += v |
||||||
|
} else { |
||||||
|
delete(s.blockStats, k) |
||||||
|
} |
||||||
|
} |
||||||
|
if blocksCount != 0 { |
||||||
|
variance = variance / float64(blocksCount) |
||||||
|
} |
||||||
|
if totalBlocksCount != 0 { |
||||||
|
totalVariance = totalVariance / float64(totalBlocksCount) |
||||||
|
} |
||||||
|
result := make(map[string]interface{}) |
||||||
|
result["variance"] = variance |
||||||
|
result["blocksCount"] = blocksCount |
||||||
|
result["window"] = s.config.LuckWindow |
||||||
|
result["totalVariance"] = totalVariance |
||||||
|
result["totalBlocksCount"] = totalBlocksCount |
||||||
|
result["largeWindow"] = s.config.LargeLuckWindow |
||||||
|
return result |
||||||
|
} |
File diff suppressed because one or more lines are too long
@ -0,0 +1,182 @@ |
|||||||
|
<!DOCTYPE html> |
||||||
|
<html lang="en"> |
||||||
|
<head> |
||||||
|
<meta charset="utf-8"> |
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge"> |
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"> |
||||||
|
<title>MoneroProxy</title> |
||||||
|
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> |
||||||
|
<link href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet"> |
||||||
|
<script src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/js/bootstrap.min.js"></script> |
||||||
|
<script src="//cdn.polyfill.io/v2/polyfill.min.js"></script> |
||||||
|
<script src="//cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.5/handlebars.min.js"></script> |
||||||
|
<script src="handlebars-intl.min.js"></script> |
||||||
|
<link href="style.css" rel="stylesheet"> |
||||||
|
<script src="script.js"></script> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<script id="stats-template" type="text/x-handlebars-template"> |
||||||
|
<div class="row marketing"> |
||||||
|
<div class="col-xs-6"> |
||||||
|
<dl class="dl-horizontal"> |
||||||
|
<dt>Hashrate</dt> |
||||||
|
<dd><span class="badge alert-info">{{formatNumber hashrate maximumFractionDigits=4}}</span></dd> |
||||||
|
<dt>Hashrate 24h</dt> |
||||||
|
<dd><span class="badge alert-info">{{formatNumber hashrate24h maximumFractionDigits=4}}</span></dd> |
||||||
|
<dt>Total Miners</dt> |
||||||
|
<dd><span class="badge alert-info">{{totalMiners}}</span></dd> |
||||||
|
<dt>Miners Online</dt> |
||||||
|
<dd><span class="badge alert-success">{{totalOnline}}</span></dd> |
||||||
|
</dl> |
||||||
|
</div> |
||||||
|
<div class="col-xs-6"> |
||||||
|
<dl class="dl-horizontal"> |
||||||
|
{{#if current}} |
||||||
|
<dt>Accepted</dt> |
||||||
|
<dd><span class="badge alert-success">{{formatNumber current.accepts}}</span></dd> |
||||||
|
<dt>Rejected</dt> |
||||||
|
<dd><span class="badge alert-danger">{{formatNumber current.rejects}}</span></dd> |
||||||
|
{{/if}} |
||||||
|
<dt>Miners Timed Out</dt> |
||||||
|
<dd><span class="badge alert-danger">{{formatNumber timedOut}}</span></dd> |
||||||
|
{{#if current.lastSubmissionAt}} |
||||||
|
<dt>Last Submission</dt> |
||||||
|
<dd><span class="badge alert-info">{{formatRelative current.lastSubmissionAt now=now}}</span></dd> |
||||||
|
{{/if}} |
||||||
|
</dl> |
||||||
|
</div> |
||||||
|
<div class="col-xs-12"> |
||||||
|
{{#if errors}} |
||||||
|
<div id="alert" class="alert alert-danger" role="alert"> |
||||||
|
<strong>{{errors}}</strong> |
||||||
|
</div> |
||||||
|
{{/if}} |
||||||
|
{{#if info}} |
||||||
|
<p> |
||||||
|
{{#if template}} |
||||||
|
<strong>Block height:</strong> <span class="label label-primary">{{height}}</span> |
||||||
|
<strong>Difficulty:</strong> <span class="label label-primary">{{formatNumber diff maximumFractionDigits=4}}</span> |
||||||
|
{{/if}} |
||||||
|
{{#if info}} |
||||||
|
{{#if testnet}} |
||||||
|
<span class="label label-danger">TESTNET</span> |
||||||
|
{{else}} |
||||||
|
<span class="label label-success">MAINNET</span> |
||||||
|
{{/if}} |
||||||
|
<strong>Connections:</strong> <span class="label label-primary">{{formatNumber connections}}</span> |
||||||
|
{{/if}} |
||||||
|
</p> |
||||||
|
{{/if}} |
||||||
|
</div> |
||||||
|
<div class="col-xs-12"> |
||||||
|
<p> |
||||||
|
<strong>Blocks {{luck.window}}:</strong> <span class="label label-primary">{{formatNumber luck.blocksCount}}</span> |
||||||
|
<strong>Shares/Diff {{luck.window}}:</strong> |
||||||
|
<span class="label label-primary">{{formatNumber luck.variance style="percent" minimumFractionDigits=2 maximumFractionDigits=2}}</span> |
||||||
|
<strong>Blocks {{luck.largeWindow}}:</strong> <span class="label label-primary">{{formatNumber luck.totalBlocksCount}}</span> |
||||||
|
<strong>Shares/Diff {{luck.largeWindow}}:</strong> |
||||||
|
<span class="label label-primary">{{formatNumber luck.totalVariance style="percent" minimumFractionDigits=2 maximumFractionDigits=2}}</span> |
||||||
|
{{#if template}} |
||||||
|
<strong>Round Progress:</strong> |
||||||
|
<span class="label label-primary">{{formatNumber variance style="percent" minimumFractionDigits=2 maximumFractionDigits=2}}</span> |
||||||
|
{{/if}} |
||||||
|
</p> |
||||||
|
</div> |
||||||
|
<div class="col-xs-12"> |
||||||
|
<h4>Upstream</h4> |
||||||
|
<table class="table table-condensed"> |
||||||
|
<tr> |
||||||
|
<th>Name</th> |
||||||
|
<th>Url</th> |
||||||
|
<th>Accepted</th> |
||||||
|
<th>Rejected</th> |
||||||
|
<th>Fails</th> |
||||||
|
</tr> |
||||||
|
{{#each upstreams}} |
||||||
|
{{#if sick}} |
||||||
|
<tr class="danger"> |
||||||
|
{{else}} |
||||||
|
<tr class="success"> |
||||||
|
{{/if}} |
||||||
|
{{#if current}} |
||||||
|
<td><strong>{{name}}</strong></td> |
||||||
|
{{else}} |
||||||
|
<td>{{name}}</td> |
||||||
|
{{/if}} |
||||||
|
<td>{{url}}</td> |
||||||
|
<td>{{formatNumber accepts}}</td> |
||||||
|
<td><strong>{{formatNumber rejects}}</strong></td> |
||||||
|
<td>{{failsCount}}</td> |
||||||
|
</tr> |
||||||
|
{{/each}} |
||||||
|
</table> |
||||||
|
</div> |
||||||
|
<div class="col-xs-12"> |
||||||
|
<h4>Miners</h4> |
||||||
|
<div class="table-responsive"> |
||||||
|
<table class="table table-condensed"> |
||||||
|
<tr> |
||||||
|
<th>ID</th> |
||||||
|
<th>IP</th> |
||||||
|
<th>HR</th> |
||||||
|
<th>HR 24h</th> |
||||||
|
<th>Last Share</th> |
||||||
|
<th>Accepted</th> |
||||||
|
<th>Rejected</th> |
||||||
|
<th>Blocks Accepted</th> |
||||||
|
<th>Blocks Rejected</th> |
||||||
|
</tr> |
||||||
|
{{#each miners}} |
||||||
|
{{#if timeout}} |
||||||
|
<tr class="danger"> |
||||||
|
{{else}} |
||||||
|
{{#if warning}} |
||||||
|
<tr class="warning"> |
||||||
|
{{else}} |
||||||
|
<tr class="success"> |
||||||
|
{{/if}} |
||||||
|
{{/if}} |
||||||
|
<td>{{name}}</td> |
||||||
|
<td> |
||||||
|
{{#if ip}} |
||||||
|
{{ip}} |
||||||
|
{{else}} |
||||||
|
— |
||||||
|
{{/if}} |
||||||
|
</td> |
||||||
|
<td>{{formatNumber hashrate maximumFractionDigits=4}}</td> |
||||||
|
<td>{{formatNumber hashrate24h maximumFractionDigits=4}}</td> |
||||||
|
<td>{{formatRelative lastBeat now=../now}}</td> |
||||||
|
<td>{{formatNumber validShares}}</td> |
||||||
|
<td><strong>{{formatNumber invalidShares}}</strong></td> |
||||||
|
<td>{{formatNumber accepts}}</td> |
||||||
|
<td>{{formatNumber rejects}}</td> |
||||||
|
</tr> |
||||||
|
{{/each}} |
||||||
|
</table> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</script> |
||||||
|
|
||||||
|
<div class="container"> |
||||||
|
<div class="header clearfix"> |
||||||
|
<h3 class="text-muted">MoneroProxy</h3> |
||||||
|
</div> |
||||||
|
<div id="alert" class="alert alert-danger hide" role="alert"> |
||||||
|
<strong>An error occured while polling proxy state.</strong> |
||||||
|
Make sure proxy is running. |
||||||
|
</div> |
||||||
|
<a name="stats"></a> |
||||||
|
<div id="stats"></div> |
||||||
|
</div> |
||||||
|
<footer class="footer"> |
||||||
|
<div class="container"> |
||||||
|
<p> |
||||||
|
By <a href="https://github.com/sammy007" target="_blank">sammy007</a>.<br/> |
||||||
|
<span><strong>XMR</strong>: 4Aag5kkRHmCFHM5aRUtfB2RF3c5NDmk5CVbGdg6fefszEhhFdXhnjiTCr81YxQ9bsi73CSHT3ZN3p82qyakHwZ2GHYqeaUr</span><br/> |
||||||
|
</p> |
||||||
|
</div> |
||||||
|
</footer> |
||||||
|
</body> |
||||||
|
</html> |
@ -0,0 +1,38 @@ |
|||||||
|
HandlebarsIntl.registerWith(Handlebars); |
||||||
|
|
||||||
|
$(function() { |
||||||
|
window.state = {}; |
||||||
|
var userLang = (navigator.language || navigator.userLanguage) || 'en-US'; |
||||||
|
window.intlData = { locales: userLang }; |
||||||
|
var source = $("#stats-template").html(); |
||||||
|
var template = Handlebars.compile(source); |
||||||
|
refreshStats(template); |
||||||
|
|
||||||
|
setInterval(function() { |
||||||
|
refreshStats(template); |
||||||
|
}, 5000) |
||||||
|
}); |
||||||
|
|
||||||
|
function refreshStats(template) { |
||||||
|
$.getJSON("/stats", function(stats) { |
||||||
|
$("#alert").addClass('hide'); |
||||||
|
|
||||||
|
// Sort miners by ID
|
||||||
|
if (stats.miners) { |
||||||
|
stats.miners = stats.miners.sort(compare) |
||||||
|
} |
||||||
|
// Repaint stats
|
||||||
|
var html = template(stats, { data: { intl: window.intlData } }); |
||||||
|
$('#stats').html(html); |
||||||
|
}).fail(function() { |
||||||
|
$("#alert").removeClass('hide'); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
function compare(a, b) { |
||||||
|
if (a.name < b.name) |
||||||
|
return -1; |
||||||
|
if (a.name > b.name) |
||||||
|
return 1; |
||||||
|
return 0; |
||||||
|
} |
@ -0,0 +1,98 @@ |
|||||||
|
/* Fixes */ |
||||||
|
.dl-horizontal dt { |
||||||
|
white-space: normal; |
||||||
|
} |
||||||
|
|
||||||
|
/* Space out content a bit */ |
||||||
|
body { |
||||||
|
padding-top: 20px; |
||||||
|
padding-bottom: 20px; |
||||||
|
} |
||||||
|
|
||||||
|
/* Custom page header */ |
||||||
|
.header { |
||||||
|
padding-bottom: 20px; |
||||||
|
border-bottom: 1px solid #e5e5e5; |
||||||
|
} |
||||||
|
/* Make the masthead heading the same height as the navigation */ |
||||||
|
.header h3 { |
||||||
|
margin-top: 0; |
||||||
|
margin-bottom: 0; |
||||||
|
line-height: 40px; |
||||||
|
} |
||||||
|
|
||||||
|
a.logo { |
||||||
|
background: #04191f; |
||||||
|
padding: 3px; |
||||||
|
text-decoration: none; |
||||||
|
-webkit-border-radius: 3px; |
||||||
|
-moz-border-radius: 3px; |
||||||
|
border-radius: 3px; |
||||||
|
} |
||||||
|
span.logo-1 { |
||||||
|
font-weight: 700; |
||||||
|
color: #1994b8; |
||||||
|
} |
||||||
|
span.logo-2 { |
||||||
|
font-weight: 300; |
||||||
|
color: #fff; |
||||||
|
} |
||||||
|
span.logo-3 { |
||||||
|
color: #fff; |
||||||
|
font-weight: 100; |
||||||
|
} |
||||||
|
|
||||||
|
/* Custom page footer */ |
||||||
|
.footer { |
||||||
|
padding-top: 15px; |
||||||
|
color: #777; |
||||||
|
border-top: 1px solid #e5e5e5; |
||||||
|
} |
||||||
|
|
||||||
|
/* Customize container */ |
||||||
|
@media (min-width: 1200px) { |
||||||
|
.container { |
||||||
|
width: 970px; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.container-narrow > hr { |
||||||
|
margin: 30px 0; |
||||||
|
} |
||||||
|
|
||||||
|
/* Main marketing message and sign up button */ |
||||||
|
.jumbotron { |
||||||
|
text-align: center; |
||||||
|
border-bottom: 1px solid #e5e5e5; |
||||||
|
} |
||||||
|
.jumbotron .btn { |
||||||
|
padding: 14px 24px; |
||||||
|
font-size: 21px; |
||||||
|
} |
||||||
|
|
||||||
|
/* Supporting marketing content */ |
||||||
|
.marketing { |
||||||
|
margin: 40px 0; |
||||||
|
} |
||||||
|
.marketing p + h4 { |
||||||
|
margin-top: 28px; |
||||||
|
} |
||||||
|
|
||||||
|
/* Responsive: Portrait tablets and up */ |
||||||
|
@media screen and (min-width: 768px) { |
||||||
|
/* Remove the padding we set earlier */ |
||||||
|
.header, |
||||||
|
.marketing, |
||||||
|
.footer { |
||||||
|
padding-right: 0; |
||||||
|
padding-left: 0; |
||||||
|
} |
||||||
|
/* Space out the masthead */ |
||||||
|
.header { |
||||||
|
margin-bottom: 30px; |
||||||
|
} |
||||||
|
/* Remove the bottom border on the jumbotron for visual effect */ |
||||||
|
.jumbotron { |
||||||
|
border-bottom: 0; |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue