Skip to content
Permalink
Browse files

Add luck stats

  • Loading branch information
sammy007 committed Jun 14, 2016
1 parent 0725b54 commit b2f7055e28af3c69cc97e35bdaaaf54849ac8b20
Showing with 118 additions and 24 deletions.
  1. +2 −0 README.md
  2. +15 −3 api/server.go
  3. +1 −0 config.example.json
  4. +77 −21 storage/redis.go
  5. +3 −0 www/app/templates/blocks.hbs
  6. +20 −0 www/app/templates/luck.hbs
@@ -212,6 +212,8 @@ otherwise you will get errors on start because of JSON comments.**
"hashrateWindow": "30m",
// Long and precise hashrate from shares, 3h is cool, keep it
"hashrateLargeWindow": "3h",
// Collect stats for shares/diff ratio for this number of blocks
"luckWindow": [64, 128, 256],
// Max number of payments to display in frontend
"payments": 50,
// Max numbers of blocks to display in frontend
@@ -4,6 +4,7 @@ import (
"encoding/json"
"log"
"net/http"
"sort"
"strings"
"sync"
"sync/atomic"
@@ -21,6 +22,7 @@ type ApiConfig struct {
StatsCollectInterval string `json:"statsCollectInterval"`
HashrateWindow string `json:"hashrateWindow"`
HashrateLargeWindow string `json:"hashrateLargeWindow"`
LuckWindow []int `json:"luckWindow"`
Payments int64 `json:"payments"`
Blocks int64 `json:"blocks"`
PurgeOnly bool `json:"purgeOnly"`
@@ -70,6 +72,8 @@ func (s *ApiServer) Start() {
purgeTimer := time.NewTimer(purgeIntv)
log.Printf("Set purge interval to %v", purgeIntv)

sort.Ints(s.config.LuckWindow)

if s.config.PurgeOnly {
s.purgeStale()
} else {
@@ -133,10 +137,17 @@ func (s *ApiServer) collectStats() {
stats, err := s.backend.CollectStats(s.hashrateWindow, s.config.Blocks, s.config.Payments)
if err != nil {
log.Printf("Failed to fetch stats from backend: %v", err)
} else {
log.Printf("Stats collection finished %s", time.Since(start))
s.stats.Store(stats)
return
}
if len(s.config.LuckWindow) > 0 {
stats["luck"], err = s.backend.CollectLuckStats(s.config.LuckWindow)
if err != nil {
log.Printf("Failed to fetch luck stats from backend: %v", err)
return
}
}
s.stats.Store(stats)
log.Printf("Stats collection finished %s", time.Since(start))
}

func (s *ApiServer) StatsIndex(w http.ResponseWriter, r *http.Request) {
@@ -205,6 +216,7 @@ func (s *ApiServer) BlocksIndex(w http.ResponseWriter, r *http.Request) {
reply["immatureTotal"] = stats["immatureTotal"]
reply["candidates"] = stats["candidates"]
reply["candidatesTotal"] = stats["candidatesTotal"]
reply["luck"] = stats["luck"]
}

err := json.NewEncoder(w).Encode(reply)
@@ -54,6 +54,7 @@
"statsCollectInterval": "5s",
"hashrateWindow": "30m",
"hashrateLargeWindow": "3h",
"luckWindow": [64, 128, 256],
"payments": 30,
"blocks": 50
},
@@ -28,7 +28,7 @@ type RedisClient struct {
type BlockData struct {
Height int64 `json:"height"`
Timestamp int64 `json:"timestamp"`
Difficulty string `json:"difficulty"`
Difficulty int64 `json:"difficulty"`
TotalShares int64 `json:"shares"`
Uncle bool `json:"uncle"`
UncleHeight int64 `json:"uncleHeight"`
@@ -751,6 +751,60 @@ func (r *RedisClient) CollectWorkersStats(sWindow, lWindow time.Duration, login
return stats, nil
}

func (r *RedisClient) CollectLuckStats(windows []int) (map[string]interface{}, error) {
stats := make(map[string]interface{})

tx := r.client.Multi()
defer tx.Close()

max := int64(windows[len(windows)-1])

cmds, err := tx.Exec(func() error {
tx.ZRevRangeWithScores(r.formatKey("blocks", "immature"), 0, -1)
tx.ZRevRangeWithScores(r.formatKey("blocks", "matured"), 0, max-1)
return nil
})
if err != nil {
return stats, err
}
blocks := convertBlockResults(cmds[0].(*redis.ZSliceCmd), cmds[1].(*redis.ZSliceCmd))

calcLuck := func(max int) (int, float64, float64, float64) {
var total int
var sharesDiff, uncles, orphans float64
for i, block := range blocks {
if i > (max - 1) {
break
}
if block.Uncle {
uncles++
}
if block.Orphan {
orphans++
}
sharesDiff += float64(block.TotalShares) / float64(block.Difficulty)
total++
}
if total > 0 {
sharesDiff /= float64(total)
uncles /= float64(total)
orphans /= float64(total)
}
return total, sharesDiff, uncles, orphans
}
for _, max := range windows {
total, sharesDiff, uncleRate, orphanRate := calcLuck(max)
row := map[string]interface{}{
"luck": sharesDiff, "uncleRate": uncleRate, "orphanRate": orphanRate,
}
stats[strconv.Itoa(total)] = row
if total < max {
break
}
}
return stats, nil
}

func convertCandidateResults(raw *redis.ZSliceCmd) []*BlockData {
var result []*BlockData
for _, v := range raw.Val() {
@@ -763,34 +817,36 @@ func convertCandidateResults(raw *redis.ZSliceCmd) []*BlockData {
block.PowHash = fields[1]
block.MixDigest = fields[2]
block.Timestamp, _ = strconv.ParseInt(fields[3], 10, 64)
block.Difficulty = fields[4]
block.Difficulty, _ = strconv.ParseInt(fields[4], 10, 64)
block.TotalShares, _ = strconv.ParseInt(fields[5], 10, 64)
block.candidateKey = v.Member.(string)
result = append(result, &block)
}
return result
}

func convertBlockResults(raw *redis.ZSliceCmd) []*BlockData {
func convertBlockResults(rows ...*redis.ZSliceCmd) []*BlockData {
var result []*BlockData
for _, v := range raw.Val() {
// "uncleHeight:orphan:nonce:blockHash:timestamp:diff:totalShares:rewardInWei"
block := BlockData{}
block.Height = int64(v.Score)
block.RoundHeight = block.Height
fields := strings.Split(v.Member.(string), ":")
block.UncleHeight, _ = strconv.ParseInt(fields[0], 10, 64)
block.Uncle = block.UncleHeight > 0
block.Orphan, _ = strconv.ParseBool(fields[1])
block.Nonce = fields[2]
block.Hash = fields[3]
block.Timestamp, _ = strconv.ParseInt(fields[4], 10, 64)
block.Difficulty = fields[5]
block.TotalShares, _ = strconv.ParseInt(fields[6], 10, 64)
block.RewardString = fields[7]
block.ImmatureReward = fields[7]
block.immatureKey = v.Member.(string)
result = append(result, &block)
for _, row := range rows {
for _, v := range row.Val() {
// "uncleHeight:orphan:nonce:blockHash:timestamp:diff:totalShares:rewardInWei"
block := BlockData{}
block.Height = int64(v.Score)
block.RoundHeight = block.Height
fields := strings.Split(v.Member.(string), ":")
block.UncleHeight, _ = strconv.ParseInt(fields[0], 10, 64)
block.Uncle = block.UncleHeight > 0
block.Orphan, _ = strconv.ParseBool(fields[1])
block.Nonce = fields[2]
block.Hash = fields[3]
block.Timestamp, _ = strconv.ParseInt(fields[4], 10, 64)
block.Difficulty, _ = strconv.ParseInt(fields[5], 10, 64)
block.TotalShares, _ = strconv.ParseInt(fields[6], 10, 64)
block.RewardString = fields[7]
block.ImmatureReward = fields[7]
block.immatureKey = v.Member.(string)
result = append(result, &block)
}
}
return result
}
@@ -8,6 +8,9 @@
</div>
</div>
<div class="container">
{{#if model.luck}}
{{partial "luck"}}
{{/if}}
<ul class="nav nav-tabs">
{{#active-li currentWhen='blocks.index' role='presentation'}}
{{#link-to 'blocks.index'}}Blocks <span class="badge alert-success">{{format-number model.maturedTotal}}</span>{{/link-to}}
@@ -0,0 +1,20 @@
<table class="table table-condensed table-striped">
<thead>
<tr>
<th>Blocks</th>
<th>Shares/Diff</th>
<th>Uncle Rate</th>
<th>Orphan Rate</th>
</tr>
</thead>
<tbody>
{{#each-in model.luck as |total row|}}
<tr>
<td>{{total}}</td>
<td>{{format-number row.luck style='percent'}}</td>
<td>{{format-number row.uncleRate style='percent'}}</td>
<td>{{format-number row.orphanRate style='percent'}}</td>
</tr>
{{/each-in}}
</tbody>
</table>

0 comments on commit b2f7055

Please sign in to comment.
You can’t perform that action at this time.