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
View
@@ -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
View
@@ -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)
View
@@ -54,6 +54,7 @@
"statsCollectInterval": "5s",
"hashrateWindow": "30m",
"hashrateLargeWindow": "3h",
+ "luckWindow": [64, 128, 256],
"payments": 30,
"blocks": 50
},
View
@@ -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}}
View
@@ -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.