From 4958ce60072a7bfe4f312f6619f30caac48a4673 Mon Sep 17 00:00:00 2001 From: Tanguy Pruvot Date: Wed, 12 Nov 2014 11:43:55 +0100 Subject: [PATCH] api: add a basic stats api on port 2068 you can use PHP api-example.php as a json wrapper... to be tested on windows... Signed-off-by: Tanguy Pruvot --- Makefile.am | 2 +- api-example.php | 114 ++++++++++ api.c | 479 ++++++++++++++++++++++++++++++++++++++++ ccminer.vcxproj | 1 + ccminer.vcxproj.filters | 3 + cpu-miner.c | 46 +++- miner.h | 32 +++ 7 files changed, 666 insertions(+), 11 deletions(-) create mode 100644 api-example.php create mode 100644 api.c diff --git a/Makefile.am b/Makefile.am index 3a3caa2..26897ef 100644 --- a/Makefile.am +++ b/Makefile.am @@ -17,7 +17,7 @@ ccminer_SOURCES = elist.h miner.h compat.h \ compat/inttypes.h compat/stdbool.h compat/unistd.h \ compat/sys/time.h compat/getopt/getopt.h \ cpu-miner.c util.c crc32.c hefty1.c scrypt.c \ - hashlog.cpp stats.cpp cuda.cu \ + api.c hashlog.cpp stats.cpp cuda.cu \ heavy/heavy.cu \ heavy/cuda_blake512.cu heavy/cuda_blake512.h \ heavy/cuda_combine.cu heavy/cuda_combine.h \ diff --git a/api-example.php b/api-example.php new file mode 100644 index 0000000..86e8ac6 --- /dev/null +++ b/api-example.php @@ -0,0 +1,114 @@ + 0) + { + $items = explode(';', $obj); + $item = $items[0]; + $id = explode('=', $items[0], 2); + if (count($id) == 1) + $name = $id[0]; + else + $name = $id[0].$id[1]; + + if (strlen($name) == 0) + $name = 'null'; + + if (isset($data[$name])) { + $num = 1; + while (isset($data[$name.$num])) + $num++; + $name .= $num; + } + + $counter = 0; + foreach ($items as $item) + { + $id = explode('=', $item, 2); + if (count($id) == 2) + $data[$name][$id[0]] = $id[1]; + else + $data[$name][$counter] = $id[0]; + + $counter++; + } + + } + } + if ($cmd == 'summary') + return array_pop($data); + else + return $data; +} + +ob_start(); +$summary = request('summary'); +$stats = request('stats'); +ob_end_clean(); +//echo ob_get_clean()."\n"; + +header("Content-Type: application/json"); +echo json_encode(compact('summary', 'stats'))."\n"; +?> diff --git a/api.c b/api.c new file mode 100644 index 0000000..6f4f02e --- /dev/null +++ b/api.c @@ -0,0 +1,479 @@ +/* + * Copyright 2014 ccminer team + * + * Implementation by tpruvot (based on cgminer) + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. See COPYING for more details. + */ +#define APIVERSION "1.0" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "compat.h" +#include "miner.h" + +#ifndef WIN32 +# include +# include +# include +# include +# include +# define SOCKETTYPE long +# define SOCKETFAIL(a) ((a) < 0) +# define INVSOCK -1 /* INVALID_SOCKET */ +# define INVINETADDR -1 /* INADDR_NONE */ +# define CLOSESOCKET close +# define SOCKETINIT {} +# define SOCKERRMSG strerror(errno) +#else +# include +# define SOCKETTYPE SOCKET +# define SOCKETFAIL(a) ((a) == SOCKET_ERROR) +# define INVSOCK INVALID_SOCKET +# define INVINETADDR INADDR_NONE +# define CLOSESOCKET closesocket +#endif + +#define GROUP(g) (toupper(g)) +#define PRIVGROUP GROUP('W') +#define NOPRIVGROUP GROUP('R') +#define ISPRIVGROUP(g) (GROUP(g) == PRIVGROUP) +#define GROUPOFFSET(g) (GROUP(g) - GROUP('A')) +#define VALIDGROUP(g) (GROUP(g) >= GROUP('A') && GROUP(g) <= GROUP('Z')) +#define COMMANDS(g) (apigroups[GROUPOFFSET(g)].commands) +#define DEFINEDGROUP(g) (ISPRIVGROUP(g) || COMMANDS(g) != NULL) +struct APIGROUPS { + // This becomes a string like: "|cmd1|cmd2|cmd3|" so it's quick to search + char *commands; +} apigroups['Z' - 'A' + 1]; // only A=0 to Z=25 (R: noprivs, W: allprivs) + +struct IP4ACCESS { + in_addr_t ip; + in_addr_t mask; + char group; +}; + +static int ips = 1; +static struct IP4ACCESS *ipaccess = NULL; + +// Big enough for largest API request +// though a PC with 100s of CPUs may exceed the size ... +// Current code assumes it can socket send this size also +#define MYBUFSIZ 16384 + +// Socket is on 127.0.0.1 +#define QUEUE 10 + +#define LOCAL_ADDR_V4 "127.0.0.1" +static const char *localaddr = LOCAL_ADDR_V4; +static const char *UNAVAILABLE = " - API will not be available"; +static char *buffer = NULL; + +static int bye = 0; + +extern int opt_intensity; +extern int opt_n_threads; +extern int opt_api_listen; +extern uint64_t global_hashrate; +extern uint32_t accepted_count; +extern uint32_t rejected_count; + +char *opt_api_allow = LOCAL_ADDR_V4; +int opt_api_network = 1; +#define gpu_threads opt_n_threads + +extern void get_currentalgo(char* buf, int sz); + +/***************************************************************/ + +static void gpustatus(int thr_id) +{ + char buf[BUFSIZ]; + float gt; + int gf, gp; + + + if (thr_id >= 0 && thr_id < gpu_threads) { + struct cgpu_info *cgpu = &thr_info[thr_id].gpu; + + int total_secs = 1; // todo + cgpu->utility = cgpu->accepted / ( total_secs ? total_secs : 1 ) * 60; + +#ifdef HAVE_HWMONITORING + // todo + if (gpu->has_monitoring) { + gt = gpu_temp(gpu); + gf = gpu_fanspeed(gpu); + gp = gpu_fanpercent(gpu); + } + else +#endif + gt = gf = gp = 0; + + // todo: can be 0 if set by algo (auto) + if (opt_intensity == 0 && opt_work_size) { + int i = 0; + uint32_t ws = opt_work_size; + while (ws > 1 && i++ < 32) + ws = ws >> 1; + cgpu->intensity = i; + } else { + cgpu->intensity = opt_intensity; + } + + // todo: per gpu + cgpu->accepted = accepted_count; + cgpu->rejected = rejected_count; + + cgpu->khashes = stats_get_speed(thr_id) / 1000.0; + + sprintf(buf, "GPU=%d;TEMP=%.1f;FAN=%d;FANP=%d;KHS=%.2f;" + "ACC=%d;REJ=%d;HWF=%d;U=%.2f;I=%d|", + thr_id, gt, gf, gp, cgpu->khashes, + cgpu->accepted, cgpu->rejected, cgpu->hw_errors, + cgpu->utility, cgpu->intensity); + + strcat(buffer, buf); + } +} + +/*****************************************************************************/ + +static char *getsummary(char *params) +{ + char algo[64] = ""; + get_currentalgo(algo, sizeof(algo)); + *buffer = '\0'; + sprintf(buffer, "NAME=%s;VER=%s;API=%s;" + "ALGO=%s;KHS=%.2f|", + PACKAGE_NAME, PACKAGE_VERSION, APIVERSION, + algo, (double)global_hashrate / 1000.0); + return buffer; +} + +static char *getstats(char *params) +{ + *buffer = '\0'; + for (int i = 0; i < gpu_threads; i++) + gpustatus(i); + return buffer; +} + +struct CMDS { + char *name; + char *(*func)(char *); +} cmds[] = { + { "summary", getsummary }, + { "stats", getstats }, +}; + +#define CMDMAX 2 + +static void send_result(int c, char *result) +{ + int n; + + if (result == NULL) + result = ""; + + // ignore failure - it's closed immediately anyway + n = write(c, result, strlen(result)+1); +} + +/* + * Interpret [W:]IP[/Prefix][,[R|W:]IP2[/Prefix2][,...]] --api-allow option + * special case of 0/0 allows /0 (means all IP addresses) + */ +#define ALLIP4 "0/0" +/* + * N.B. IP4 addresses are by Definition 32bit big endian on all platforms + */ +static void setup_ipaccess() +{ + char *buf, *ptr, *comma, *slash, *dot; + int ipcount, mask, octet, i; + char group; + + buf = malloc(strlen(opt_api_allow) + 1); + if (unlikely(!buf)) + proper_exit(1);//, "Failed to malloc ipaccess buf"); + + strcpy(buf, opt_api_allow); + + ipcount = 1; + ptr = buf; + while (*ptr) if (*(ptr++) == ',') + ipcount++; + + // possibly more than needed, but never less + ipaccess = calloc(ipcount, sizeof(struct IP4ACCESS)); + if (unlikely(!ipaccess)) + proper_exit(1);//, "Failed to calloc ipaccess"); + + ips = 0; + ptr = buf; + while (ptr && *ptr) { + while (*ptr == ' ' || *ptr == '\t') + ptr++; + + if (*ptr == ',') { + ptr++; + continue; + } + + comma = strchr(ptr, ','); + if (comma) + *(comma++) = '\0'; + + group = NOPRIVGROUP; + + if (isalpha(*ptr) && *(ptr+1) == ':') { + if (DEFINEDGROUP(*ptr)) + group = GROUP(*ptr); + ptr += 2; + } + + ipaccess[ips].group = group; + + if (strcmp(ptr, ALLIP4) == 0) + ipaccess[ips].ip = ipaccess[ips].mask = 0; + else + { + slash = strchr(ptr, '/'); + if (!slash) + ipaccess[ips].mask = 0xffffffff; + else { + *(slash++) = '\0'; + mask = atoi(slash); + if (mask < 1 || mask > 32) + goto popipo; // skip invalid/zero + + ipaccess[ips].mask = 0; + while (mask-- >= 0) { + octet = 1 << (mask % 8); + ipaccess[ips].mask |= (octet << (24 - (8 * (mask >> 3)))); + } + } + + ipaccess[ips].ip = 0; // missing default to '.0' + for (i = 0; ptr && (i < 4); i++) { + dot = strchr(ptr, '.'); + if (dot) + *(dot++) = '\0'; + + octet = atoi(ptr); + if (octet < 0 || octet > 0xff) + goto popipo; // skip invalid + + ipaccess[ips].ip |= (octet << (24 - (i * 8))); + + ptr = dot; + } + + ipaccess[ips].ip &= ipaccess[ips].mask; + } + + ips++; +popipo: + ptr = comma; + } + + free(buf); +} + +static bool check_connect(struct sockaddr_in *cli, char **connectaddr, char *group) +{ + bool addrok = false; + + *connectaddr = inet_ntoa(cli->sin_addr); + + *group = NOPRIVGROUP; + if (opt_api_allow) { + int client_ip = htonl(cli->sin_addr.s_addr); + for (int i = 0; i < ips; i++) { + if ((client_ip & ipaccess[i].mask) == ipaccess[i].ip) { + addrok = true; + *group = ipaccess[i].group; + break; + } + } + } else if (opt_api_network) + addrok = true; + else + addrok = (strcmp(*connectaddr, localaddr) == 0); + + return addrok; +} + +static void api() +{ + const char *addr = localaddr; + short int port = opt_api_listen; // 4068 + char buf[BUFSIZ]; + int c, n, bound; + char *connectaddr; + char *binderror; + char group; + time_t bindstart; + struct sockaddr_in serv; + struct sockaddr_in cli; + socklen_t clisiz; + bool addrok = false; + long long counter; + char *result; + char *params; + int i; + + SOCKETTYPE *apisock; + if (!opt_api_listen && opt_debug) { + applog(LOG_DEBUG, "API disabled"); + return; + } + + if (opt_api_allow) { + setup_ipaccess(); + if (ips == 0) { + applog(LOG_WARNING, "API not running (no valid IPs specified)%s", UNAVAILABLE); + } + } + + apisock = calloc(1, sizeof(*apisock)); + *apisock = INVSOCK; + + sleep(1); + + *apisock = socket(AF_INET, SOCK_STREAM, 0); + if (*apisock == INVSOCK) { + applog(LOG_ERR, "API initialisation failed (%s)%s", strerror(errno), UNAVAILABLE); + return; + } + + memset(&serv, 0, sizeof(serv)); + serv.sin_family = AF_INET; + serv.sin_addr.s_addr = inet_addr(localaddr); + if (serv.sin_addr.s_addr == (in_addr_t)INVINETADDR) { + applog(LOG_ERR, "API initialisation 2 failed (%s)%s", strerror(errno), UNAVAILABLE); + return; + } + + serv.sin_port = htons(port); + +#ifndef WIN32 + // On linux with SO_REUSEADDR, bind will get the port if the previous + // socket is closed (even if it is still in TIME_WAIT) but fail if + // another program has it open - which is what we want + int optval = 1; + // If it doesn't work, we don't really care - just show a debug message + if (SOCKETFAIL(setsockopt(*apisock, SOL_SOCKET, SO_REUSEADDR, (void *)(&optval), sizeof(optval)))) + applog(LOG_DEBUG, "API setsockopt SO_REUSEADDR failed (ignored): %s", SOCKERRMSG); +#else + // On windows a 2nd program can bind to a port>1024 already in use unless + // SO_EXCLUSIVEADDRUSE is used - however then the bind to a closed port + // in TIME_WAIT will fail until the timeout - so we leave the options alone +#endif + + // try for 1 minute ... in case the old one hasn't completely gone yet + bound = 0; + bindstart = time(NULL); + while (bound == 0) { + if (bind(*apisock, (struct sockaddr *)(&serv), sizeof(serv)) < 0) { + binderror = strerror(errno); + if ((time(NULL) - bindstart) > 61) + break; + else { + applog(LOG_ERR, "API bind to port %d failed - trying again in 15sec", port); + sleep(15); + } + } + else + bound = 1; + } + + + if (bound == 0) { + applog(LOG_ERR, "API bind to port %d failed (%s)%s", port, binderror, UNAVAILABLE); + free(apisock); + return; + } + + if (SOCKETFAIL(listen(*apisock, QUEUE))) { + applog(LOG_ERR, "API initialisation 3 failed (%s)%s", strerror(errno), UNAVAILABLE); + CLOSESOCKET(*apisock); + free(apisock); + return; + } + + buffer = malloc(MYBUFSIZ+1); + + counter = 0; + while (bye == 0) { + counter++; + + clisiz = sizeof(cli); + if (SOCKETFAIL(c = accept(*apisock, (struct sockaddr *)(&cli), &clisiz))) { + applog(LOG_ERR, "API failed (%s)%s", strerror(errno), UNAVAILABLE); + CLOSESOCKET(*apisock); + free(apisock); + free(buffer); + return; + } + + addrok = check_connect(&cli, &connectaddr, &group); + if (opt_protocol) + applog(LOG_DEBUG, "API: connection from %s - %s", + connectaddr, addrok ? "Accepted" : "Ignored"); + + if (addrok) { + n = read(c, &buf[0], BUFSIZ-1); + if (n < 0) + close(c); + else { + buf[n] = '\0'; + params = strchr(buf, '|'); + if (params != NULL) + *(params++) = '\0'; + + for (i = 0; i < CMDMAX; i++) { + if (strcmp(buf, cmds[i].name) == 0) { + result = (cmds[i].func)(params); + send_result(c, result); + close(c); + break; + } + } + } + } + } + + CLOSESOCKET(*apisock); + free(apisock); + free(buffer); +} + +void *api_thread(void *userdata) +{ + struct thr_info *mythr = (struct thr_info*)userdata; + + api(); + + tq_freeze(mythr->q); + + return NULL; +} \ No newline at end of file diff --git a/ccminer.vcxproj b/ccminer.vcxproj index 554b88f..5e5190d 100644 --- a/ccminer.vcxproj +++ b/ccminer.vcxproj @@ -240,6 +240,7 @@ + diff --git a/ccminer.vcxproj.filters b/ccminer.vcxproj.filters index 95543a5..caac514 100644 --- a/ccminer.vcxproj.filters +++ b/ccminer.vcxproj.filters @@ -192,6 +192,9 @@ Source Files + + Source Files + diff --git a/cpu-miner.c b/cpu-miner.c index 4180d19..cf551ce 100644 --- a/cpu-miner.c +++ b/cpu-miner.c @@ -129,7 +129,7 @@ struct workio_cmd { } u; }; -typedef enum { +enum sha_algos { ALGO_ANIME, ALGO_BLAKE, ALGO_BLAKECOIN, @@ -156,7 +156,7 @@ typedef enum { ALGO_X15, ALGO_X17, ALGO_DMD_GR, -} sha256_algos; +}; static const char *algo_names[] = { "anime", @@ -205,12 +205,12 @@ int opt_timeout = 270; static int opt_scantime = 5; static json_t *opt_config; static const bool opt_time = true; -static sha256_algos opt_algo = ALGO_X11; +static enum sha_algos opt_algo = ALGO_X11; int opt_n_threads = 0; static double opt_difficulty = 1; // CH bool opt_trust_pool = false; uint16_t opt_vote = 9999; -static int num_processors; +int num_processors; int device_map[8] = {0,1,2,3,4,5,6,7}; // CB char *device_name[8]; // CB int device_sm[8]; @@ -223,22 +223,26 @@ char *opt_proxy; long opt_proxy_type; struct thr_info *thr_info; static int work_thr_id; +struct thr_api *thr_api; int longpoll_thr_id = -1; int stratum_thr_id = -1; +int api_thr_id = -1; bool stratum_need_reset = false; struct work_restart *work_restart = NULL; static struct stratum_ctx stratum; pthread_mutex_t applog_lock; static pthread_mutex_t stats_lock; -static unsigned long accepted_count = 0L; -static unsigned long rejected_count = 0L; +uint32_t accepted_count = 0L; +uint32_t rejected_count = 0L; static double *thr_hashrates; uint64_t global_hashrate = 0; int opt_statsavg = 20; - +int opt_intensity = 0; uint32_t opt_work_size = 0; /* default */ +int opt_api_listen = 4068; + #ifdef HAVE_GETOPT_LONG #include #else @@ -395,6 +399,11 @@ static struct work _ALIGN(64) g_work; static time_t g_work_time; static pthread_mutex_t g_work_lock; +void get_currentalgo(char* buf, int sz) +{ + snprintf(buf, sz, "%s", algo_names[opt_algo]); +} + /** * Exit app */ @@ -1605,7 +1614,7 @@ static void parse_arg(int key, char *arg) for (i = 0; i < ARRAY_SIZE(algo_names); i++) { if (algo_names[i] && !strcmp(arg, algo_names[i])) { - opt_algo = (sha256_algos)i; + opt_algo = (enum sha_algos)i; break; } } @@ -1634,6 +1643,7 @@ static void parse_arg(int key, char *arg) v = atoi(arg); if (v < 0 || v > 31) show_usage_and_exit(1); + opt_intensity = v; if (v > 0) /* 0 = default */ opt_work_size = (1 << v); break; @@ -2046,10 +2056,10 @@ int main(int argc, char *argv[]) if (!work_restart) return 1; - thr_info = (struct thr_info *)calloc(opt_n_threads + 3, sizeof(*thr)); + thr_info = (struct thr_info *)calloc(opt_n_threads + 4, sizeof(*thr)); if (!thr_info) return 1; - + thr_hashrates = (double *) calloc(opt_n_threads, sizeof(double)); if (!thr_hashrates) return 1; @@ -2103,6 +2113,22 @@ int main(int argc, char *argv[]) tq_push(thr_info[stratum_thr_id].q, strdup(rpc_url)); } + if (opt_api_listen) { + /* api thread */ + api_thr_id = opt_n_threads + 3; + thr = &thr_info[api_thr_id]; + thr->id = api_thr_id; + thr->q = tq_new(); + if (!thr->q) + return 1; + + /* start stratum thread */ + if (unlikely(pthread_create(&thr->pth, NULL, api_thread, thr))) { + applog(LOG_ERR, "api thread create failed"); + return 1; + } + } + /* start mining threads */ for (i = 0; i < opt_n_threads; i++) { thr = &thr_info[i]; diff --git a/miner.h b/miner.h index de82b50..54922cc 100644 --- a/miner.h +++ b/miner.h @@ -352,10 +352,41 @@ extern int scanhash_x17(int thr_id, uint32_t *pdata, const uint32_t *ptarget, uint32_t max_nonce, unsigned long *hashes_done); +/* api related */ +void *api_thread(void *userdata); + +struct cgpu_info { + int accepted; + int rejected; + int hw_errors; + double khashes; + double utility; + int intensity; +#ifdef HAVE_HWMONITORING + bool has_monitoring; + int gpu_engine; + int min_engine; + int gpu_fan; + int min_fan; + int gpu_memclock; + int gpu_memdiff; + int gpu_powertune; + float gpu_vddc; +#endif +}; + +struct thr_api { + int id; + pthread_t pth; + struct thread_q *q; +}; +/* end of api */ + struct thr_info { int id; pthread_t pth; struct thread_q *q; + struct cgpu_info gpu; }; struct work_restart { @@ -382,6 +413,7 @@ extern pthread_mutex_t applog_lock; extern struct thr_info *thr_info; extern int longpoll_thr_id; extern int stratum_thr_id; +extern int api_thr_id; extern struct work_restart *work_restart; extern bool opt_trust_pool; extern uint16_t opt_vote;