diff --git a/README b/README index 4769e9dd..24b14087 100644 --- a/README +++ b/README @@ -801,6 +801,13 @@ The list of requests - a (*) means it requires privileged access - and replies a restart (*) none There is no status section but just a single "RESTART" reply before cgminer restarts + stats STATS Each device or pool that has 1 or more getworks + with a list of stats regarding getwork times + The values returned by this may change in future + versions thus would not normally be displayed + except when trying to determine performance and + issues with pool perforamce + When you enable, disable or restart a GPU or PGA, you will also get Thread messages in the cgminer status window diff --git a/api.c b/api.c index 6c884373..ea3eeb34 100644 --- a/api.c +++ b/api.c @@ -232,6 +232,7 @@ static const char *OSINFO = #define _DEVDETAILS "DEVDETAILS" #define _BYE "BYE" #define _RESTART "RESTART" +#define _MINESTATS "STATS" static const char ISJSON = '{'; #define JSON0 "{" @@ -265,6 +266,7 @@ static const char ISJSON = '{'; #define JSON_BYE JSON1 _BYE JSON1 #define JSON_RESTART JSON1 _RESTART JSON1 #define JSON_CLOSE JSON3 +#define JSON_MINESTATS JSON1 _MINESTATS JSON2 #define JSON_END JSON4 static const char *JSON_COMMAND = "command"; @@ -351,6 +353,7 @@ static const char *JSON_PARAMETER = "parameter"; #define MSG_ACTPOOL 67 #define MSG_REMPOOL 68 #define MSG_DEVDETAILS 69 +#define MSG_MINESTATS 70 enum code_severity { SEVERITY_ERR, @@ -476,6 +479,7 @@ struct CODES { { SEVERITY_SUCC, MSG_REMPOOL, PARAM_BOTH, "Removed pool %d:'%s'" }, { SEVERITY_SUCC, MSG_NOTIFY, PARAM_NONE, "Notify" }, { SEVERITY_SUCC, MSG_DEVDETAILS,PARAM_NONE, "Device Details" }, + { SEVERITY_SUCC, MSG_MINESTATS,PARAM_NONE, "CGMiner stats" }, { SEVERITY_FAIL, 0, 0, NULL } }; @@ -2000,6 +2004,61 @@ void dosave(__maybe_unused SOCKETTYPE c, char *param, bool isjson) ptr = NULL; } +static int itemstats(int i, char *id, struct cgminer_stats *stats, bool isjson) +{ + char buf[BUFSIZ]; + + if (stats->getwork_calls) + { + sprintf(buf, isjson + ? "%s{\"STATS\":%d,\"ID\":\"%s\",\"Elapsed\":%.0f,\"Calls\":%d,\"Wait\":%ld.%06ld,\"Max\":%ld.%06ld,\"Min\":%ld.%06ld}" + : "%sSTATS=%d,ID=%s,Elapsed=%.0f,Calls=%d,Wait=%ld.%06ld,Max=%ld.%06ld,Min=%ld.%06ld" SEPSTR, + (isjson && (i > 0)) ? COMMA : BLANK, + i, id, total_secs, stats->getwork_calls, + stats->getwork_wait.tv_sec, stats->getwork_wait.tv_usec, + stats->getwork_wait_max.tv_sec, stats->getwork_wait_max.tv_usec, + stats->getwork_wait_min.tv_sec, stats->getwork_wait_min.tv_usec); + + strcat(io_buffer, buf); + + i++; + } + + return i; +} +static void minerstats(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson) +{ + char buf[BUFSIZ]; + int i, j; + + strcpy(io_buffer, message(MSG_MINESTATS, 0, NULL, isjson)); + + if (isjson) { + strcat(io_buffer, COMMA); + strcat(io_buffer, JSON_MINESTATS); + } + + i = 0; + for (j = 0; j < total_devices; j++) { + struct cgpu_info *cgpu = devices[j]; + + if (cgpu && cgpu->api) { + sprintf(buf, "%s%d", cgpu->api->name, cgpu->device_id); + i = itemstats(i, buf, &(cgpu->cgminer_stats), isjson); + } + } + + for (j = 0; j < total_pools; j++) { + struct pool *pool = pools[j]; + + sprintf(buf, "POOL%d", j); + i = itemstats(i, buf, &(pool->cgminer_stats), isjson); + } + + if (isjson) + strcat(io_buffer, JSON_CLOSE); +} + struct CMDS { char *name; void (*func)(SOCKETTYPE, char *, bool); @@ -2041,6 +2100,7 @@ struct CMDS { { "notify", notify, false }, { "devdetails", devdetails, false }, { "restart", dorestart, true }, + { "stats", minerstats, false }, { NULL, NULL, false } }; diff --git a/cgminer.c b/cgminer.c index 60eb842f..d5999f29 100644 --- a/cgminer.c +++ b/cgminer.c @@ -3717,6 +3717,9 @@ void *miner_thread(void *userdata) const int thr_id = mythr->id; struct cgpu_info *cgpu = mythr->cgpu; struct device_api *api = cgpu->api; + struct cgminer_stats *dev_stats = &(cgpu->cgminer_stats); + struct cgminer_stats *pool_stats; + struct timeval getwork_start; /* Try to cycle approximately 5 times before each log update */ const unsigned long def_cycle = opt_log_interval / 5 ? : 1; @@ -3732,6 +3735,8 @@ void *miner_thread(void *userdata) bool requested = false; pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); + gettimeofday(&getwork_start, NULL); + if (api->thread_init && !api->thread_init(mythr)) { cgpu->device_last_not_well = time(NULL); cgpu->device_not_well_reason = REASON_THREAD_FAIL_INIT; @@ -3770,7 +3775,40 @@ void *miner_thread(void *userdata) do { gettimeofday(&tv_start, NULL); + timersub(&tv_start, &getwork_start, &getwork_start); + + timeradd(&getwork_start, + &(dev_stats->getwork_wait), + &(dev_stats->getwork_wait)); + if (timercmp(&getwork_start, &(dev_stats->getwork_wait_max), >)) { + dev_stats->getwork_wait_max.tv_sec = getwork_start.tv_sec; + dev_stats->getwork_wait_max.tv_usec = getwork_start.tv_usec; + } + if (timercmp(&getwork_start, &(dev_stats->getwork_wait_min), <)) { + dev_stats->getwork_wait_min.tv_sec = getwork_start.tv_sec; + dev_stats->getwork_wait_min.tv_usec = getwork_start.tv_usec; + } + dev_stats->getwork_calls++; + + pool_stats = &(work->pool->cgminer_stats); + + timeradd(&getwork_start, + &(pool_stats->getwork_wait), + &(pool_stats->getwork_wait)); + if (timercmp(&getwork_start, &(pool_stats->getwork_wait_max), >)) { + pool_stats->getwork_wait_max.tv_sec = getwork_start.tv_sec; + pool_stats->getwork_wait_max.tv_usec = getwork_start.tv_usec; + } + if (timercmp(&getwork_start, &(pool_stats->getwork_wait_min), <)) { + pool_stats->getwork_wait_min.tv_sec = getwork_start.tv_sec; + pool_stats->getwork_wait_min.tv_usec = getwork_start.tv_usec; + } + pool_stats->getwork_calls++; + hashes = api->scanhash(mythr, work, work->blk.nonce + max_nonce); + + gettimeofday(&getwork_start, NULL); + if (unlikely(work_restart[thr_id].restart)) { /* Apart from device_thread 0, we stagger the @@ -4899,6 +4937,9 @@ int main(int argc, char *argv[]) load_temp_cutoffs(); + for (i = 0; i < total_devices; ++i) + devices[i]->cgminer_stats.getwork_wait_min.tv_sec = MIN_SEC_UNSET; + logstart += total_devices; logcursor = logstart + 1; @@ -4917,6 +4958,8 @@ int main(int argc, char *argv[]) for (i = 0; i < total_pools; i++) { struct pool *pool = pools[i]; + pool->cgminer_stats.getwork_wait_min.tv_sec = MIN_SEC_UNSET; + if (!pool->rpc_userpass) { if (!pool->rpc_user || !pool->rpc_pass) quit(1, "No login credentials supplied for pool %u %s", i, pool->rpc_url); diff --git a/miner.h b/miner.h index a965ecc9..4c58bfd8 100644 --- a/miner.h +++ b/miner.h @@ -252,6 +252,15 @@ enum dev_reason { #define REASON_DEV_THERMAL_CUTOFF_STR "Device reached thermal cutoff" #define REASON_UNKNOWN_STR "Unknown reason - code bug" +#define MIN_SEC_UNSET 99999999 + +struct cgminer_stats { + uint32_t getwork_calls; + struct timeval getwork_wait; + struct timeval getwork_wait_max; + struct timeval getwork_wait_min; +}; + struct cgpu_info { int cgminer_id; struct device_api *api; @@ -324,6 +333,8 @@ struct cgpu_info { int dev_nostart_count; int dev_over_heat_count; // It's a warning but worth knowing int dev_thermal_cutoff_count; + + struct cgminer_stats cgminer_stats; }; extern bool add_cgpu(struct cgpu_info*); @@ -653,6 +664,8 @@ struct pool { struct list_head curlring; time_t last_share_time; + + struct cgminer_stats cgminer_stats; }; struct work {