refactor-code-modules #1
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
logs/
|
||||
badgerdb/
|
||||
notes.md
|
||||
|
78
database/db.go
Normal file
78
database/db.go
Normal file
@ -0,0 +1,78 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/ostafen/clover/v2"
|
||||
"github.com/ostafen/clover/v2/document"
|
||||
c "github.com/ostafen/clover/v2/query"
|
||||
badgerstore "github.com/ostafen/clover/v2/store/badger"
|
||||
)
|
||||
|
||||
const (
|
||||
CollectionName = "shares"
|
||||
)
|
||||
|
||||
func InitDatabase() (*clover.DB, error) {
|
||||
store, err := badgerstore.Open("badgerdb")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open BadgerDB store: %v", err)
|
||||
}
|
||||
|
||||
db, err := clover.OpenWithStore(store)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open CloverDB: %v", err)
|
||||
}
|
||||
|
||||
// Ensure collection exists
|
||||
hasCollection, err := db.HasCollection(CollectionName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to check collection: %v", err)
|
||||
}
|
||||
|
||||
if !hasCollection {
|
||||
if err := db.CreateCollection(CollectionName); err != nil {
|
||||
return nil, fmt.Errorf("failed to create collection: %v", err)
|
||||
}
|
||||
if err := db.CreateIndex(CollectionName, "CreateDate"); err != nil {
|
||||
return nil, fmt.Errorf("failed to create index: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func GetHighestShareInRange(db *clover.DB, collection string, since time.Time) (*document.Document, error) {
|
||||
// Convert `since` to the format in `createdate`
|
||||
lower := since.Unix()
|
||||
upper := time.Now().Unix()
|
||||
|
||||
// Filter by timestamp range
|
||||
criteria := c.Field("CreateDate").GtEq(fmt.Sprint(lower)).
|
||||
And(c.Field("CreateDate").LtEq(fmt.Sprint(upper)))
|
||||
|
||||
// Query sorted by "sdiff" descending, limit 1
|
||||
results, err := db.FindAll(c.NewQuery(collection).
|
||||
Where(criteria).
|
||||
Sort(c.SortOption{Field: "SDiff", Direction: -1}).
|
||||
Limit(1))
|
||||
|
||||
if err != nil || len(results) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
return results[0], nil
|
||||
}
|
||||
|
||||
func PrintAllHashes(db *clover.DB) {
|
||||
docs, err := db.FindAll(c.NewQuery(CollectionName))
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to read from collection: %v", err)
|
||||
}
|
||||
|
||||
for _, doc := range docs {
|
||||
hash := doc.Get("Hash")
|
||||
fmt.Println(hash)
|
||||
}
|
||||
}
|
12
go.mod
12
go.mod
@ -1,24 +1,16 @@
|
||||
module github.com/pikami/pool-stats
|
||||
module pool-stats
|
||||
|
||||
go 1.24.3
|
||||
|
||||
require (
|
||||
github.com/dgraph-io/badger/v3 v3.2103.5
|
||||
github.com/ostafen/clover/v2 v2.0.0-alpha.3.0.20250212110647-35f6fd38bde2
|
||||
)
|
||||
require github.com/ostafen/clover/v2 v2.0.0-alpha.3.0.20250212110647-35f6fd38bde2
|
||||
|
||||
require (
|
||||
github.com/cespare/xxhash v1.1.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/dgraph-io/badger/v4 v4.5.1 // indirect
|
||||
github.com/dgraph-io/ristretto v0.2.0 // indirect
|
||||
github.com/dgraph-io/ristretto/v2 v2.1.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/gofrs/uuid/v5 v5.3.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/golang/snappy v1.0.0 // indirect
|
||||
github.com/google/flatbuffers v25.2.10+incompatible // indirect
|
||||
github.com/google/orderedcode v0.0.1 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
|
81
go.sum
81
go.sum
@ -1,57 +1,35 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4=
|
||||
github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgraph-io/badger/v3 v3.2103.5 h1:ylPa6qzbjYRQMU6jokoj4wzcaweHylt//CH0AKt0akg=
|
||||
github.com/dgraph-io/badger/v3 v3.2103.5/go.mod h1:4MPiseMeDQ3FNCYwRbbcBOGJLf5jsE0PPFzRiKjtcdw=
|
||||
github.com/dgraph-io/badger/v4 v4.5.1 h1:7DCIXrQjo1LKmM96YD+hLVJ2EEsyyoWxJfpdd56HLps=
|
||||
github.com/dgraph-io/badger/v4 v4.5.1/go.mod h1:qn3Be0j3TfV4kPbVoK0arXCD1/nr1ftth6sbL5jxdoA=
|
||||
github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA=
|
||||
github.com/dgraph-io/ristretto v0.2.0 h1:XAfl+7cmoUDWW/2Lx8TGZQjjxIQ2Ley9DSf52dru4WE=
|
||||
github.com/dgraph-io/ristretto v0.2.0/go.mod h1:8uBHCU/PBV4Ag0CJrP47b9Ofby5dqWNh4FicAdoqFNU=
|
||||
github.com/dgraph-io/ristretto/v2 v2.1.0 h1:59LjpOJLNDULHh8MC4UaegN52lC4JnO2dITsie/Pa8I=
|
||||
github.com/dgraph-io/ristretto/v2 v2.1.0/go.mod h1:uejeqfYXpUomfse0+lO+13ATz4TypQYLJZzBSAemuB4=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
|
||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/gofrs/uuid/v5 v5.3.1 h1:aPx49MwJbekCzOyhZDjJVb0hx3A0KLjlbLx6p2gY0p0=
|
||||
github.com/gofrs/uuid/v5 v5.3.1/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
@ -60,12 +38,6 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
|
||||
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q=
|
||||
github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
@ -74,103 +46,60 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/orderedcode v0.0.1 h1:UzfcAexk9Vhv8+9pNOgRu41f16lHq725vPwnSeiG/Us=
|
||||
github.com/google/orderedcode v0.0.1/go.mod h1:iVyU4/qPKHY5h/wSd6rZZCDcLJNxiWO6dvsYES2Sb20=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/ostafen/clover/v2 v2.0.0-alpha.3.0.20250212110647-35f6fd38bde2 h1:OzU5Rt6T/5Zba/mjRMUyTnAwRtfsRz8GLvJhtY65SU4=
|
||||
github.com/ostafen/clover/v2 v2.0.0-alpha.3.0.20250212110647-35f6fd38bde2/go.mod h1:sKfSpDjks1LEor8MMuu43yzkFmU3cA4W42UNhaoPM/Y=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk=
|
||||
go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
||||
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@ -180,21 +109,13 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
@ -211,8 +132,6 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
100
ingest/ingest.go
Normal file
100
ingest/ingest.go
Normal file
@ -0,0 +1,100 @@
|
||||
package ingest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ostafen/clover/v2"
|
||||
"github.com/ostafen/clover/v2/document"
|
||||
|
||||
"pool-stats/database"
|
||||
"pool-stats/models"
|
||||
)
|
||||
|
||||
const logsDir = "/home/pk/pro/pkstats/logs"
|
||||
|
||||
func WatchAndIngest(db *clover.DB) {
|
||||
ticker := time.NewTicker(30 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
<-ticker.C
|
||||
IngestClosedBlocks(db)
|
||||
}
|
||||
}
|
||||
|
||||
func IngestClosedBlocks(db *clover.DB) {
|
||||
entries, err := os.ReadDir(logsDir)
|
||||
if err != nil {
|
||||
log.Println("Error reading logsDir:", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Find block-like directories (starts with 000)
|
||||
blockDirs := []os.DirEntry{}
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() && strings.HasPrefix(entry.Name(), "000") {
|
||||
blockDirs = append(blockDirs, entry)
|
||||
}
|
||||
}
|
||||
|
||||
if len(blockDirs) <= 1 {
|
||||
return // Nothing to ingest
|
||||
}
|
||||
|
||||
// Sort dirs alphabetically (ascending), last is newest
|
||||
sort.Slice(blockDirs, func(i, j int) bool {
|
||||
return blockDirs[i].Name() < blockDirs[j].Name()
|
||||
})
|
||||
|
||||
// Ingest all except last (current block dir)
|
||||
for _, dir := range blockDirs[:len(blockDirs)-1] {
|
||||
IngestBlockDir(db, filepath.Join(logsDir, dir.Name()))
|
||||
_ = os.RemoveAll(filepath.Join(logsDir, dir.Name()))
|
||||
}
|
||||
}
|
||||
|
||||
func IngestBlockDir(db *clover.DB, dirPath string) {
|
||||
files, err := os.ReadDir(dirPath)
|
||||
if err != nil {
|
||||
log.Printf("Failed to read block dir %s: %v", dirPath, err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
if !strings.HasSuffix(f.Name(), ".sharelog") {
|
||||
continue
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(filepath.Join(dirPath, f.Name()))
|
||||
if err != nil {
|
||||
log.Println("Failed to read file:", err)
|
||||
continue
|
||||
}
|
||||
|
||||
lines := strings.Split(string(data), "\n")
|
||||
for _, line := range lines {
|
||||
if strings.TrimSpace(line) == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
var share models.ShareLog
|
||||
if err := json.Unmarshal([]byte(line), &share); err != nil {
|
||||
log.Println("JSON parse error:", err)
|
||||
continue
|
||||
}
|
||||
|
||||
doc := document.NewDocumentOf(&share)
|
||||
if _, err := db.InsertOne(database.CollectionName, doc); err != nil {
|
||||
log.Println("DB insert error:", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("Ingested and deleted %s", dirPath)
|
||||
}
|
284
main.go
284
main.go
@ -1,300 +1,36 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/ostafen/clover/v2"
|
||||
"github.com/ostafen/clover/v2/document"
|
||||
c "github.com/ostafen/clover/v2/query"
|
||||
badgerstore "github.com/ostafen/clover/v2/store/badger"
|
||||
"pool-stats/database"
|
||||
"pool-stats/ingest"
|
||||
"pool-stats/web"
|
||||
)
|
||||
|
||||
const logsDir = "/home/pk/pro/pkstats/logs"
|
||||
const collectionName = "shares"
|
||||
|
||||
type ShareLog struct {
|
||||
WorkInfoID uint64 `json:"workinfoid"` // ID of the associated work unit
|
||||
ClientID int `json:"clientid"` // ID of the client/miner connection
|
||||
Enonce1 string `json:"enonce1"` // Extra nonce 1 (assigned by pool)
|
||||
Nonce2 string `json:"nonce2"` // Extra nonce 2 (sent by miner)
|
||||
Nonce string `json:"nonce"` // Nonce used to generate the solution
|
||||
NTime string `json:"ntime"` // Network time used in the block
|
||||
Diff float64 `json:"diff"` // Target difficulty for the share
|
||||
SDiff float64 `json:"sdiff"` // Share difficulty achieved
|
||||
Hash string `json:"hash"` // Resulting hash from the share
|
||||
Result bool `json:"result"` // Was this share a valid result
|
||||
Errn int `json:"errn"` // Error code (0 = no error)
|
||||
|
||||
CreateDate string `json:"createdate"` // Timestamp: "seconds,nanoseconds"
|
||||
CreateBy string `json:"createby"` // Origin of share creation ("code")
|
||||
CreateCode string `json:"createcode"` // Component that created this entry
|
||||
CreateInet string `json:"createinet"` // IP + port of submit origin
|
||||
|
||||
WorkerName string `json:"workername"` // Full worker name (username.worker)
|
||||
Username string `json:"username"` // User's Bitcoin address
|
||||
Address string `json:"address"` // IP address of the worker
|
||||
Agent string `json:"agent"` // Miner agent string (e.g., bitaxe/BM1370)
|
||||
}
|
||||
|
||||
type ShareStat struct {
|
||||
Label string
|
||||
Diff string
|
||||
Time string
|
||||
}
|
||||
|
||||
type Handlers struct {
|
||||
DB *clover.DB
|
||||
}
|
||||
|
||||
// ParseCreateDate can be used to convert ShareLog.CreateDate to time.Time
|
||||
func (s *ShareLog) ParseCreateDate() (time.Time, error) {
|
||||
var sec, nsec int64
|
||||
_, err := fmt.Sscanf(s.CreateDate, "%d,%d", &sec, &nsec)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
return time.Unix(sec, nsec), nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
store, _ := badgerstore.Open("badgerdb")
|
||||
db, err := clover.OpenWithStore(store)
|
||||
db, err := database.InitDatabase()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to open CloverDB: %v", err)
|
||||
log.Fatalf("Failed to initialize database: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// Ensure collection exists
|
||||
hasCollection, err := db.HasCollection(collectionName)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to check collection: %v", err)
|
||||
}
|
||||
if !hasCollection {
|
||||
_ = db.CreateCollection(collectionName)
|
||||
db.CreateIndex(collectionName, "CreateDate")
|
||||
}
|
||||
|
||||
printAllHashes(db)
|
||||
lastDayHighestShare, err := getHighestShareInRange(db, collectionName, time.Now().Add(-24*time.Hour))
|
||||
if err != nil {
|
||||
log.Printf("Failed to get highest share in last 24h: %v", err)
|
||||
} else {
|
||||
createdAt, _ := lastDayHighestShare.Get("CreateDate").(string)
|
||||
fmt.Printf("Highest share in last 24h: %s, Diff: %f, Created at: %s\n",
|
||||
lastDayHighestShare.Get("Hash"),
|
||||
lastDayHighestShare.Get("SDiff"),
|
||||
createdAt,
|
||||
)
|
||||
}
|
||||
|
||||
go watchAndIngest(db)
|
||||
go ingest.WatchAndIngest(db)
|
||||
|
||||
go func() {
|
||||
handlers := Handlers{DB: db}
|
||||
http.HandleFunc("/", handlers.indexHandler)
|
||||
handlers := web.Handlers{DB: db}
|
||||
http.HandleFunc("/", handlers.IndexHandler)
|
||||
fmt.Println("Listening on :8081")
|
||||
log.Fatal(http.ListenAndServe(":8081", nil))
|
||||
}()
|
||||
|
||||
fmt.Println("Waitin for ctrl-c")
|
||||
fmt.Println("Waiting for ctrl-c")
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-sigs
|
||||
store.Close()
|
||||
}
|
||||
|
||||
func printAllHashes(db *clover.DB) {
|
||||
docs, err := db.FindAll(c.NewQuery(collectionName))
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to read from collection: %v", err)
|
||||
}
|
||||
|
||||
for _, doc := range docs {
|
||||
hash := doc.Get("Hash")
|
||||
fmt.Println(hash)
|
||||
}
|
||||
}
|
||||
|
||||
func watchAndIngest(db *clover.DB) {
|
||||
ticker := time.NewTicker(30 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
<-ticker.C
|
||||
ingestClosedBlocks(db)
|
||||
}
|
||||
}
|
||||
|
||||
func ingestClosedBlocks(db *clover.DB) {
|
||||
entries, err := os.ReadDir(logsDir)
|
||||
if err != nil {
|
||||
log.Println("Error reading logsDir:", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Find block-like directories (starts with 000)
|
||||
blockDirs := []fs.DirEntry{}
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() && strings.HasPrefix(entry.Name(), "000") {
|
||||
blockDirs = append(blockDirs, entry)
|
||||
}
|
||||
}
|
||||
|
||||
if len(blockDirs) <= 1 {
|
||||
return // Nothing to ingest
|
||||
}
|
||||
|
||||
// Sort dirs alphabetically (ascending), last is newest
|
||||
sort.Slice(blockDirs, func(i, j int) bool {
|
||||
return blockDirs[i].Name() < blockDirs[j].Name()
|
||||
})
|
||||
|
||||
// Ingest all except last (current block dir)
|
||||
for _, dir := range blockDirs[:len(blockDirs)-1] {
|
||||
ingestBlockDir(db, filepath.Join(logsDir, dir.Name()))
|
||||
_ = os.RemoveAll(filepath.Join(logsDir, dir.Name()))
|
||||
}
|
||||
}
|
||||
|
||||
func ingestBlockDir(db *clover.DB, dirPath string) {
|
||||
files, err := os.ReadDir(dirPath)
|
||||
if err != nil {
|
||||
log.Printf("Failed to read block dir %s: %v", dirPath, err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
if !strings.HasSuffix(f.Name(), ".sharelog") {
|
||||
continue
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(filepath.Join(dirPath, f.Name()))
|
||||
if err != nil {
|
||||
log.Println("Failed to read file:", err)
|
||||
continue
|
||||
}
|
||||
|
||||
lines := strings.Split(string(data), "\n")
|
||||
for _, line := range lines {
|
||||
if strings.TrimSpace(line) == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
var share ShareLog
|
||||
if err := json.Unmarshal([]byte(line), &share); err != nil {
|
||||
log.Println("JSON parse error:", err)
|
||||
continue
|
||||
}
|
||||
|
||||
doc := document.NewDocumentOf(&share)
|
||||
if _, err := db.InsertOne(collectionName, doc); err != nil {
|
||||
log.Println("DB insert error:", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("Ingested and deleted %s", dirPath)
|
||||
}
|
||||
|
||||
func parseCreatedate(createdate string) time.Time {
|
||||
parts := strings.Split(createdate, ",")
|
||||
if len(parts) == 2 {
|
||||
sec, _ := strconv.ParseInt(parts[0], 10, 64)
|
||||
nsec, _ := strconv.ParseInt(parts[1], 10, 64)
|
||||
return time.Unix(sec, nsec)
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
func getHighestShareInRange(db *clover.DB, collection string, since time.Time) (*document.Document, error) {
|
||||
// Convert `since` to the format in `createdate`
|
||||
lower := since.Unix()
|
||||
upper := time.Now().Unix()
|
||||
|
||||
// Filter by timestamp range
|
||||
criteria := c.Field("CreateDate").GtEq(fmt.Sprint(lower)).
|
||||
And(c.Field("CreateDate").LtEq(fmt.Sprint(upper)))
|
||||
|
||||
// Query sorted by "sdiff" descending, limit 1
|
||||
results, err := db.FindAll(c.NewQuery(collection).
|
||||
Where(criteria).
|
||||
Sort(c.SortOption{Field: "SDiff", Direction: -1}).
|
||||
Limit(1))
|
||||
|
||||
if err != nil || len(results) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
return results[0], nil
|
||||
}
|
||||
|
||||
func (h Handlers) indexHandler(w http.ResponseWriter, r *http.Request) {
|
||||
stats, err := getStats(h.DB)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to load stats", 500)
|
||||
return
|
||||
}
|
||||
|
||||
tmpl := template.Must(template.ParseFiles("templates/index.html"))
|
||||
tmpl.Execute(w, stats)
|
||||
}
|
||||
|
||||
func humanDiff(diff float64) string {
|
||||
units := []string{"", "K", "M", "G", "T"}
|
||||
i := 0
|
||||
for diff >= 1000 && i < len(units)-1 {
|
||||
diff /= 1000
|
||||
i++
|
||||
}
|
||||
return fmt.Sprintf("%.2f%s", diff, units[i])
|
||||
}
|
||||
|
||||
func getStats(db *clover.DB) ([]ShareStat, error) {
|
||||
now := time.Now()
|
||||
ranges := []struct {
|
||||
Label string
|
||||
Since time.Time
|
||||
}{
|
||||
{"Past Hour", now.Add(-1 * time.Hour)},
|
||||
{"Past 24h", now.Add(-24 * time.Hour)},
|
||||
{"Past 7d", now.Add(-7 * 24 * time.Hour)},
|
||||
}
|
||||
|
||||
stats := []ShareStat{}
|
||||
|
||||
// All-time highest
|
||||
doc, _ := getHighestShareInRange(db, collectionName, time.Unix(0, 0))
|
||||
if doc != nil {
|
||||
stats = append(stats, ShareStat{
|
||||
Label: "All Time",
|
||||
Diff: humanDiff(doc.Get("SDiff").(float64)),
|
||||
Time: parseCreatedate(doc.Get("CreateDate").(string)).Format(time.RFC822),
|
||||
})
|
||||
}
|
||||
|
||||
for _, r := range ranges {
|
||||
doc, _ := getHighestShareInRange(db, collectionName, r.Since)
|
||||
if doc != nil {
|
||||
stats = append(stats, ShareStat{
|
||||
Label: r.Label,
|
||||
Diff: humanDiff(doc.Get("SDiff").(float64)),
|
||||
Time: parseCreatedate(doc.Get("CreateDate").(string)).Format(time.RFC822),
|
||||
})
|
||||
} else {
|
||||
stats = append(stats, ShareStat{Label: r.Label, Diff: "-", Time: "-"})
|
||||
}
|
||||
}
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
|
46
models/share_log.go
Normal file
46
models/share_log.go
Normal file
@ -0,0 +1,46 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ShareLog struct {
|
||||
WorkInfoID uint64 `json:"workinfoid"` // ID of the associated work unit
|
||||
ClientID int `json:"clientid"` // ID of the client/miner connection
|
||||
Enonce1 string `json:"enonce1"` // Extra nonce 1 (assigned by pool)
|
||||
Nonce2 string `json:"nonce2"` // Extra nonce 2 (sent by miner)
|
||||
Nonce string `json:"nonce"` // Nonce used to generate the solution
|
||||
NTime string `json:"ntime"` // Network time used in the block
|
||||
Diff float64 `json:"diff"` // Target difficulty for the share
|
||||
SDiff float64 `json:"sdiff"` // Share difficulty achieved
|
||||
Hash string `json:"hash"` // Resulting hash from the share
|
||||
Result bool `json:"result"` // Was this share a valid result
|
||||
Errn int `json:"errn"` // Error code (0 = no error)
|
||||
|
||||
CreateDate string `json:"createdate"` // Timestamp: "seconds,nanoseconds"
|
||||
CreateBy string `json:"createby"` // Origin of share creation ("code")
|
||||
CreateCode string `json:"createcode"` // Component that created this entry
|
||||
CreateInet string `json:"createinet"` // IP + port of submit origin
|
||||
|
||||
WorkerName string `json:"workername"` // Full worker name (username.worker)
|
||||
Username string `json:"username"` // User's Bitcoin address
|
||||
Address string `json:"address"` // IP address of the worker
|
||||
Agent string `json:"agent"` // Miner agent string (e.g., bitaxe/BM1370)
|
||||
}
|
||||
|
||||
type ShareStat struct {
|
||||
Label string
|
||||
Diff string
|
||||
Time string
|
||||
}
|
||||
|
||||
// ParseCreateDate can be used to convert ShareLog.CreateDate to time.Time
|
||||
func (s *ShareLog) ParseCreateDate() (time.Time, error) {
|
||||
var sec, nsec int64
|
||||
_, err := fmt.Sscanf(s.CreateDate, "%d,%d", &sec, &nsec)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
return time.Unix(sec, nsec), nil
|
||||
}
|
72
stats/stats.go
Normal file
72
stats/stats.go
Normal file
@ -0,0 +1,72 @@
|
||||
package stats
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ostafen/clover/v2"
|
||||
|
||||
"pool-stats/database"
|
||||
"pool-stats/models"
|
||||
)
|
||||
|
||||
func parseCreatedate(createdate string) time.Time {
|
||||
parts := strings.Split(createdate, ",")
|
||||
if len(parts) == 2 {
|
||||
sec, _ := strconv.ParseInt(parts[0], 10, 64)
|
||||
nsec, _ := strconv.ParseInt(parts[1], 10, 64)
|
||||
return time.Unix(sec, nsec)
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
func humanDiff(diff float64) string {
|
||||
units := []string{"", "K", "M", "G", "T"}
|
||||
i := 0
|
||||
for diff >= 1000 && i < len(units)-1 {
|
||||
diff /= 1000
|
||||
i++
|
||||
}
|
||||
return fmt.Sprintf("%.2f%s", diff, units[i])
|
||||
}
|
||||
|
||||
func GetStats(db *clover.DB) ([]models.ShareStat, error) {
|
||||
now := time.Now()
|
||||
ranges := []struct {
|
||||
Label string
|
||||
Since time.Time
|
||||
}{
|
||||
{"Past Hour", now.Add(-1 * time.Hour)},
|
||||
{"Past 24h", now.Add(-24 * time.Hour)},
|
||||
{"Past 7d", now.Add(-7 * 24 * time.Hour)},
|
||||
}
|
||||
|
||||
stats := []models.ShareStat{}
|
||||
|
||||
// All-time highest
|
||||
doc, _ := database.GetHighestShareInRange(db, database.CollectionName, time.Unix(0, 0))
|
||||
if doc != nil {
|
||||
stats = append(stats, models.ShareStat{
|
||||
Label: "All Time",
|
||||
Diff: humanDiff(doc.Get("SDiff").(float64)),
|
||||
Time: parseCreatedate(doc.Get("CreateDate").(string)).Format(time.RFC822),
|
||||
})
|
||||
}
|
||||
|
||||
for _, r := range ranges {
|
||||
doc, _ := database.GetHighestShareInRange(db, database.CollectionName, r.Since)
|
||||
if doc != nil {
|
||||
stats = append(stats, models.ShareStat{
|
||||
Label: r.Label,
|
||||
Diff: humanDiff(doc.Get("SDiff").(float64)),
|
||||
Time: parseCreatedate(doc.Get("CreateDate").(string)).Format(time.RFC822),
|
||||
})
|
||||
} else {
|
||||
stats = append(stats, models.ShareStat{Label: r.Label, Diff: "-", Time: "-"})
|
||||
}
|
||||
}
|
||||
|
||||
return stats, nil
|
||||
}
|
25
web/handlers.go
Normal file
25
web/handlers.go
Normal file
@ -0,0 +1,25 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
|
||||
"pool-stats/stats"
|
||||
|
||||
"github.com/ostafen/clover/v2"
|
||||
)
|
||||
|
||||
type Handlers struct {
|
||||
DB *clover.DB
|
||||
}
|
||||
|
||||
func (h Handlers) IndexHandler(w http.ResponseWriter, r *http.Request) {
|
||||
shareStats, err := stats.GetStats(h.DB)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to load stats", 500)
|
||||
return
|
||||
}
|
||||
|
||||
tmpl := template.Must(template.ParseFiles("templates/index.html"))
|
||||
tmpl.Execute(w, shareStats)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user