recent stats fixes

This commit is contained in:
cryptonote-social 2020-08-16 16:31:54 -07:00
parent a97298f709
commit faea137ff2
5 changed files with 276 additions and 669 deletions

View File

@ -113,6 +113,9 @@ typedef struct get_miner_state_response {
// MINING_PAUSED_USER_OVERRIDE = -5
// indicates miner is paused, and is in the "user focred mining pause" state.
//
// MINING_PAUSED_TIME_EXCLUDED = -6
// indicates miner is paused because we're in the user-excluded time period
//
// MINING_ACTIVE = 1
// indicates miner is actively mining
//
@ -135,8 +138,9 @@ typedef struct get_miner_state_response {
// NOTE: you must free() username
const char* username;
// Stats below aremay be stale, with the seconds_old field specifying in seconds how out of
// date they are.
// Stats below may be stale, with the seconds_old field specifying in seconds how out of
// date they are. A negative value of seconds_old indicates pool stats have yet to be fetched
// and should be ignored.
int seconds_old;
long lifetime_hashes; // total sum of hashes contributed to the pool under this username

View File

@ -68,11 +68,6 @@ func MultiMain(s ScreenStater, agent string) {
}
flag.Parse()
if strings.Index(*uname, ".") != -1 {
crylog.Fatal("Usernames cannot contain the '.' character. If you are trying to specify a wallet id, use -wallet instead")
return
}
var hr1, hr2 int
hr1 = -1
var err error
@ -97,7 +92,6 @@ func MultiMain(s ScreenStater, agent string) {
return
}
}
fmt.Printf("==== %s v%s ====\n", APPLICATION_NAME, VERSION_STRING)
if *uname == DONATE_USERNAME {
fmt.Printf("\nNo username specified, mining on behalf of donate.getmonero.org.\n")
@ -119,6 +113,11 @@ func MultiMain(s ScreenStater, agent string) {
crylog.Info("Miner username:", *uname)
crylog.Info("Threads:", *t)
if hr1 == -1 || hr2 == -1 {
hr1 = 0
hr2 = 0
}
config := MinerConfig{
ScreenStater: s,
Threads: *t,
@ -133,7 +132,7 @@ func MultiMain(s ScreenStater, agent string) {
UseTLS: *tls,
AdvancedConfig: *config,
}
if Mine(&config) != nil {
if err = Mine(&config); err != nil {
crylog.Fatal("Miner failed:", err)
}
}

707
miner.go
View File

@ -4,70 +4,24 @@ package csminer
import (
"bufio"
"bytes"
"encoding/hex"
"encoding/json"
"io/ioutil"
"net/http"
"errors"
"os"
"runtime"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/cryptonote-social/csminer/blockchain"
"github.com/cryptonote-social/csminer/crylog"
"github.com/cryptonote-social/csminer/rx"
"github.com/cryptonote-social/csminer/minerlib"
"github.com/cryptonote-social/csminer/stratum/client"
)
var (
cl *client.Client
clMutex sync.Mutex
clientAlive bool // true when the stratum client is connected and healthy
stopper uint32 // atomic int used to signal rxlib worker threads to stop mining
wg sync.WaitGroup // used to wait for stopped worker threads to finish
pokeChannel chan int // used to send messages to main job loop to take various actions
// miner client stats
sharesAccepted int64
sharesRejected int64
poolSideHashes int64
clientSideHashes, recentHashes int64
startTime time.Time // when the miner started up
lastStatsResetTime time.Time
lastStatsUpdateTime time.Time
screenIdle int32 // only mine when this is > 0
batteryPower int32 // only mine when this is > 0
manualMinerToggle int32 // whether paused mining has been manually overridden
mc *MinerConfig
)
var ()
const (
HANDLED = 1
USE_CACHED = 2
// Valid screen states
SCREEN_IDLE = 0
SCREEN_ACTIVE = 1
BATTERY_POWER = 2
AC_POWER = 3
SCREEN_IDLE_POKE = 0
SCREEN_ON_POKE = 1
BATTERY_POWER_POKE = 2
AC_POWER_POKE = 3
PRINT_STATS_POKE = 4
ENTER_HIT_POKE = 5
INCREASE_THREADS_POKE = 6
DECREASE_THREADS_POKE = 7
)
type ScreenState int
@ -92,450 +46,147 @@ type MinerConfig struct {
}
func Mine(c *MinerConfig) error {
mc = c
if mc.UseTLS {
cl = client.NewClient("cryptonote.social:5556", mc.Agent)
} else {
cl = client.NewClient("cryptonote.social:5555", mc.Agent)
imResp := minerlib.InitMiner(&minerlib.InitMinerArgs{
Threads: 1,
ExcludeHourStart: c.ExcludeHrStart,
ExcludeHourEnd: c.ExcludeHrEnd,
})
if imResp.Code > 2 {
crylog.Error("Bad configuration:", imResp.Message)
return errors.New("InitMiner failed: " + imResp.Message)
}
if imResp.Code == 2 {
crylog.Warn("")
crylog.Warn("WARNING: Could not allocate hugepages. Mining might be slow. A reboot might help.")
crylog.Warn("")
}
startTime = time.Now()
lastStatsResetTime = time.Now()
seed := []byte{}
screenIdle = 1
batteryPower = 0
sleepSec := 3 * time.Second // time to sleep if connection attempt fails
for {
plResp := minerlib.PoolLogin(&minerlib.PoolLoginArgs{
Username: c.Username,
RigID: c.RigID,
Wallet: c.Wallet,
Agent: c.Agent,
Config: c.AdvancedConfig,
UseTLS: c.UseTLS,
})
if plResp.Code < 0 {
crylog.Error("Pool server not responding:", plResp.Message)
crylog.Info("Sleeping for", sleepSec, "seconds before trying again.")
time.Sleep(sleepSec)
sleepSec += time.Second
continue
}
if plResp.Code == 1 {
if len(plResp.Message) > 0 {
crylog.Warn(":::::::::::::::::::::::::::::::::::::::::::::::::::::::::\n")
if plResp.MessageID == client.NO_WALLET_SPECIFIED_WARNING_CODE {
crylog.Warn("WARNING: your username is not yet associated with any")
crylog.Warn(" wallet id. You should fix this immediately.")
} else {
crylog.Warn("WARNING from pool server")
crylog.Warn(" Message:", plResp.Message)
}
crylog.Warn(" Code :", plResp.MessageID, "\n")
crylog.Warn(":::::::::::::::::::::::::::::::::::::::::::::::::::::::::")
}
break
}
crylog.Error("Pool refused login:", plResp.Message)
return errors.New("pool refused login")
}
manualMinerToggle = 0
if mc.Saver {
ch, err := mc.ScreenStater.GetScreenStateChannel()
if c.Saver {
ch, err := c.ScreenStater.GetScreenStateChannel()
if err != nil {
crylog.Error("failed to get screen state monitor, screen state will be ignored")
} else {
// We assume the screen is active when the miner is started. This may
// not hold if someone is running the miner from an auto-start script?
screenIdle = 0
go monitorScreenSaver(ch)
}
} else {
minerlib.ReportIdleScreenState(true)
}
printKeyboardCommands()
startKeyboardScanning()
wasJustMining := false
pokeChannel = make(chan int, 5) // use small amount of buffering for when internet may be bad
outer:
for {
jobChannel := connectClient()
resetRecentStats()
var job *client.MultiClientJob
for {
onoff := getActivityMessage()
crylog.Info("[mining=" + onoff + "]")
select {
case poke := <-pokeChannel:
pokeRes := handlePoke(wasJustMining, poke)
switch pokeRes {
case HANDLED:
continue
case USE_CACHED:
if job == nil {
crylog.Warn("no job to work on")
continue
}
default:
crylog.Error("mystery poke:", pokeRes)
continue
}
case job = <-jobChannel:
if job == nil {
crylog.Warn("stratum client died, reconnecting")
continue outer
}
}
crylog.Info("Current job:", job.JobID, "Difficulty:", blockchain.TargetToDifficulty(job.Target))
// Stop existing mining, if any, and wait for mining threads to finish.
atomic.StoreUint32(&stopper, 1)
wg.Wait()
// Check if we need to reinitialize rx dataset
newSeed, err := hex.DecodeString(job.SeedHash)
if err != nil {
crylog.Error("invalid seed hash:", job.SeedHash)
continue
}
if bytes.Compare(newSeed, seed) != 0 {
crylog.Info("New seed:", job.SeedHash)
rx.InitRX(newSeed, mc.Threads, runtime.GOMAXPROCS(0))
seed = newSeed
resetRecentStats()
}
// Start mining on new job if mining is active
if miningActive() {
if !wasJustMining {
// Reset recent stats whenever going from not mining to mining,
// and don't print stats because they could be inaccurate
resetRecentStats()
wasJustMining = true
} else {
printStats(true)
}
atomic.StoreUint32(&stopper, 0)
for i := 0; i < mc.Threads; i++ {
wg.Add(1)
go goMine(&wg, *job, i /*thread*/)
}
} else if wasJustMining {
// Print stats whenever we go from mining to not mining
printStats(false)
wasJustMining = false
}
scanner := bufio.NewScanner(os.Stdin)
var manualMinerActivate bool
for scanner.Scan() {
b := scanner.Text()
switch b {
case "i":
crylog.Info("Increasing thread count.")
minerlib.IncreaseThreads()
case "d":
crylog.Info("Decreasing thread count.")
minerlib.DecreaseThreads()
case "h", "s", "p":
printStats()
case "q", "quit", "exit":
crylog.Info("quitting due to keyboard command")
return nil
case "?", "help":
printKeyboardCommands()
}
}
}
// connectClient will try to establish the connection to the stratum server and won't return until
// successful. It will also start the job dispatching loop once connected. clientAlive will be true
// upon return.
func connectClient() chan *client.MultiClientJob {
clMutex.Lock()
clientAlive = false
clMutex.Unlock()
sleepSec := 3 * time.Second // time to sleep if connection attempt fails
if mc.StartDiff > 0 {
// TODO: -start_diff flag is deprecated in favor of -config, so remove this when
// appropriate.
if len(mc.AdvancedConfig) > 0 {
mc.AdvancedConfig += ";"
}
mc.AdvancedConfig += "start_diff=" + strconv.Itoa(mc.StartDiff)
}
for {
loginName := mc.Username
if mc.Wallet != "" {
loginName = mc.Wallet + "." + mc.Username
}
clMutex.Lock()
err, code, message := cl.Connect(loginName, mc.AdvancedConfig, mc.RigID, mc.UseTLS)
clMutex.Unlock()
if err != nil {
if code != 0 {
// stratum server error
crylog.Error("Pool server did not allow a connection due to error:")
crylog.Error(" ::::::", message, "::::::")
os.Exit(1)
}
crylog.Warn("Client failed to connect:", err)
time.Sleep(sleepSec)
sleepSec += time.Second
continue
} else if code != 0 {
// We got a warning from the stratum server
crylog.Warn(":::::::::::::::::::::::::::::::::::::::::::::::::::::::::\n")
if code == client.NO_WALLET_SPECIFIED_WARNING_CODE {
crylog.Warn("WARNING: your username is not yet associated with any")
crylog.Warn(" wallet id. You should fix this immediately with the")
crylog.Warn(" -wallet flag.\n")
if len(b) == 0 {
if !manualMinerActivate {
manualMinerActivate = true
crylog.Info("Overriding mining state to ACTIVE")
minerlib.OverrideMiningActivityState(true)
} else {
crylog.Warn("WARNING from pool server")
crylog.Warn(" Message:", message)
crylog.Warn(" Code :", code, "\n")
crylog.Info("Removing mining override")
manualMinerActivate = false
minerlib.RemoveMiningActivityOverride()
}
crylog.Warn(":::::::::::::::::::::::::::::::::::::::::::::::::::::::::\n")
}
break
}
clMutex.Lock()
clientAlive = true
clMutex.Unlock()
go func() {
err := cl.DispatchJobs()
if err != nil {
crylog.Error("Job dispatcher exitted with error:", err)
}
clMutex.Lock()
if clientAlive {
clientAlive = false
cl.Close()
}
clMutex.Unlock()
}()
crylog.Info("Connected")
return cl.JobChannel
crylog.Error("Scanning terminated")
return errors.New("didn't expect keyboard scanning to terminate")
}
func timeExcluded() bool {
currHr := time.Now().Hour()
startHr := mc.ExcludeHrStart
endHr := mc.ExcludeHrEnd
if startHr < endHr {
return currHr >= startHr && currHr < endHr
}
return currHr < startHr && currHr >= endHr
}
func resetRecentStats() {
atomic.StoreInt64(&recentHashes, 0)
lastStatsResetTime = time.Now()
}
func printStats(isMining bool) {
crylog.Info("=====================================")
crylog.Info("Shares [accepted:rejected]:", sharesAccepted, ":", sharesRejected)
crylog.Info("Hashes [client:pool]:", clientSideHashes, ":", poolSideHashes)
var elapsed1 float64
if isMining {
// if we're actively mining then hash count is only accurate
// as of the last update time
elapsed1 = lastStatsUpdateTime.Sub(startTime).Seconds()
func printStats() {
s := minerlib.GetMiningState()
msg := getActivityMessage(s.MiningActivity)
crylog.Info("")
crylog.Info("===============================================================================")
crylog.Info("Mining", msg)
crylog.Info("===============================================================================")
if s.RecentHashrate < 0 {
crylog.Info("Recent Hashrate : --calculating--")
} else {
elapsed1 = time.Now().Sub(startTime).Seconds()
crylog.Info("Recent Hashrate :", strconv.FormatFloat(s.RecentHashrate, 'f', 2, 64))
}
elapsed2 := lastStatsUpdateTime.Sub(lastStatsResetTime).Seconds()
if elapsed1 > 0.0 && elapsed2 > 0.0 {
crylog.Info("Hashes/sec [inception:recent]:",
strconv.FormatFloat(float64(clientSideHashes)/elapsed1, 'f', 2, 64), ":",
strconv.FormatFloat(float64(recentHashes)/elapsed2, 'f', 2, 64))
}
crylog.Info("=====================================")
}
func goMine(wg *sync.WaitGroup, job client.MultiClientJob, thread int) {
defer wg.Done()
input, err := hex.DecodeString(job.Blob)
diffTarget := blockchain.TargetToDifficulty(job.Target)
if err != nil {
crylog.Error("invalid blob:", job.Blob)
return
}
hash := make([]byte, 32)
nonce := make([]byte, 4)
for {
res := rx.HashUntil(input, uint64(diffTarget), thread, hash, nonce, &stopper)
lastStatsUpdateTime = time.Now()
if res <= 0 {
atomic.AddInt64(&clientSideHashes, -res)
atomic.AddInt64(&recentHashes, -res)
break
}
atomic.AddInt64(&clientSideHashes, res)
atomic.AddInt64(&recentHashes, res)
crylog.Info("Share found:", blockchain.HashDifficulty(hash), thread)
fnonce := hex.EncodeToString(nonce)
// If the client is alive, submit the share in a separate thread so we can resume hashing
// immediately, otherwise wait until it's alive.
for {
var alive bool
clMutex.Lock()
alive = clientAlive
clMutex.Unlock()
if alive {
break
}
//crylog.Warn("client ded")
time.Sleep(time.Second)
}
go func(fnonce, jobid string) {
clMutex.Lock()
defer clMutex.Unlock()
if !clientAlive {
crylog.Warn("client died, abandoning work")
return
}
resp, err := cl.SubmitWork(fnonce, jobid)
if err != nil {
crylog.Warn("Submit work client failure:", jobid, err)
clientAlive = false
cl.Close()
return
}
if len(resp.Error) > 0 {
atomic.AddInt64(&sharesRejected, 1)
crylog.Warn("Submit work server error:", jobid, resp.Error)
return
}
atomic.AddInt64(&sharesAccepted, 1)
atomic.AddInt64(&poolSideHashes, diffTarget)
}(fnonce, job.JobID)
crylog.Info("Hashrate since inception :", strconv.FormatFloat(s.Hashrate, 'f', 2, 64))
crylog.Info("Threads :", s.Threads)
crylog.Info("===============================================================================")
crylog.Info("Shares [accepted:rejected]:", s.SharesAccepted, ":", s.SharesRejected)
crylog.Info("Hashes [client:pool]:", s.ClientSideHashes, ":", s.PoolSideHashes)
crylog.Info("===============================================================================")
if s.SecondsOld >= 0.0 {
crylog.Info("Pool username :", s.PoolUsername)
crylog.Info("Last pool stats refresh :", s.SecondsOld, "seconds ago")
crylog.Info(" Lifetime hashes :", prettyInt(s.LifetimeHashes))
crylog.Info(" Paid :", strconv.FormatFloat(s.Paid, 'f', 12, 64), "$XMR")
crylog.Info(" Owed :", strconv.FormatFloat(s.Owed, 'f', 12, 64), "$XMR")
crylog.Info(" Time to next reward (est.) :", s.TimeToReward)
crylog.Info(" Accumulated (est.) :", strconv.FormatFloat(s.Accumulated, 'f', 12, 64), "$XMR")
crylog.Info("===============================================================================")
}
crylog.Info("")
}
func printKeyboardCommands() {
crylog.Info("Keyboard commands:")
crylog.Info(" s: print miner stats")
crylog.Info(" p: print pool-side user stats")
//crylog.Info(" p: print pool-side user stats")
crylog.Info(" i/d: increase/decrease number of threads by 1")
crylog.Info(" q: quit")
crylog.Info(" <enter>: override a paused miner")
}
func startKeyboardScanning() {
scanner := bufio.NewScanner(os.Stdin)
go func() {
for scanner.Scan() {
b := scanner.Text()
switch b {
case "i":
pokeJobDispatcher(INCREASE_THREADS_POKE)
case "d":
pokeJobDispatcher(DECREASE_THREADS_POKE)
case "h", "s":
pokeSuccess := pokeJobDispatcher(PRINT_STATS_POKE)
if !pokeSuccess {
// stratum client is probably dead due to bad internet connection, but it should
// still be safe to print stats.
printStats(false)
}
case "q", "quit", "exit":
crylog.Info("quitting due to keyboard command")
os.Exit(0)
case "?", "help":
printKeyboardCommands()
case "p":
err := printPoolSideStats()
if err != nil {
crylog.Error("Failed to get pool side user stats:", err)
}
}
if len(b) == 0 {
// Ignore enter-hit mining override if on battery power
if atomic.LoadInt32(&batteryPower) > 0 {
crylog.Warn("on battery power, keyboard overrides ignored")
continue
}
pokeSuccess := pokeJobDispatcher(ENTER_HIT_POKE)
if pokeSuccess {
if atomic.LoadInt32(&manualMinerToggle) == 0 {
atomic.StoreInt32(&manualMinerToggle, 1)
} else {
atomic.StoreInt32(&manualMinerToggle, 0)
}
}
}
}
crylog.Error("Scanning terminated")
}()
}
func printPoolSideStats() error {
c := &http.Client{
Timeout: 15 * time.Second,
}
uri := "https://cryptonote.social/json/WorkerStats"
sbody := "{\"Coin\": \"xmr\", \"Worker\": \"" + mc.Username + "\"}\n"
body := strings.NewReader(sbody)
resp, err := c.Post(uri, "", body)
if err != nil {
return err
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
s := &struct {
Code int
CycleProgress float64
Hashrate1 int64
Hashrate24 int64
LifetimeHashes int64
LifetimeBestHash int64
Donate float64
AmountPaid float64
AmountOwed float64
}{}
err = json.Unmarshal(b, &s)
if err != nil {
return err
}
// Now get pool stats
uri = "https://cryptonote.social/json/PoolStats"
sbody = "{\"Coin\": \"xmr\"}\n"
body = strings.NewReader(sbody)
resp2, err := http.DefaultClient.Post(uri, "", body)
if err != nil {
return err
}
defer resp2.Body.Close()
b, err = ioutil.ReadAll(resp2.Body)
if err != nil {
return err
}
ps := &struct {
Code int
NextBlockReward float64
Margin float64
PPROPProgress float64
PPROPHashrate int64
NetworkDifficulty int64
SmoothedDifficulty int64 // Network difficulty averaged over the past hour
}{}
err = json.Unmarshal(b, &ps)
if err != nil {
return err
}
s.CycleProgress /= (1.0 + ps.Margin)
diff := float64(ps.SmoothedDifficulty)
if diff == 0.0 {
diff = float64(ps.NetworkDifficulty)
}
hr := float64(ps.PPROPHashrate)
var ttreward string
if hr > 0.0 {
ttr := (diff*(1.0+ps.Margin) - (ps.PPROPProgress * diff)) / hr / 3600.0 / 24.0
if ttr > 0.0 {
if ttr < 1.0 {
ttr *= 24.0
if ttr < 1.0 {
ttr *= 60.0
ttreward = strconv.FormatFloat(ttr, 'f', 2, 64) + " min"
} else {
ttreward = strconv.FormatFloat(ttr, 'f', 2, 64) + " hrs"
}
} else {
ttreward = strconv.FormatFloat(ttr, 'f', 2, 64) + " days"
}
} else if ttr < 0.0 {
ttreward = "overdue"
}
}
crylog.Info("==========================================")
crylog.Info("User :", mc.Username)
crylog.Info("Progress :", strconv.FormatFloat(s.CycleProgress*100.0, 'f', 5, 64)+"%")
crylog.Info("1 Hr Hashrate :", s.Hashrate1)
crylog.Info("24 Hr Hashrate :", s.Hashrate24)
crylog.Info("Lifetime Hashes :", prettyInt(s.LifetimeHashes))
crylog.Info("Paid :", strconv.FormatFloat(s.AmountPaid, 'f', 12, 64), "$XMR")
if s.AmountOwed > 0.0 {
crylog.Info("Amount Owed :", strconv.FormatFloat(s.AmountOwed, 'f', 12, 64), "$XMR")
}
/*crylog.Info("PPROP Progress :", strconv.FormatFloat(ps.PPROPProgress*100.0, 'f', 5, 64)+"%")*/
crylog.Info("")
crylog.Info("Estimated stats :")
if len(ttreward) > 0 {
crylog.Info(" Time to next reward:", ttreward)
}
if ps.NextBlockReward > 0.0 && s.CycleProgress > 0.0 {
crylog.Info(" Reward accumulated :", strconv.FormatFloat(ps.NextBlockReward*s.CycleProgress, 'f', 12, 64), "$XMR")
}
crylog.Info("==========================================")
return nil
}
func prettyInt(i int64) string {
s := strconv.Itoa(int(i))
out := []byte{}
@ -558,128 +209,38 @@ func monitorScreenSaver(ch chan ScreenState) {
for state := range ch {
switch state {
case SCREEN_IDLE:
crylog.Info("Screen idle")
atomic.StoreInt32(&screenIdle, 1)
pokeJobDispatcher(SCREEN_IDLE_POKE)
minerlib.ReportIdleScreenState(true)
case SCREEN_ACTIVE:
crylog.Info("Screen active")
atomic.StoreInt32(&screenIdle, 0)
pokeJobDispatcher(SCREEN_ON_POKE)
minerlib.ReportIdleScreenState(false)
case BATTERY_POWER:
crylog.Info("Battery power")
atomic.StoreInt32(&batteryPower, 1)
pokeJobDispatcher(BATTERY_POWER_POKE)
minerlib.ReportPowerState(true)
case AC_POWER:
crylog.Info("AC power")
atomic.StoreInt32(&batteryPower, 0)
pokeJobDispatcher(AC_POWER_POKE)
minerlib.ReportPowerState(false)
}
}
}
// Poke the job dispatcher. Returns false if the client is not currently alive.
func pokeJobDispatcher(pokeMsg int) bool {
clMutex.Lock()
alive := clientAlive
clMutex.Unlock()
if !alive {
// jobs will block, so just ignore
crylog.Warn("Stratum client is not alive. Ignoring poke:", pokeMsg)
return false
func getActivityMessage(activityState int) string {
switch activityState {
case minerlib.MINING_PAUSED_NO_CONNECTION:
return "PAUSED: no connection."
case minerlib.MINING_PAUSED_SCREEN_ACTIVITY:
return "PAUSED: screen is active. Hit <enter> to override."
case minerlib.MINING_PAUSED_BATTERY_POWER:
return "PAUSED: on battery power. Hit <enter> to override."
case minerlib.MINING_PAUSED_USER_OVERRIDE:
return "PAUSED: keyboard override active. Hit <enter> to remove override."
case minerlib.MINING_PAUSED_TIME_EXCLUDED:
return "PAUSED: within time of day exclusion. Hit <enter> to override."
case minerlib.MINING_ACTIVE:
return "ACTIVE"
case minerlib.MINING_ACTIVE_USER_OVERRIDE:
return "ACTIVE: keyboard override active. Hit <enter> to remove override."
}
pokeChannel <- pokeMsg
return true
}
func miningActive() bool {
if atomic.LoadInt32(&batteryPower) > 0 {
return false
}
if atomic.LoadInt32(&manualMinerToggle) > 0 {
// keyboard override to always mine no matter what
return true
}
if atomic.LoadInt32(&screenIdle) == 0 {
// don't mine if screen is active
return false
}
if timeExcluded() {
// don't mine if we're in the excluded time range
return false
}
return true
}
func getActivityMessage() string {
battery := atomic.LoadInt32(&batteryPower) > 0
if battery {
return "PAUSED due to running on battery power"
}
saver := atomic.LoadInt32(&screenIdle) > 0
toggled := atomic.LoadInt32(&manualMinerToggle) > 0
onoff := ""
if timeExcluded() {
if !toggled {
onoff = "PAUSED due to -exclude hour range. <enter> to mine anyway"
} else {
onoff = "ACTIVE (threads=" + strconv.Itoa(mc.Threads) + ") due to keyboard override. <enter> to undo override"
}
} else if !saver {
if !toggled {
onoff = "PAUSED until screen lock. <enter> to mine anyway"
} else {
onoff = "ACTIVE (threads=" + strconv.Itoa(mc.Threads) + ") due to keyboard override. <enter> to undo override"
}
crylog.Fatal("Unknown activity state:", activityState)
if activityState > 0 {
return "ACTIVE: unknown reason"
} else {
onoff = "ACTIVE (threads=" + strconv.Itoa(mc.Threads) + ")"
return "PAUSED: unknown reason"
}
return onoff
}
func handlePoke(wasMining bool, poke int) int {
if poke == PRINT_STATS_POKE {
if !wasMining {
printStats(wasMining)
return HANDLED
}
// If we are actively mining we'll want to accumulate stats from workers before printing
// stats for accuracy, so just trigger a fall-through and main loop will sync+dump stats.
return USE_CACHED
}
if poke == INCREASE_THREADS_POKE {
atomic.StoreUint32(&stopper, 1)
wg.Wait()
t := rx.AddThread()
if t < 0 {
crylog.Error("Failed to add another thread")
return USE_CACHED
}
mc.Threads = t
crylog.Info("Increased # of threads to:", mc.Threads)
resetRecentStats()
return USE_CACHED
}
if poke == DECREASE_THREADS_POKE {
atomic.StoreUint32(&stopper, 1)
wg.Wait()
t := rx.RemoveThread()
if t < 0 {
crylog.Error("Failed to decrease threads")
return USE_CACHED
}
mc.Threads = t
crylog.Info("Decreased # of threads to:", mc.Threads)
resetRecentStats()
return USE_CACHED
}
isMiningNow := miningActive()
if wasMining != isMiningNow {
// mining state was toggled so fall through using last received job which will
// appropriately halt or restart any mining threads and/or print stats.
return USE_CACHED
}
return HANDLED
}

View File

@ -10,7 +10,7 @@ import (
"bytes"
"encoding/hex"
"runtime"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
@ -31,6 +31,9 @@ const (
// Indicates miner is paused, and is in the "user focred mining pause" state.
MINING_PAUSED_USER_OVERRIDE = -5
// Indicates miner is paused because we're in the user-excluded time period
MINING_PAUSED_TIME_EXCLUDED = -6
// Indicates miner is actively mining
MINING_ACTIVE = 1
@ -53,10 +56,11 @@ const (
var (
// miner config
configMutex sync.Mutex
plArgs *PoolLoginArgs
threads int
lastSeed []byte
configMutex sync.Mutex
plArgs *PoolLoginArgs
threads int
lastSeed []byte
excludeHourStart, excludeHourEnd int
doneChanMutex sync.Mutex
miningLoopDoneChan chan bool // non-nil when a mining loop is active
@ -94,6 +98,9 @@ type PoolLoginArgs struct {
// config: advanced options config string, can be null.
Config string
// UseTLS: Whether to use TLS when connecting to the pool
UseTLS bool
}
type PoolLoginResponse struct {
@ -106,8 +113,9 @@ type PoolLoginResponse struct {
// code > 1: login unsuccessful; pool server refused login. Message will contain information that
// can be shown to user to help fix the problem. Caller should retry with new login
// parameters.
Code int
Message string
Code int
Message string
MessageID int
}
// See MINING_ACTIVITY const values above for all possibilities. Shorter story: negative value ==
@ -129,6 +137,10 @@ func getMiningActivityState() int {
return MINING_ACTIVE_USER_OVERRIDE
}
if timeExcluded() {
return MINING_PAUSED_TIME_EXCLUDED
}
if batteryPower {
return MINING_PAUSED_BATTERY_POWER
}
@ -154,7 +166,14 @@ func PoolLogin(args *PoolLoginArgs) *PoolLoginResponse {
configMutex.Lock()
defer configMutex.Unlock()
r := &PoolLoginResponse{}
loginName := args.Username
if strings.Index(args.Username, ".") != -1 {
// Handle this specially since xmrig style login might cause users to specify wallet.username here
r.Code = 2
r.Message = "The '.' character is not allowed in usernames."
return r
}
if args.Wallet != "" {
loginName = args.Wallet + "." + args.Username
}
@ -162,32 +181,32 @@ func PoolLogin(args *PoolLoginArgs) *PoolLoginResponse {
config := args.Config
rigid := args.RigID
r := &PoolLoginResponse{}
err, code, message, jc := cl.Connect("cryptonote.social:5555", false /*useTLS*/, agent, loginName, config, rigid)
err, code, message, jc := cl.Connect("cryptonote.social:5555", args.UseTLS, agent, loginName, config, rigid)
if err != nil {
if code != 0 {
crylog.Error("Pool server did not allow login due to error:")
crylog.Error(" ::::::", message, "::::::")
//crylog.Error("Pool server did not allow login due to error:")
//crylog.Error(" ::::::", message, "::::::")
r.Code = 2
r.Message = message
return r
}
crylog.Error("Couldn't connect to pool server:", err)
//crylog.Error("Couldn't connect to pool server:", err)
r.Code = -1
r.Message = err.Error()
return r
} else if code != 0 {
// We got a warning from the stratum server
crylog.Warn(":::::::::::::::::::::::::::::::::::::::::::::::::::::::::\n")
if code == client.NO_WALLET_SPECIFIED_WARNING_CODE {
crylog.Warn("WARNING: your username is not yet associated with any")
crylog.Warn(" wallet id. You should fix this immediately.")
} else {
crylog.Warn("WARNING from pool server")
crylog.Warn(" Message:", message)
}
crylog.Warn(" Code :", code, "\n")
crylog.Warn(":::::::::::::::::::::::::::::::::::::::::::::::::::::::::\n")
//crylog.Warn(":::::::::::::::::::::::::::::::::::::::::::::::::::::::::\n")
//if code == client.NO_WALLET_SPECIFIED_WARNING_CODE {
//crylog.Warn("WARNING: your username is not yet associated with any")
//crylog.Warn(" wallet id. You should fix this immediately.")
//} else {
//crylog.Warn("WARNING from pool server")
//crylog.Warn(" Message:", message)
//}
//crylog.Warn(" Code :", code, "\n")
//crylog.Warn(":::::::::::::::::::::::::::::::::::::::::::::::::::::::::\n")
r.MessageID = code
r.Message = message
}
@ -236,6 +255,9 @@ func InitMiner(args *InitMinerArgs) *InitMinerResponse {
r.Message = "exclude_hour_start and exclude_hour_end must each be between 0 and 24"
return r
}
excludeHourStart = hr1
excludeHourEnd = hr2
code := rx.InitRX(args.Threads)
if code < 0 {
crylog.Error("Failed to initialize RandomX")
@ -250,6 +272,7 @@ func InitMiner(args *InitMinerArgs) *InitMinerResponse {
}
stats.Init()
threads = args.Threads
crylog.Info("minerlib initialized")
return r
}
@ -262,12 +285,9 @@ func reconnectClient() <-chan *client.MultiClientJob {
if plArgs.Wallet != "" {
loginName = plArgs.Wallet + "." + plArgs.Username
}
agent := plArgs.Agent
config := plArgs.Config
rigid := plArgs.RigID
crylog.Info("Attempting to reconnect...")
err, code, message, jc := cl.Connect("cryptonote.social:5555", false /*useTLS*/, agent, loginName, config, rigid)
err, code, message, jc := cl.Connect(
"cryptonote.social:5555", plArgs.UseTLS, plArgs.Agent, loginName, plArgs.Config, plArgs.RigID)
configMutex.Unlock()
if err == nil {
return jc
@ -296,6 +316,8 @@ func MiningLoop(jobChan <-chan *client.MultiClientJob) {
var job *client.MultiClientJob
for {
select {
case <-time.After(15 * time.Second):
break
case job = <-jobChan:
if job == nil {
crylog.Info("stratum client closed, reconnecting...")
@ -305,13 +327,14 @@ func MiningLoop(jobChan <-chan *client.MultiClientJob) {
stats.ResetRecent()
continue
}
crylog.Info("Current job:", job.JobID, "Difficulty:", blockchain.TargetToDifficulty(job.Target))
case poke := <-pokeChannel:
if poke == EXIT_LOOP_POKE {
crylog.Info("Stopping mining loop")
stopWorkers()
return
}
pokeRes := handlePoke(true /*wasJustMining*/, poke)
pokeRes := handlePoke(poke)
switch pokeRes {
case HANDLED:
continue
@ -325,28 +348,9 @@ func MiningLoop(jobChan <-chan *client.MultiClientJob) {
continue
}
}
crylog.Info("Current job:", job.JobID, "Difficulty:", blockchain.TargetToDifficulty(job.Target))
stopWorkers()
as := getMiningActivityState()
if as < 0 {
if wasJustMining {
printStats(true)
crylog.Info("Mining is now paused:", as)
wasJustMining = false
stats.ResetRecent()
}
continue
}
if !wasJustMining {
crylog.Info("Mining is now active:", as)
wasJustMining = true
stats.ResetRecent()
} else {
printStats(true)
}
// Check if we need to reinitialize rx dataset
newSeed, err := hex.DecodeString(job.SeedHash)
if err != nil {
@ -360,6 +364,21 @@ func MiningLoop(jobChan <-chan *client.MultiClientJob) {
stats.ResetRecent()
}
as := getMiningActivityState()
if as < 0 {
if wasJustMining {
crylog.Info("Mining is now paused:", as)
wasJustMining = false
stats.ResetRecent()
}
continue
}
if !wasJustMining {
crylog.Info("Mining is now active:", as)
wasJustMining = true
stats.ResetRecent()
}
atomic.StoreUint32(&stopper, 0)
for i := 0; i < threads; i++ {
wg.Add(1)
@ -378,8 +397,8 @@ func stopWorkers() {
stats.RecentStatsNowAccurate()
}
func handlePoke(wasMining bool, poke int) int {
crylog.Info("Handling poke:", poke, wasMining)
func handlePoke(poke int) int {
//crylog.Info("Handling poke:", poke)
switch poke {
case INCREASE_THREADS_POKE:
stopWorkers()
@ -412,6 +431,7 @@ func handlePoke(wasMining bool, poke int) int {
return USE_CACHED
case STATE_CHANGE_POKE:
stats.ResetRecent()
return USE_CACHED
case UPDATE_STATS_POKE:
@ -427,18 +447,17 @@ type GetMiningStateResponse struct {
Threads int
}
func ForceRecentStatsUpdate() {
pokeJobDispatcher(UPDATE_STATS_POKE)
}
func GetMiningState() *GetMiningStateResponse {
as := getMiningActivityState()
var isMining bool
if as > 0 {
isMining = true
}
s, secondsSinceReset, _ := stats.GetSnapshot(isMining)
if secondsSinceReset > 10.0 && s.RecentHashrate < -1.0 {
// We have enough of a window to compute an accurate recent hashrate, so
// force the mining loop to do so for the next call.
pokeJobDispatcher(UPDATE_STATS_POKE)
}
s, _, _ := stats.GetSnapshot(isMining)
configMutex.Lock()
defer configMutex.Unlock()
return &GetMiningStateResponse{
@ -468,23 +487,25 @@ func DecreaseThreads() {
// Poke the job dispatcher. Returns false if the client is not currently alive.
func pokeJobDispatcher(pokeMsg int) {
crylog.Info("Poking job dispatcher:", pokeMsg)
//crylog.Info("Poking job dispatcher:", pokeMsg)
pokeChannel <- pokeMsg
}
/*
func printStats(isMining bool) {
s, _, window := stats.GetSnapshot(isMining)
configMutex.Lock()
if disableStatsPrinting {
configMutex.Unlock()
return
}
crylog.Info("Recent hashrate computation window (seconds):", window)
crylog.Info("=====================================")
//crylog.Info("Shares [accepted:rejected]:", s.SharesAccepted, ":", s.SharesRejected)
//crylog.Info("Hashes [client:pool]:", s.ClientSideHashes, ":", s.PoolSideHashes)
if s.RecentHashrate >= 0.0 {
crylog.Info("Hashrate:", strconv.FormatFloat(s.RecentHashrate, 'f', 2, 64))
//strconv.FormatFloat(s.Hashrate, 'f', 2, 64), ":",
} else {
crylog.Info("Hashrate: --calculating--")
}
configMutex.Lock()
uname := plArgs.Username
crylog.Info("Threads:", threads)
configMutex.Unlock()
@ -507,6 +528,7 @@ func printStats(isMining bool) {
}
crylog.Info("=====================================")
}
*/
func goMine(job client.MultiClientJob, thread int) {
defer wg.Done()
@ -567,6 +589,7 @@ func OverrideMiningActivityState(mine bool) {
return
}
crylog.Info("New mining override state:", newState)
miningOverride = newState
pokeJobDispatcher(STATE_CHANGE_POKE)
}
@ -578,6 +601,7 @@ func RemoveMiningActivityOverride() {
}
crylog.Info("Removing mining override")
miningOverride = 0
pokeJobDispatcher(STATE_CHANGE_POKE)
}
func ReportIdleScreenState(isIdle bool) {
@ -601,3 +625,14 @@ func ReportPowerState(battery bool) {
batteryPower = battery
pokeJobDispatcher(STATE_CHANGE_POKE)
}
// configMutex should be locked before calling
func timeExcluded() bool {
currHr := time.Now().Hour()
startHr := excludeHourStart
endHr := excludeHourEnd
if startHr < endHr {
return currHr >= startHr && currHr < endHr
}
return currHr < startHr && currHr >= endHr
}

View File

@ -1,7 +1,7 @@
package stats
import (
//"github.com/cryptonote-social/csminer/crylog"
// "github.com/cryptonote-social/csminer/crylog"
"encoding/json"
"io/ioutil"
@ -16,9 +16,14 @@ var (
mutex sync.RWMutex
// client side stats
startTime time.Time // when the miner started up
lastResetTime time.Time
lastUpdateTime time.Time
startTime time.Time // when the miner started up
recentStatsResetTime time.Time // last time the user instructed recent stats to be reset
accurateTime time.Time // time of last call to RecentStatsNowAccurate
recentHashesAccurate int64 // snapshotted by RecentStatsNowAccurate
totalHashesAccurate int64 // snapshotted by RecentStatsNowAccurate
sharesAccepted int64
sharesRejected int64
poolSideHashes int64
@ -39,14 +44,19 @@ func Init() {
defer mutex.Unlock()
now := time.Now()
startTime = now
lastResetTime = now
lastUpdateTime = now
recentStatsResetTime = now
accurateTime = now
}
// Call whenever we're at at a point where recent hashrate calculation would be accurate,
// e.g. after all worker threads have been tallied.
func RecentStatsNowAccurate() {
lastUpdateTime = time.Now()
mutex.Lock()
defer mutex.Unlock()
recentHashesAccurate = recentHashes
totalHashesAccurate = clientSideHashes
accurateTime = time.Now()
}
func TallyHashes(hashes int64) {
@ -76,8 +86,8 @@ func ResetRecent() {
defer mutex.Unlock()
recentHashes = 0
now := time.Now()
lastUpdateTime = now
lastResetTime = now
accurateTime = now
recentStatsResetTime = now
}
type Snapshot struct {
@ -92,7 +102,7 @@ type Snapshot struct {
LifetimeHashes int64
Paid, Owed, Accumulated float64
TimeToReward string
SecondsOld int // how many seconds out of date the pool stats are
SecondsOld int // how many seconds out of date the pool stats are, or -1 if none available yet
}
func GetSnapshot(isMining bool) (s *Snapshot, secondsSinceReset float64, secondsRecentWindow float64) {
@ -108,31 +118,25 @@ func GetSnapshot(isMining bool) (s *Snapshot, secondsSinceReset float64, seconds
if isMining {
// if we're actively mining then hash count is only accurate
// as of the last update time
elapsedOverall = lastUpdateTime.Sub(startTime).Seconds()
elapsedOverall = accurateTime.Sub(startTime).Seconds()
} else {
elapsedOverall = time.Now().Sub(startTime).Seconds()
}
// Recent stats are only accurate up to the last update time
elapsedRecent := lastUpdateTime.Sub(lastResetTime).Seconds()
if elapsedOverall > 0.0 {
r.Hashrate = float64(totalHashesAccurate) / elapsedOverall
}
if !isMining {
r.RecentHashrate = 0.0
if elapsedOverall > 0.0 {
r.Hashrate = float64(clientSideHashes) / elapsedOverall
}
} else {
var elapsedRecent float64
if isMining {
// Recent stats are only accurate up to the last snapshot time
elapsedRecent = accurateTime.Sub(recentStatsResetTime).Seconds()
if elapsedRecent > 5.0 {
// For accurate results, we require at least 5 seconds of mining during the recent
// period in order to return a recent hashrate.
r.RecentHashrate = float64(recentHashes) / elapsedRecent
r.RecentHashrate = float64(recentHashesAccurate) / elapsedRecent
} else {
r.RecentHashrate = -1.0 // indicates not enough data
}
if elapsedOverall > 0.0 {
r.Hashrate = float64(clientSideHashes) / elapsedOverall
} else {
r.Hashrate = 0.0
}
}
if lastPoolUsername != "" {
@ -143,8 +147,12 @@ func GetSnapshot(isMining bool) (s *Snapshot, secondsSinceReset float64, seconds
r.Accumulated = accumulated
r.TimeToReward = timeToReward
}
r.SecondsOld = int(time.Now().Sub(lastPoolUpdateTime).Seconds())
return r, time.Now().Sub(lastResetTime).Seconds(), elapsedRecent
if lastPoolUpdateTime.IsZero() {
r.SecondsOld = -1.0
} else {
r.SecondsOld = int(time.Now().Sub(lastPoolUpdateTime).Seconds())
}
return r, time.Now().Sub(recentStatsResetTime).Seconds(), elapsedRecent
}
func RefreshPoolStats(username string) error {