mirror of
https://github.com/cryptonote-social/csminer.git
synced 2024-09-11 05:35:23 +03:00
recent stats fixes
This commit is contained in:
parent
a97298f709
commit
faea137ff2
@ -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
|
||||
|
13
csminer.go
13
csminer.go
@ -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
707
miner.go
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user