diff --git a/.gitignore b/.gitignore index 5c36f60..4766f46 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ logs/ badgerdb/ +notes.md diff --git a/database/db.go b/database/db.go new file mode 100644 index 0000000..30d83c8 --- /dev/null +++ b/database/db.go @@ -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) + } +} diff --git a/go.mod b/go.mod index 094d9bd..2d7d2a4 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index 6b801f1..5523ffd 100644 --- a/go.sum +++ b/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= diff --git a/ingest/ingest.go b/ingest/ingest.go new file mode 100644 index 0000000..985caf2 --- /dev/null +++ b/ingest/ingest.go @@ -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) +} diff --git a/main.go b/main.go index 4e52eeb..5294e27 100644 --- a/main.go +++ b/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 } diff --git a/models/share_log.go b/models/share_log.go new file mode 100644 index 0000000..92c7baf --- /dev/null +++ b/models/share_log.go @@ -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 +} diff --git a/stats/stats.go b/stats/stats.go new file mode 100644 index 0000000..3aa0266 --- /dev/null +++ b/stats/stats.go @@ -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 +} diff --git a/web/handlers.go b/web/handlers.go new file mode 100644 index 0000000..b045575 --- /dev/null +++ b/web/handlers.go @@ -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) +}