Browse Source

Merge branch 'cg_merges_20131023b' into bfgminer

Luke Dashjr 12 years ago
parent
commit
be4af52a71
13 changed files with 749 additions and 159 deletions
  1. 55 5
      README
  2. 7 2
      README.FPGA
  3. 82 3
      README.RPC
  4. 69 7
      api.c
  5. 1 1
      configure.ac
  6. 66 28
      driver-icarus.c
  7. 2 0
      icarus-common.h
  8. 8 0
      logging.h
  9. 229 41
      miner.c
  10. 17 3
      miner.h
  11. 197 58
      miner.php
  12. 15 10
      util.c
  13. 1 1
      util.h

+ 55 - 5
README

@@ -213,7 +213,7 @@ Options for both config file and command line:
 --failover-only     Don't leak work to backup pools when primary pool is lagging
 --force-dev-init    Always initialize devices when possible (such as bitstream uploads to some FPGAs)
 --kernel-path|-K <arg> Specify a path to where bitstream and kernel files are (default: "/usr/local/bin")
---load-balance      Change multipool strategy from failover to efficiency based balance
+--load-balance      Change multipool strategy from failover to quota based balance
 --log|-l <arg>      Interval in seconds between log output (default: 5)
 --log-file|-L <arg> Append log file for output messages
 --log-microseconds  Include microseconds in log output
@@ -249,7 +249,7 @@ Options for both config file and command line:
 --shares <arg>      Quit after mining N shares (default: unlimited)
 --show-processors   Show per processor statistics in summary
 --skip-security-checks <arg> Skip security checks sometimes to save bandwidth; only check 1/<arg>th of the time (default: never skip)
---socks-proxy <arg> Set socks4 proxy (host:port) for all pools without a proxy specified
+--socks-proxy <arg> Set socks proxy (host:port) for all pools without a proxy specified
 --stratum-port <arg> Port number to listen on for stratum miners (-1 means disabled) (default: -1)
 --submit-threads    Minimum number of concurrent share submissions (default: 64)
 --syslog            Use system log for output messages (default: standard error)
@@ -496,9 +496,19 @@ This strategy moves at user-defined intervals from one active pool to the next,
 skipping pools that are idle.
 
 LOAD BALANCE:
-This strategy sends work to all the pools to maintain optimum load. The most
-efficient pools will tend to get a lot more shares. If any pool falls idle, the
-rest will tend to take up the slack keeping the miner busy.
+This strategy sends work to all the pools on a quota basis. By default, all
+pools are allocated equal quotas unless specified with --quota. This
+apportioning of work is based on work handed out, not shares returned so is
+independent of difficulty targets or rejected shares. While a pool is disabled
+or dead, its quota is dropped until it is re-enabled. Quotas are forward
+looking, so if the quota is changed on the fly, it only affects future work.
+If all pools are set to zero quota or all pools with quota are dead, it will
+fall back to a failover mode. See quota below for more information.
+
+The failover-only flag has special meaning in combination with load-balance
+mode and it will distribute quota back to priority pool 0 from any pools that
+are unable to provide work for any reason so as to maintain quota ratios
+between the rest of the pools.
 
 BALANCE:
 This strategy monitors the amount of difficulty 1 shares solved for each pool
@@ -530,6 +540,46 @@ bfgminer -o http://localhost:8332 -u username -p password \
     --coinbase-sig "rig1: This is Joe's block!"
 
 
+---
+QUOTAS
+
+The load-balance multipool strategy works off a quota based scheduler. The
+quotas handed out by default are equal, but the user is allowed to specify any
+arbitrary ratio of quotas. For example, if all the quota values add up to 100,
+each quota value will be a percentage, but if 2 pools are specified and pool0
+is given a quota of 1 and pool1 is given a quota of 9, pool0 will get 10% of
+the work and pool1 will get 90%. Quotas can be changed on the fly with RPC,
+and do not act retrospectively. Setting a quota to zero will effectively
+disable that pool unless all other pools are disabled or dead. In that
+scenario, load-balance falls back to regular failover priority-based strategy.
+While a pool is dead, it loses its quota and no attempt is made to catch up
+when it comes back to life.
+
+To specify quotas on the command line, pools should be specified with a
+semicolon separated --quota(or -U) entry instead of --url. Pools specified with
+--url are given a nominal quota value of 1 and entries can be mixed.
+
+For example:
+--url poola:porta -u usernamea -p passa --quota "2;poolb:portb" -u usernameb -p passb
+Will give poola 1/3 of the work and poolb 2/3 of the work.
+
+Writing configuration files with quotas is likewise supported. To use the above
+quotas in a configuration file they would be specified thus:
+
+"pools" : [
+        {
+                "url" : "poola:porta",
+                "user" : "usernamea",
+                "pass" : "passa"
+        },
+        {
+                "quota" : "2;poolb:portb",
+                "user" : "usernameb",
+                "pass" : "passb"
+        }
+]
+
+
 ---
 LOGGING
 

+ 7 - 2
README.FPGA

@@ -162,8 +162,8 @@ an early CM1 Icarus copy bitstream)
 
 --icarus-timing <arg> Set how the Icarus timing is calculated - one setting/value for all or comma separated
            default[=N]   Use the default Icarus hash time (2.6316ns)
-           short         Calculate the hash time and stop adjusting it at ~315 difficulty 1 shares (~1hr)
-           long          Re-calculate the hash time continuously
+           short=[N]     Calculate the hash time and stop adjusting it at ~315 difficulty 1 shares (~1hr)
+           long=[N]      Re-calculate the hash time continuously
            value[=N]     Specify the hash time in nanoseconds (e.g. 2.6316) and abort time (e.g. 2.6316=80)
 
 If you define fewer comma separated values than Icarus devices, the last values
@@ -188,6 +188,11 @@ Any CPU delays while calculating the hash time will affect the result
 ~315 difficulty 1 shares
 'long' mode requires it to always be stable to ensure accuracy, however, over
 time it continually corrects itself
+The optional additional =N for 'short' or 'long' specifies the limit to set the
+timeout to in deciseconds; thus if the timing code calculation is higher while
+running, it will instead use the limit
+This can be set to the appropriate value to ensure the device never goes idle
+even if the calculation is negatively affected by system performance
 
 When in 'short' or 'long' mode, it will report the hash time value each time it
 is re-calculated

+ 82 - 3
README.RPC

@@ -208,6 +208,10 @@ The list of requests - a (*) means it requires privileged access - and replies:
                               stating the results of changing pool priorities
                               See usage below
 
+ poolquota|N,Q (*)
+               none           There is no reply section just the STATUS section
+                              stating the results of changing pool quota to Q
+
  disablepool|N (*)
                none           There is no reply section just the STATUS section
                               stating the results of disabling pool N
@@ -466,6 +470,19 @@ api-example.py - a Python script to access the API
 Feature Changelog for external applications using the API:
 
 
+API V2.1
+
+Added API command:
+ 'poolquota' - Set pool quota for load-balance strategy.
+
+Modified API command:
+ 'devs', 'gpu', 'pga', 'procs' and 'asc' - add 'Device Elapsed', 'Stale',
+                                             'Work Utility', 'Difficulty Stale'
+ 'pools' - add 'Quota'
+ 'summary' - add 'Diff1 Work', 'MHS %ds' (where %d is the log interval)
+
+---------
+
 API V2.0 (BFGMiner v3.3.0)
 
 Removed API commands:
@@ -986,7 +1003,9 @@ or in your bfgminer.conf
 And in miner.php set $mcast = true;
 
 This will ignore the value of $rigs and overwrite it with the list of zero or
-more rigs found on the network in the timout specified
+more rigs found on the network in the timeout specified
+A rig will not reply if the API settings would mean it would also ignore an
+API request from the web server running miner.php
 
 ---------
 
@@ -1057,8 +1076,8 @@ It will be superseded by myminer.php
 
 ---------
 
-The example.php above also shows how to define more that one rig to
-be shown my miner.php
+The example myminer.php above also shows how to define more that one rig
+to be shown my miner.php
 
 Each rig string is 2 or 3 values separated by colons ':'
 They are simply an IP address or host name, followed by the
@@ -1228,6 +1247,14 @@ Set $mcast to true to look for your rigs and ignore $rigs
 
 ---------
 
+Default:
+ $mcastexpect = 0;
+
+The minimum number of rigs expected to be found when $mcast is true
+If fewer are found, an error will be included at the top of the page
+
+---------
+
 Default:
  $mcastaddr = '224.0.0.75';
 
@@ -1269,6 +1296,38 @@ N.B. the accuracy of the timing used to wait for the replies is
 
 ---------
 
+Default:
+ $mcastretries = 0;
+
+Set $mcastretries to the number of times to retry the multicast
+
+If $mcastexpect is 0, this is simply the number of extra times
+that it will send the multicast request
+N.B. BFGMiner doesn't listen for multicast requests for 1000ms after
+each one it hears
+
+If $mcastexpect is > 0, it will stop looking for replies once it
+has found at least $mcastexpect rigs, but it only checks this rig
+limit each time it reaches the $mcasttimeout limit, thus it can find
+more than $mcastexpect rigs if more exist
+It will send the multicast message up to $mcastretries extra times or
+until it has found at least $mcastexpect rigs
+However, when using $mcastretries, it is possible for it to sometimes
+ignore some rigs on the network if $mcastexpect is less than the
+number of rigs on the network and some rigs are too slow to reply
+
+---------
+
+Default:
+ $allowgen = false;
+
+Set $allowgen to true to allow customsummarypages to use 'gen' 
+false means ignore any 'gen' options
+This is disabled by default due to the possible security risk
+of using it, see the end of this document for an explanation
+
+---------
+
 Default:
  $rigipsecurity = true;
 
@@ -1531,6 +1590,7 @@ The example given:
 With BFGMiner 2.10.1 and later, miner.php includes an extension to the custom
 pages that allows you to apply SQL style commands to the data: where, group,
 and having
+BFGMiner 3.4.0 also includes another option 'gen'
 
 As an example, miner.php includes a more complex custom page called 'Pools'
 this includes the extension:
@@ -1542,6 +1602,7 @@ $poolsext = array(
                          'POOL.Stratum Active'),
         'calc' => array('STATS.Bytes Sent' => 'sum',
                         'STATS.Bytes Recv' => 'sum'),
+        'gen' => array('AvShr', 'POOL.Difficulty Accepted/max(POOL.Accepted,1)),
         'having' => array(array('STATS.Bytes Recv', '>', 0)))
 );
 
@@ -1593,3 +1654,21 @@ The first 4 are as expected - the numerical sum, average, minimum or maximum
  of course any valid 'DEVS.Xyz' would give the same 'count' value
 'any' is effectively random: the field value in the 1st row of the grouped data
 An unrecognised 'function' uses 'any'
+
+A 'gen' allows you to generate new fields from any php valid function of any
+of the other fields
+ e.g. 'gen' => array('AvShr', 'POOL.Difficulty Accepted/max(POOL.Accepted,1)),
+will generate a new field called GEN.AvShr that is the function shown, which
+in this case is the average difficulty of each share submitted
+
+THERE IS A SECURITY RISK WITH HOW GEN WORKS
+It simply replaces all the variables with their values and then requests PHP
+to execute the formula - thus if a field value returned from a BFGMiner API
+request contained PHP code, it could be executed by your web server
+Of course BFGMiner doesn't do this, but if you do not control the BFGMiner that
+returns the data in the API calls, someone could modify BFGMiner to return a
+PHP string in a field you use in 'gen'
+Thus use 'gen' at your own risk
+If someone feels the urge to write a mathematical interpreter in PHP to get
+around this risk, feel free to write one and submit it to the API author for
+consideration

+ 69 - 7
api.c

@@ -1,6 +1,6 @@
 /*
  * Copyright 2011-2013 Andrew Smith
- * Copyright 2011-2012 Con Kolivas
+ * Copyright 2011-2013 Con Kolivas
  * Copyright 2012-2013 Luke Dashjr
  *
  * This program is free software; you can redistribute it and/or modify it
@@ -59,7 +59,7 @@ static const char SEPARATOR = '|';
 #define SEPSTR "|"
 static const char GPUSEP = ',';
 
-static const char *APIVERSION = "1.25.3";
+static const char *APIVERSION = "2.1";
 static const char *DEAD = "Dead";
 static const char *SICK = "Sick";
 static const char *NOSTART = "NoStart";
@@ -351,6 +351,9 @@ static const char *JSON_PARAMETER = "parameter";
 
 #define MSG_DEVSCAN 0x100
 
+#define MSG_INVNEG 121
+#define MSG_SETQUOTA 122
+
 enum code_severity {
 	SEVERITY_ERR,
 	SEVERITY_WARN,
@@ -519,6 +522,8 @@ struct CODES {
  { SEVERITY_SUCC,  MSG_SETCONFIG,PARAM_SET,	"Set config '%s' to %d" },
  { SEVERITY_ERR,   MSG_UNKCON,	PARAM_STR,	"Unknown config '%s'" },
  { SEVERITY_ERR,   MSG_INVNUM,	PARAM_BOTH,	"Invalid number (%d) for '%s' range is 0-9999" },
+ { SEVERITY_ERR,   MSG_INVNEG,	PARAM_BOTH,	"Invalid negative number (%d) for '%s'" },
+ { SEVERITY_SUCC,  MSG_SETQUOTA,PARAM_SET,	"Set pool '%s' to quota %d'" },
  { SEVERITY_ERR,   MSG_CONPAR,	PARAM_NONE,	"Missing config parameters 'name,N'" },
  { SEVERITY_ERR,   MSG_CONVAL,	PARAM_STR,	"Missing config value N for '%s,N'" },
 #ifdef HAVE_AN_FPGA
@@ -1495,13 +1500,14 @@ void devstatus_an(struct io_data *io_data, struct cgpu_info *cgpu, bool isjson,
 
 	n = find_index_by_cgpu(cgpu);
 
+	double runtime = cgpu_runtime(cgpu);
 	bool enabled = false;
 	double total_mhashes = 0, rolling = 0, utility = 0;
 	enum alive status = cgpu->status;
 	float temp = -1;
-	int accepted = 0, rejected = 0, hw_errors = 0;
+	int accepted = 0, rejected = 0, stale = 0, hw_errors = 0;
 	int diff1 = 0, bad_nonces = 0;
-	double diff_accepted = 0, diff_rejected = 0;
+	double diff_accepted = 0, diff_rejected = 0, diff_stale = 0;
 	int last_share_pool = -1;
 	time_t last_share_pool_time = -1, last_device_valid_work = -1;
 	double last_share_diff = -1;
@@ -1516,10 +1522,12 @@ void devstatus_an(struct io_data *io_data, struct cgpu_info *cgpu, bool isjson,
 		utility += proc->utility;
 		accepted += proc->accepted;
 		rejected += proc->rejected;
+		stale += proc->stale;
 		hw_errors += proc->hw_errors;
 		diff1 += proc->diff1;
 		diff_accepted += proc->diff_accepted;
 		diff_rejected += proc->diff_rejected;
+		diff_stale += proc->diff_stale;
 		bad_nonces += proc->bad_nonces;
 		if (status != proc->status)
 			status = LIFE_MIXED;
@@ -1543,7 +1551,9 @@ void devstatus_an(struct io_data *io_data, struct cgpu_info *cgpu, bool isjson,
 	root = api_add_string(root, "Status", status2str(status), false);
 	if (temp > 0)
 		root = api_add_temp(root, "Temperature", &temp, false);
-	double mhs = total_mhashes / cgpu_runtime(cgpu);
+	
+	root = api_add_elapsed(root, "Device Elapsed", &runtime, false);
+	double mhs = total_mhashes / runtime;
 	root = api_add_mhs(root, "MHS av", &mhs, false);
 	char mhsname[27];
 	sprintf(mhsname, "MHS %ds", opt_log_interval);
@@ -1552,15 +1562,19 @@ void devstatus_an(struct io_data *io_data, struct cgpu_info *cgpu, bool isjson,
 	root = api_add_int(root, "Rejected", &rejected, false);
 	root = api_add_int(root, "Hardware Errors", &hw_errors, false);
 	root = api_add_utility(root, "Utility", &utility, false);
+	root = api_add_int(root, "Stale", &stale, false);
 	if (last_share_pool != -1)
 	{
 		root = api_add_int(root, "Last Share Pool", &last_share_pool, false);
 		root = api_add_time(root, "Last Share Time", &last_share_pool_time, false);
 	}
 	root = api_add_mhtotal(root, "Total MH", &total_mhashes, false);
+	double work_utility = diff1 / runtime;
 	root = api_add_int(root, "Diff1 Work", &diff1, false);
+	root = api_add_utility(root, "Work Utility", &work_utility, false);
 	root = api_add_diff(root, "Difficulty Accepted", &diff_accepted, false);
 	root = api_add_diff(root, "Difficulty Rejected", &diff_rejected, false);
+	root = api_add_diff(root, "Difficulty Stale", &diff_stale, false);
 	if (last_share_diff > 0)
 		root = api_add_diff(root, "Last Share Difficulty", &last_share_diff, false);
 	if (last_device_valid_work != -1)
@@ -1976,6 +1990,7 @@ static void poolstatus(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __m
 		root = api_add_escape(root, "URL", pool->rpc_url, false);
 		root = api_add_string(root, "Status", status, false);
 		root = api_add_int(root, "Priority", &(pool->prio), false);
+		root = api_add_int(root, "Quota", &pool->quota, false);
 		root = api_add_string(root, "Long Poll", lp, false);
 		root = api_add_uint(root, "Getworks", &(pool->getwork_requested), false);
 		root = api_add_int(root, "Accepted", &(pool->accepted), false);
@@ -2049,6 +2064,9 @@ static void summary(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __mayb
 	root = api_add_string(root, "Algorithm", algo, false);
 #endif
 	root = api_add_mhs(root, "MHS av", &(mhs), false);
+	char mhsname[27];
+	sprintf(mhsname, "MHS %ds", opt_log_interval);
+	root = api_add_mhs(root, mhsname, &(total_rolling), false);
 	root = api_add_uint(root, "Found Blocks", &(found_blocks), true);
 	root = api_add_int(root, "Getworks", &(total_getworks), true);
 	root = api_add_int(root, "Accepted", &(total_accepted), true);
@@ -2062,6 +2080,7 @@ static void summary(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __mayb
 	root = api_add_uint(root, "Remote Failures", &(total_ro), true);
 	root = api_add_uint(root, "Network Blocks", &(new_blocks), true);
 	root = api_add_mhtotal(root, "Total MH", &(total_mhashes_done), true);
+	root = api_add_int(root, "Diff1 Work", &total_diff1, true);
 	root = api_add_utility(root, "Work Utility", &(work_utility), false);
 	root = api_add_diff(root, "Difficulty Accepted", &(total_diff_accepted), true);
 	root = api_add_diff(root, "Difficulty Rejected", &(total_diff_rejected), true);
@@ -2523,6 +2542,48 @@ static void poolpriority(struct io_data *io_data, __maybe_unused SOCKETTYPE c, c
 	}
 }
 
+static void poolquota(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
+{
+	struct pool *pool;
+	int quota, id;
+	char *comma;
+
+	if (total_pools == 0) {
+		message(io_data, MSG_NOPOOL, 0, NULL, isjson);
+		return;
+	}
+
+	if (param == NULL || *param == '\0') {
+		message(io_data, MSG_MISPID, 0, NULL, isjson);
+		return;
+	}
+
+	comma = strchr(param, ',');
+	if (!comma) {
+		message(io_data, MSG_CONVAL, 0, param, isjson);
+		return;
+	}
+
+	*(comma++) = '\0';
+
+	id = atoi(param);
+	if (id < 0 || id >= total_pools) {
+		message(io_data, MSG_INVPID, id, NULL, isjson);
+		return;
+	}
+	pool = pools[id];
+
+	quota = atoi(comma);
+	if (quota < 0) {
+		message(io_data, MSG_INVNEG, quota, pool->rpc_url, isjson);
+		return;
+	}
+
+	pool->quota = quota;
+	adjust_quota_gcd();
+	message(io_data, MSG_SETQUOTA, quota, pool->rpc_url, isjson);
+}
+
 static void disablepool(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
 {
 	struct pool *pool;
@@ -3381,6 +3442,7 @@ struct CMDS {
 	{ "switchpool",		switchpool,	true },
 	{ "addpool",		addpool,	true },
 	{ "poolpriority",	poolpriority,	true },
+	{ "poolquota",		poolquota,	true },
 	{ "enablepool",		enablepool,	true },
 	{ "disablepool",	disablepool,	true },
 	{ "removepool",		removepool,	true },
@@ -3909,9 +3971,9 @@ static void mcast()
 				reply_sock = socket(AF_INET, SOCK_DGRAM, 0);
 
 				snprintf(replybuf, sizeof(replybuf),
-							"cgm-%s-%d",
+							"cgm-%s-%d-%s",
 							opt_api_mcast_code,
-							opt_api_port);
+							opt_api_port, opt_api_mcast_des);
 
 				rep = sendto(reply_sock, replybuf, strlen(replybuf)+1,
 						0, (struct sockaddr *)(&came_from),

+ 1 - 1
configure.ac

@@ -31,7 +31,7 @@ AC_CONFIG_MACRO_DIR([m4])
 AC_CONFIG_SRCDIR([miner.c])
 AC_CONFIG_HEADERS([config.h])
 
-AM_INIT_AUTOMAKE([foreign])
+AM_INIT_AUTOMAKE([foreign subdir-objects])
 m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
 AC_USE_SYSTEM_EXTENSIONS
 

+ 66 - 28
driver-icarus.c

@@ -79,6 +79,13 @@ ASSERT1(sizeof(uint32_t) == 4);
 
 #define ICARUS_READ_TIME(baud) ((double)ICARUS_READ_SIZE * (double)8.0 / (double)(baud))
 
+// Defined in deciseconds
+// There's no need to have this bigger, since the overhead/latency of extra work
+// is pretty small once you get beyond a 10s nonce range time and 10s also
+// means that nothing slower than 429MH/s can go idle so most icarus devices
+// will always mine without idling
+#define ICARUS_READ_COUNT_LIMIT_MAX 100
+
 // In timing mode: Default starting value until an estimate can be obtained
 // 5 seconds allows for up to a ~840MH/s device
 #define ICARUS_READ_COUNT_TIMING	(5 * TIME_FACTOR)
@@ -134,7 +141,9 @@ static struct timeval history_sec = { HISTORY_SEC, 0 };
 
 static const char *MODE_DEFAULT_STR = "default";
 static const char *MODE_SHORT_STR = "short";
+static const char *MODE_SHORT_STREQ = "short=";
 static const char *MODE_LONG_STR = "long";
+static const char *MODE_LONG_STREQ = "long=";
 static const char *MODE_VALUE_STR = "value";
 static const char *MODE_UNKNOWN_STR = "unknown";
 
@@ -260,10 +269,7 @@ int icarus_gets(unsigned char *buf, int fd, struct timeval *tv_finish, struct th
 		if (thr && thr->work_restart) {
 			if (epollfd != -1)
 				close(epollfd);
-			if (opt_debug) {
-				applog(LOG_DEBUG,
-					"Icarus Read: Interrupted by work restart");
-			}
+			applog(LOG_DEBUG, "Icarus Read: Interrupted by work restart");
 			return ICA_GETS_RESTART;
 		}
 
@@ -271,11 +277,8 @@ int icarus_gets(unsigned char *buf, int fd, struct timeval *tv_finish, struct th
 		if (rc >= read_count) {
 			if (epollfd != -1)
 				close(epollfd);
-			if (opt_debug) {
-				applog(LOG_DEBUG,
-					"Icarus Read: No data in %.2f seconds",
-					(float)rc * epoll_timeout / 1000.);
-			}
+			applog(LOG_DEBUG, "Icarus Read: No data in %.2f seconds",
+			       (float)rc * epoll_timeout / 1000.);
 			return ICA_GETS_TIMEOUT;
 		}
 	}
@@ -356,18 +359,46 @@ static void set_timing_mode(int this_option_offset, struct cgpu_info *icarus)
 	}
 
 	info->read_count = 0;
+	info->read_count_limit = 0; // 0 = no limit
 
 	if (strcasecmp(buf, MODE_SHORT_STR) == 0) {
+		// short
 		info->read_count = ICARUS_READ_COUNT_TIMING;
 
 		info->timing_mode = MODE_SHORT;
 		info->do_icarus_timing = true;
+	} else if (strncasecmp(buf, MODE_SHORT_STREQ, strlen(MODE_SHORT_STREQ)) == 0) {
+		// short=limit
+		info->read_count = ICARUS_READ_COUNT_TIMING;
+
+		info->timing_mode = MODE_SHORT;
+		info->do_icarus_timing = true;
+
+		info->read_count_limit = atoi(&buf[strlen(MODE_SHORT_STREQ)]);
+		if (info->read_count_limit < 0)
+			info->read_count_limit = 0;
+		if (info->read_count_limit > ICARUS_READ_COUNT_LIMIT_MAX)
+			info->read_count_limit = ICARUS_READ_COUNT_LIMIT_MAX;
 	} else if (strcasecmp(buf, MODE_LONG_STR) == 0) {
+		// long
 		info->read_count = ICARUS_READ_COUNT_TIMING;
 
 		info->timing_mode = MODE_LONG;
 		info->do_icarus_timing = true;
+	} else if (strncasecmp(buf, MODE_LONG_STREQ, strlen(MODE_LONG_STREQ)) == 0) {
+		// long=limit
+		info->read_count = ICARUS_READ_COUNT_TIMING;
+
+		info->timing_mode = MODE_LONG;
+		info->do_icarus_timing = true;
+
+		info->read_count_limit = atoi(&buf[strlen(MODE_LONG_STREQ)]);
+		if (info->read_count_limit < 0)
+			info->read_count_limit = 0;
+		if (info->read_count_limit > ICARUS_READ_COUNT_LIMIT_MAX)
+			info->read_count_limit = ICARUS_READ_COUNT_LIMIT_MAX;
 	} else if ((Hs = atof(buf)) != 0) {
+		// ns[=read_count]
 		info->Hs = Hs / NANOSEC;
 		info->fullnonce = info->Hs * (((double)0xffffffff) + 1);
 
@@ -407,9 +438,10 @@ static void set_timing_mode(int this_option_offset, struct cgpu_info *icarus)
 
 	info->min_data_count = MIN_DATA_COUNT;
 
-	applog(LOG_DEBUG, "%"PRIpreprv": Init: mode=%s read_count=%d Hs=%e",
+	applog(LOG_DEBUG, "%"PRIpreprv": Init: mode=%s read_count=%d limit=%dms Hs=%e",
 		icarus->proc_repr,
-		timing_mode_str(info->timing_mode), info->read_count, info->Hs);
+		timing_mode_str(info->timing_mode),
+		info->read_count, info->read_count_limit, info->Hs);
 }
 
 static uint32_t mask(int work_division)
@@ -917,6 +949,7 @@ static int64_t icarus_scanhash(struct thr_info *thr, struct work *work,
 	int count;
 	double Hs, W, fullnonce;
 	int read_count;
+	bool limited;
 	int64_t estimate_hashes;
 	uint32_t values;
 	int64_t hash_count_range;
@@ -1062,12 +1095,10 @@ keepwaiting:
 		if (unlikely(estimate_hashes > 0xffffffff))
 			estimate_hashes = 0xffffffff;
 
-		if (opt_debug) {
-			applog(LOG_DEBUG, "%"PRIpreprv" no nonce = 0x%08"PRIx64" hashes (%"PRId64".%06lus)",
-					icarus->proc_repr,
-					(uint64_t)estimate_hashes,
-					(int64_t)elapsed.tv_sec, (unsigned long)elapsed.tv_usec);
-		}
+		applog(LOG_DEBUG, "%"PRIpreprv" no nonce = 0x%08"PRIx64" hashes (%"PRId64".%06lus)",
+		       icarus->proc_repr,
+		       (uint64_t)estimate_hashes,
+		       (int64_t)elapsed.tv_sec, (unsigned long)elapsed.tv_usec);
 
 		hash_count = estimate_hashes;
 		goto out;
@@ -1085,13 +1116,11 @@ keepwaiting:
 	hash_count++;
 	hash_count *= info->fpga_count;
 
-	if (opt_debug) {
-		applog(LOG_DEBUG, "%"PRIpreprv" nonce = 0x%08x = 0x%08" PRIx64 " hashes (%"PRId64".%06lus)",
-				icarus->proc_repr,
-				nonce,
-				(uint64_t)hash_count,
-				(int64_t)elapsed.tv_sec, (unsigned long)elapsed.tv_usec);
-	}
+	applog(LOG_DEBUG, "%"PRIpreprv" nonce = 0x%08x = 0x%08" PRIx64 " hashes (%"PRId64".%06lus)",
+	       icarus->proc_repr,
+	       nonce,
+	       (uint64_t)hash_count,
+	       (int64_t)elapsed.tv_sec, (unsigned long)elapsed.tv_usec);
 
 	if (info->do_default_detection && elapsed.tv_sec >= DEFAULT_DETECT_THRESHOLD) {
 		int MHs = (double)hash_count / ((double)elapsed.tv_sec * 1e6 + (double)elapsed.tv_usec);
@@ -1124,7 +1153,9 @@ keepwaiting:
 		}
 	}
 
-	// ignore possible end condition values ... and hw errors
+	// Ignore possible end condition values ... and hw errors
+	// TODO: set limitations on calculated values depending on the device
+	// to avoid crap values caused by CPU/Task Switching/Swapping/etc
 	if (info->do_icarus_timing
 	&&  !was_hw_error
 	&&  ((nonce & info->nonce_mask) > END_CONDITION)
@@ -1197,6 +1228,11 @@ keepwaiting:
 
 			fullnonce = W + Hs * (((double)0xffffffff) + 1);
 			read_count = (int)(fullnonce * TIME_FACTOR) - 1;
+			if (info->read_count_limit > 0 && read_count > info->read_count_limit) {
+				read_count = info->read_count_limit;
+				limited = true;
+			} else
+				limited = false;
 
 			info->Hs = Hs;
 			info->read_count = read_count;
@@ -1212,10 +1248,11 @@ keepwaiting:
 			else if (info->timing_mode == MODE_SHORT)
 				info->do_icarus_timing = false;
 
-//			applog(LOG_DEBUG, "%"PRIpreprv" Re-estimate: read_count=%d fullnonce=%fs history count=%d Hs=%e W=%e values=%d hash range=0x%08lx min data count=%u", icarus->proc_repr, read_count, fullnonce, count, Hs, W, values, hash_count_range, info->min_data_count);
-			applog(LOG_DEBUG, "%"PRIpreprv" Re-estimate: Hs=%e W=%e read_count=%d fullnonce=%.3fs",
+//			applog(LOG_DEBUG, "%"PRIpreprv" Re-estimate: read_count=%d%s fullnonce=%fs history count=%d Hs=%e W=%e values=%d hash range=0x%08lx min data count=%u", icarus->proc_repr, read_count, limited ? " (limited)" : "", fullnonce, count, Hs, W, values, hash_count_range, info->min_data_count);
+			applog(LOG_DEBUG, "%"PRIpreprv" Re-estimate: Hs=%e W=%e read_count=%d%s fullnonce=%.3fs",
 					icarus->proc_repr,
-					Hs, W, read_count, fullnonce);
+					Hs, W, read_count,
+					limited ? " (limited)" : "", fullnonce);
 		}
 		info->history_count++;
 		cgtime(&tv_history_finish);
@@ -1241,6 +1278,7 @@ static struct api_data *icarus_drv_stats(struct cgpu_info *cgpu)
 	// locking access to displaying API debug 'stats'
 	// If locking becomes an issue for any of them, use copy_data=true also
 	root = api_add_int(root, "read_count", &(info->read_count), false);
+	root = api_add_int(root, "read_count_limit", &(info->read_count_limit), false);
 	root = api_add_double(root, "fullnonce", &(info->fullnonce), false);
 	root = api_add_int(root, "count", &(info->count), false);
 	root = api_add_hs(root, "Hs", &(info->Hs), false);

+ 2 - 0
icarus-common.h

@@ -60,6 +60,8 @@ struct ICARUS_INFO {
 	// seconds per Hash
 	double Hs;
 	int read_count;
+	// ds limit for (short=/long=) read_count
+	int read_count_limit;
 
 	enum timing_mode timing_mode;
 	bool do_icarus_timing;

+ 8 - 0
logging.h

@@ -57,6 +57,14 @@ extern void _applog(int prio, const char *str);
 	} \
 } while (0)
 
+#define applogsiz(prio, _SIZ, fmt, ...) do { \
+	if (opt_debug || prio != LOG_DEBUG) { \
+			char tmp42[_SIZ]; \
+			snprintf(tmp42, sizeof(tmp42), fmt, ##__VA_ARGS__); \
+			_applog(prio, tmp42); \
+	} \
+} while (0)
+
 #define applogr(rv, prio, ...)  do {  \
 	applog(prio, __VA_ARGS__);  \
 	return rv;  \

+ 229 - 41
miner.c

@@ -148,6 +148,7 @@ int opt_expiry_lp = 3600;
 int opt_bench_algo = -1;
 unsigned long long global_hashrate;
 static bool opt_unittest = false;
+unsigned long global_quota_gcd = 1;
 
 #ifdef HAVE_OPENCL
 int opt_dynamic_interval = 7;
@@ -210,6 +211,7 @@ bool opt_api_listen;
 bool opt_api_mcast;
 char *opt_api_mcast_addr = API_MCAST_ADDR;
 char *opt_api_mcast_code = API_MCAST_CODE;
+char *opt_api_mcast_des = "";
 int opt_api_mcast_port = 4028;
 bool opt_api_network;
 bool opt_delaynet;
@@ -261,6 +263,7 @@ pthread_cond_t gws_cond;
 
 bool shutting_down;
 
+double total_rolling;
 double total_mhashes_done;
 static struct timeval total_tv_start, total_tv_end;
 static struct timeval miner_started;
@@ -604,6 +607,47 @@ static void sharelog(const char*disposition, const struct work*work)
 
 static char *getwork_req = "{\"method\": \"getwork\", \"params\": [], \"id\":0}\n";
 
+/* Adjust all the pools' quota to the greatest common denominator after a pool
+ * has been added or the quotas changed. */
+void adjust_quota_gcd(void)
+{
+	unsigned long gcd, lowest_quota = ~0UL, quota;
+	struct pool *pool;
+	int i;
+
+	for (i = 0; i < total_pools; i++) {
+		pool = pools[i];
+		quota = pool->quota;
+		if (!quota)
+			continue;
+		if (quota < lowest_quota)
+			lowest_quota = quota;
+	}
+
+	if (likely(lowest_quota < ~0UL)) {
+		gcd = lowest_quota;
+		for (i = 0; i < total_pools; i++) {
+			pool = pools[i];
+			quota = pool->quota;
+			if (!quota)
+				continue;
+			while (quota % gcd)
+				gcd--;
+		}
+	} else
+		gcd = 1;
+
+	for (i = 0; i < total_pools; i++) {
+		pool = pools[i];
+		pool->quota_used *= global_quota_gcd;
+		pool->quota_used /= gcd;
+		pool->quota_gcd = pool->quota / gcd;
+	}
+
+	global_quota_gcd = gcd;
+	applog(LOG_DEBUG, "Global quota greatest common denominator set to %lu", gcd);
+}
+
 /* Return value is ignored if not called from add_pool_details */
 struct pool *add_pool(void)
 {
@@ -627,6 +671,8 @@ struct pool *add_pool(void)
 	cgtime(&pool->cgminer_stats.start_tv);
 
 	pool->rpc_proxy = NULL;
+	pool->quota = 1;
+	adjust_quota_gcd();
 
 	pool->sock = INVSOCK;
 	pool->lp_socket = CURL_SOCKET_BAD;
@@ -1011,7 +1057,7 @@ static char *set_rr(enum pool_strategy *strategy)
  * stratum+tcp or by detecting a stratum server response */
 bool detect_stratum(struct pool *pool, char *url)
 {
-	if (!extract_sockaddr(pool, url))
+	if (!extract_sockaddr(url, &pool->sockaddr_url, &pool->stratum_port))
 		return false;
 
 	if (!strncasecmp(url, "stratum+tcp://", 14)) {
@@ -1024,17 +1070,18 @@ bool detect_stratum(struct pool *pool, char *url)
 	return false;
 }
 
-static char *set_url(char *arg)
+static struct pool *add_url(void)
 {
-	struct pool *pool;
-
 	total_urls++;
 	if (total_urls > total_pools)
 		add_pool();
-	pool = pools[total_urls - 1];
+	return pools[total_urls - 1];
+}
 
+static void setup_url(struct pool *pool, char *arg)
+{
 	if (detect_stratum(pool, arg))
-		return NULL;
+		return;
 
 	opt_set_charp(arg, &pool->rpc_url);
 	if (strncmp(arg, "http://", 7) &&
@@ -1048,6 +1095,41 @@ static char *set_url(char *arg)
 		strncat(httpinput, arg, 248);
 		pool->rpc_url = httpinput;
 	}
+}
+
+static char *set_url(char *arg)
+{
+	struct pool *pool = add_url();
+
+	setup_url(pool, arg);
+	return NULL;
+}
+
+static char *set_quota(char *arg)
+{
+	char *semicolon = strchr(arg, ';'), *url;
+	int len, qlen, quota;
+	struct pool *pool;
+
+	if (!semicolon)
+		return "No semicolon separated quota;URL pair found";
+	len = strlen(arg);
+	*semicolon = '\0';
+	qlen = strlen(arg);
+	if (!qlen)
+		return "No parameter for quota found";
+	len -= qlen + 1;
+	if (len < 1)
+		return "No parameter for URL found";
+	quota = atoi(arg);
+	if (quota < 0)
+		return "Invalid negative parameter for quota set";
+	url = arg + qlen + 1;
+	pool = add_url();
+	setup_url(pool, url);
+	pool->quota = quota;
+	applog(LOG_INFO, "Setting pool %d to quota %d", pool->pool_no, pool->quota);
+	adjust_quota_gcd();
 
 	return NULL;
 }
@@ -1378,6 +1460,13 @@ static char *set_api_description(const char *arg)
 	return NULL;
 }
 
+static char *set_api_mcast_des(const char *arg)
+{
+	opt_set_charp(arg, &opt_api_mcast_des);
+
+	return NULL;
+}
+
 #ifdef USE_ICARUS
 static char *set_icarus_options(const char *arg)
 {
@@ -1463,6 +1552,9 @@ static struct opt_table opt_config_table[] = {
 	OPT_WITH_ARG("--api-mcast-code",
 		     opt_set_charp, opt_show_charp, &opt_api_mcast_code,
 		     "Code expected in the API Multicast message, don't use '-'"),
+	OPT_WITH_ARG("--api-mcast-des",
+		     set_api_mcast_des, NULL, NULL,
+		     "Description appended to the API Multicast reply, default: ''"),
 	OPT_WITH_ARG("--api-mcast-port",
 		     set_int_1_to_65535, opt_show_intval, &opt_api_mcast_port,
 		     "API Multicast listen port"),
@@ -1657,7 +1749,7 @@ static struct opt_table opt_config_table[] = {
 #endif
 	OPT_WITHOUT_ARG("--load-balance",
 		     set_loadbalance, &pool_strategy,
-		     "Change multipool strategy from failover to efficiency based balance"),
+		     "Change multipool strategy from failover to quota based balance"),
 	OPT_WITH_ARG("--log|-l",
 		     set_int_0_to_9999, opt_show_intval, &opt_log_interval,
 		     "Interval in seconds between log output"),
@@ -1758,6 +1850,9 @@ static struct opt_table opt_config_table[] = {
 	OPT_WITHOUT_ARG("--quiet-work-updates|--quiet-work-update",
 			opt_set_bool, &opt_quiet_work_updates,
 			opt_hidden),
+	OPT_WITH_ARG("--quota|-U",
+		     set_quota, NULL, NULL,
+		     "quota;URL combination for server with load-balance strategy quotas"),
 	OPT_WITHOUT_ARG("--real-quiet",
 			opt_set_bool, &opt_realquiet,
 			"Disable all output"),
@@ -1826,7 +1921,7 @@ static struct opt_table opt_config_table[] = {
 			"Skip security checks sometimes to save bandwidth; only check 1/<arg>th of the time (default: never skip)"),
 	OPT_WITH_ARG("--socks-proxy",
 		     opt_set_charp, NULL, &opt_socks_proxy,
-		     "Set socks4 proxy (host:port)"),
+		     "Set socks proxy (host:port)"),
 #ifdef USE_LIBEVENT
 	OPT_WITH_ARG("--stratum-port",
 	             opt_set_intval, opt_show_intval, &stratumsrv_port,
@@ -1926,6 +2021,9 @@ static char *parse_config(json_t *config, bool fileconf)
 		/* We don't handle subtables. */
 		assert(!(opt->type & OPT_SUBTABLE));
 
+		if (!opt->names)
+			continue;
+
 		/* Pull apart the option name(s). */
 		name = strdup(opt->names);
 		for (p = strtok_r(name, "|", &sp); p; p = strtok_r(NULL, "|", &sp)) {
@@ -3291,7 +3389,7 @@ static void curses_print_status(const int ts)
 		best_share);
 	wclrtoeol(statuswin);
 	if ((pool_strategy == POOL_LOADBALANCE  || pool_strategy == POOL_BALANCE) && total_pools > 1) {
-		cg_mvwprintw(statuswin, 2, 0, " Connected to multiple pools with%s LP",
+		cg_mvwprintw(statuswin, 2, 0, " Connected to multiple pools with%s block change notify",
 			have_longpoll ? "": "out");
 	} else if (pool->has_stratum) {
 		cg_mvwprintw(statuswin, 2, 0, " Connected to %s diff %s with stratum as user %s",
@@ -3935,43 +4033,84 @@ static struct pool *select_balanced(struct pool *cp)
 
 static bool pool_active(struct pool *, bool pinging);
 static void pool_died(struct pool *);
+static struct pool *priority_pool(int choice);
+static bool pool_unusable(struct pool *pool);
 
-/* Select any active pool in a rotating fashion when loadbalance is chosen */
+/* Select any active pool in a rotating fashion when loadbalance is chosen if
+ * it has any quota left. */
 static inline struct pool *select_pool(bool lagging)
 {
 	static int rotating_pool = 0;
 	struct pool *pool, *cp;
-	int tested;
+	bool avail = false;
+	int tested, i;
 
 	cp = current_pool();
 
 retry:
-	if (pool_strategy == POOL_BALANCE)
-	{
+	if (pool_strategy == POOL_BALANCE) {
 		pool = select_balanced(cp);
-		goto have_pool;
+		goto out;
 	}
 
-	if (pool_strategy != POOL_LOADBALANCE && (!lagging || opt_fail_only))
+	if (pool_strategy != POOL_LOADBALANCE && (!lagging || opt_fail_only)) {
 		pool = cp;
-	else
+		goto out;
+	} else
 		pool = NULL;
 
+	for (i = 0; i < total_pools; i++) {
+		struct pool *tp = pools[i];
+
+		if (tp->quota_used < tp->quota_gcd) {
+			avail = true;
+			break;
+		}
+	}
+
+	/* There are no pools with quota, so reset them. */
+	if (!avail) {
+		for (i = 0; i < total_pools; i++)
+			pools[i]->quota_used = 0;
+		if (++rotating_pool >= total_pools)
+			rotating_pool = 0;
+	}
+
 	/* Try to find the first pool in the rotation that is usable */
 	tested = 0;
 	while (!pool && tested++ < total_pools) {
-		if (++rotating_pool >= total_pools)
-			rotating_pool = 0;
 		pool = pools[rotating_pool];
-		if (!pool_unworkable(pool))
-			break;
+		if (pool->quota_used++ < pool->quota_gcd) {
+			if (!pool_unworkable(pool))
+				break;
+			/* Failover-only flag for load-balance means distribute
+			 * unused quota to priority pool 0. */
+			if (opt_fail_only)
+				priority_pool(0)->quota_used--;
+		}
 		pool = NULL;
+		if (++rotating_pool >= total_pools)
+			rotating_pool = 0;
+	}
+
+	/* If there are no alive pools with quota, choose according to
+	 * priority. */
+	if (!pool) {
+		for (i = 0; i < total_pools; i++) {
+			struct pool *tp = priority_pool(i);
+
+			if (!pool_unusable(tp)) {
+				pool = tp;
+				break;
+			}
+		}
 	}
+
 	/* If still nothing is usable, use the current pool */
 	if (!pool)
 		pool = cp;
 
-have_pool:
+out:
 	if (cp != pool)
 	{
 		if (!pool_active(pool, false))
@@ -3981,6 +4120,7 @@ have_pool:
 		}
 		pool_tclear(pool, &pool->idle);
 	}
+	applog(LOG_DEBUG, "Selecting pool %d for work", pool->pool_no);
 	return pool;
 }
 
@@ -5350,7 +5490,7 @@ void switch_pools(struct pool *selected)
 	}
 
 	switch (pool_strategy) {
-		/* Both of these set to the master pool */
+		/* All of these set to the master pool */
 		case POOL_BALANCE:
 		case POOL_FAILOVER:
 		case POOL_LOADBALANCE:
@@ -5959,14 +6099,23 @@ void write_config(FILE *fcfg)
 	/* Write pool values */
 	fputs("{\n\"pools\" : [", fcfg);
 	for(i = 0; i < total_pools; i++) {
-		fprintf(fcfg, "%s\n\t{\n\t\t\"url\" : \"%s\",", i > 0 ? "," : "", json_escape(pools[i]->rpc_url));
-		if (pools[i]->rpc_proxy)
-			fprintf(fcfg, "\n\t\t\"pool-proxy\" : \"%s\",", json_escape(pools[i]->rpc_proxy));
-		fprintf(fcfg, "\n\t\t\"user\" : \"%s\",", json_escape(pools[i]->rpc_user));
-		fprintf(fcfg, "\n\t\t\"pass\" : \"%s\",", json_escape(pools[i]->rpc_pass));
-		fprintf(fcfg, "\n\t\t\"pool-priority\" : \"%d\"", pools[i]->prio);
-		if (pools[i]->force_rollntime)
-			fprintf(fcfg, ",\n\t\t\"force-rollntime\" : %d", pools[i]->force_rollntime);
+		struct pool *pool = pools[i];
+
+		if (pool->quota != 1) {
+			fprintf(fcfg, "%s\n\t{\n\t\t\"quota\" : \"%d;%s\",", i > 0 ? "," : "",
+				pool->quota,
+				json_escape(pool->rpc_url));
+		} else {
+			fprintf(fcfg, "%s\n\t{\n\t\t\"url\" : \"%s\",", i > 0 ? "," : "",
+				json_escape(pool->rpc_url));
+		}
+		if (pool->rpc_proxy)
+			fprintf(fcfg, "\n\t\t\"pool-proxy\" : \"%s\",", json_escape(pool->rpc_proxy));
+		fprintf(fcfg, "\n\t\t\"user\" : \"%s\",", json_escape(pool->rpc_user));
+		fprintf(fcfg, "\n\t\t\"pass\" : \"%s\",", json_escape(pool->rpc_pass));
+		fprintf(fcfg, "\n\t\t\"pool-priority\" : \"%d\"", pool->prio);
+		if (pool->force_rollntime)
+			fprintf(fcfg, ",\n\t\t\"force-rollntime\" : %d", pool->force_rollntime);
 		fprintf(fcfg, "\n\t}");
 	}
 	fputs("\n]\n", fcfg);
@@ -6165,6 +6314,8 @@ void write_config(FILE *fcfg)
 		fprintf(fcfg, ",\n\"api-mcast-addr\" : \"%s\"", json_escape(opt_api_mcast_addr));
 	if (strcmp(opt_api_mcast_code, API_MCAST_CODE) != 0)
 		fprintf(fcfg, ",\n\"api-mcast-code\" : \"%s\"", json_escape(opt_api_mcast_code));
+	if (*opt_api_mcast_des)
+		fprintf(fcfg, ",\n\"api-mcast-des\" : \"%s\"", json_escape(opt_api_mcast_des));
 	if (strcmp(opt_api_description, PACKAGE_STRING) != 0)
 		fprintf(fcfg, ",\n\"api-description\" : \"%s\"", json_escape(opt_api_description));
 	if (opt_api_groups)
@@ -6200,6 +6351,7 @@ void zero_stats(void)
 
 	cgtime(&total_tv_start);
 	miner_started = total_tv_start;
+	total_rolling = 0;
 	total_mhashes_done = 0;
 	total_getworks = 0;
 	total_accepted = 0;
@@ -6355,7 +6507,8 @@ updated:
 					default:
 						wlogprint("Alive");
 				}
-			wlogprint(" Pool %d: %s  User:%s\n",
+			wlogprint(" Quota %d Pool %d: %s  User:%s\n",
+				pool->quota,
 				pool->pool_no,
 				pool->rpc_url, pool->rpc_user);
 			wattroff(logwin, A_BOLD | A_DIM);
@@ -6369,7 +6522,7 @@ retry:
 	if (pool_strategy == POOL_ROTATE)
 		wlogprint("Set to rotate every %d minutes\n", opt_rotate_period);
 	wlogprint("[F]ailover only %s\n", opt_fail_only ? "enabled" : "disabled");
-	wlogprint("[A]dd pool [R]emove pool [D]isable pool [E]nable pool [P]rioritize pool\n");
+	wlogprint("Pool [A]dd [R]emove [D]isable [E]nable [P]rioritize [Q]uota change\n");
 	wlogprint("[C]hange management strategy [S]witch pool [I]nformation\n");
 	wlogprint("Or press any other key to continue\n");
 	logwin_update();
@@ -6463,6 +6616,21 @@ retry:
 		pool = pools[selected];
 		display_pool_summary(pool);
 		goto retry;
+	} else if (!strncasecmp(&input, "q", 1)) {
+		selected = curses_int("Select pool number");
+		if (selected < 0 || selected >= total_pools) {
+			wlogprint("Invalid selection\n");
+			goto retry;
+		}
+		pool = pools[selected];
+		selected = curses_int("Set quota");
+		if (selected < 0) {
+			wlogprint("Invalid negative quota\n");
+			goto retry;
+		}
+		pool->quota = selected;
+		adjust_quota_gcd();
+		goto updated;
 	} else if (!strncasecmp(&input, "f", 1)) {
 		opt_fail_only ^= true;
 		goto updated;
@@ -7056,7 +7224,6 @@ static void hashmeter(int thr_id, struct timeval *diff,
 	double secs;
 	double local_secs;
 	static double local_mhashes_done = 0;
-	static double rolling = 0;
 	double local_mhashes = (double)hashes_done / 1000000.0;
 	bool showlog = false;
 	char cHr[h2bs_fmt_size[H2B_NOUNIT]], aHr[h2bs_fmt_size[H2B_NOUNIT]], uHr[h2bs_fmt_size[H2B_SPACED]];
@@ -7131,8 +7298,8 @@ static void hashmeter(int thr_id, struct timeval *diff,
 	cgtime(&total_tv_end);
 
 	local_secs = (double)total_diff.tv_sec + ((double)total_diff.tv_usec / 1000000.0);
-	decay_time(&rolling, local_mhashes_done / local_secs, local_secs);
-	global_hashrate = roundl(rolling) * 1000000;
+	decay_time(&total_rolling, local_mhashes_done / local_secs, local_secs);
+	global_hashrate = roundl(total_rolling) * 1000000;
 
 	timersub(&total_tv_end, &total_tv_start, &total_diff);
 	total_secs = (double)total_diff.tv_sec +
@@ -7145,7 +7312,7 @@ static void hashmeter(int thr_id, struct timeval *diff,
 		((size_t[]){h2bs_fmt_size[H2B_NOUNIT], h2bs_fmt_size[H2B_NOUNIT], h2bs_fmt_size[H2B_SPACED]}),
 		true, "h/s", H2B_SHORT,
 		3,
-		1e6*rolling,
+		1e6*total_rolling,
 		1e6*total_mhashes_done / total_secs,
 		utility_to_hashrate(total_diff1 * (wtotal ? (total_diff_accepted / wtotal) : 1) * 60 / total_secs));
 
@@ -7744,6 +7911,8 @@ out:
 
 static void init_stratum_thread(struct pool *pool)
 {
+	have_longpoll = true;
+
 	if (unlikely(pthread_create(&pool->stratum_thread, NULL, stratum_thread, (void *)pool)))
 		quit(1, "Failed to create stratum thread");
 }
@@ -7753,7 +7922,7 @@ static void *longpoll_thread(void *userdata);
 static bool stratum_works(struct pool *pool)
 {
 	applog(LOG_INFO, "Testing pool %d stratum %s", pool->pool_no, pool->stratum_url);
-	if (!extract_sockaddr(pool, pool->stratum_url))
+	if (!extract_sockaddr(pool->stratum_url, &pool->sockaddr_url, &pool->stratum_port))
 		return false;
 
 	if (pool->stratum_active)
@@ -7936,7 +8105,7 @@ badwork:
 nohttp:
 		/* If we failed to parse a getwork, this could be a stratum
 		 * url without the prefix stratum+tcp:// so let's check it */
-		if (extract_sockaddr(pool, pool->rpc_url) && initiate_stratum(pool)) {
+		if (extract_sockaddr(pool->rpc_url, &pool->sockaddr_url, &pool->stratum_port) && initiate_stratum(pool)) {
 			pool->has_stratum = true;
 			goto retry_stratum;
 		}
@@ -8578,19 +8747,38 @@ struct work *clone_queued_work_bymidstate(struct cgpu_info *cgpu, char *midstate
 	return ret;
 }
 
+static void __work_completed(struct cgpu_info *cgpu, struct work *work)
+{
+	if (work->queued)
+		cgpu->queued_count--;
+	HASH_DEL(cgpu->queued_work, work);
+}
 /* This function should be used by queued device drivers when they're sure
  * the work struct is no longer in use. */
 void work_completed(struct cgpu_info *cgpu, struct work *work)
 {
 	wr_lock(&cgpu->qlock);
-	if (work->queued)
-		cgpu->queued_count--;
-	HASH_DEL(cgpu->queued_work, work);
+	__work_completed(cgpu, work);
 	wr_unlock(&cgpu->qlock);
 
 	free_work(work);
 }
 
+/* Combines find_queued_work_bymidstate and work_completed in one function
+ * withOUT destroying the work so the driver must free it. */
+struct work *take_queued_work_bymidstate(struct cgpu_info *cgpu, char *midstate, size_t midstatelen, char *data, int offset, size_t datalen)
+{
+	struct work *work;
+
+	wr_lock(&cgpu->qlock);
+	work = __find_work_bymidstate(cgpu->queued_work, midstate, midstatelen, data, offset, datalen);
+	if (work)
+		__work_completed(cgpu, work);
+	wr_unlock(&cgpu->qlock);
+
+	return work;
+}
+
 static void flush_queue(struct cgpu_info *cgpu)
 {
 	struct work *work, *tmp;

+ 17 - 3
miner.h

@@ -831,7 +831,7 @@ static inline void cglock_init(cglock_t *lock)
 	rwlock_init(&lock->rwlock);
 }
 
-/* Read lock variant of cglock */
+/* Read lock variant of cglock. Cannot be promoted. */
 static inline void cg_rlock(cglock_t *lock)
 {
 	mutex_lock(&lock->mutex);
@@ -839,7 +839,8 @@ static inline void cg_rlock(cglock_t *lock)
 	mutex_unlock_noyield(&lock->mutex);
 }
 
-/* Intermediate variant of cglock */
+/* Intermediate variant of cglock - behaves as a read lock but can be promoted
+ * to a write lock or demoted to read lock. */
 static inline void cg_ilock(cglock_t *lock)
 {
 	mutex_lock(&lock->mutex);
@@ -866,6 +867,12 @@ static inline void cg_dwlock(cglock_t *lock)
 	mutex_unlock_noyield(&lock->mutex);
 }
 
+/* Demote a write variant to an intermediate variant */
+static inline void cg_dwilock(cglock_t *lock)
+{
+	wr_unlock(&lock->rwlock);
+}
+
 /* Downgrade intermediate variant to a read lock */
 static inline void cg_dlock(cglock_t *lock)
 {
@@ -880,7 +887,7 @@ static inline void cg_runlock(cglock_t *lock)
 
 static inline void cg_wunlock(cglock_t *lock)
 {
-	wr_unlock(&lock->rwlock);
+	wr_unlock_noyield(&lock->rwlock);
 	mutex_unlock(&lock->mutex);
 }
 
@@ -909,6 +916,7 @@ extern char *opt_api_allow;
 extern bool opt_api_mcast;
 extern char *opt_api_mcast_addr;
 extern char *opt_api_mcast_code;
+extern char *opt_api_mcast_des;
 extern int opt_api_mcast_port;
 extern char *opt_api_groups;
 extern char *opt_api_description;
@@ -1011,6 +1019,7 @@ extern int enabled_pools;
 extern bool get_intrange(const char *arg, int *val1, int *val2);
 extern bool detect_stratum(struct pool *pool, char *url);
 extern void print_summary(void);
+extern void adjust_quota_gcd(void);
 extern struct pool *add_pool(void);
 extern bool add_pool_details(struct pool *pool, bool live, char *url, char *user, char *pass);
 
@@ -1070,6 +1079,7 @@ extern enum sha256_algos opt_algo;
 extern struct strategies strategies[];
 extern enum pool_strategy pool_strategy;
 extern int opt_rotate_period;
+extern double total_rolling;
 extern double total_mhashes_done;
 extern unsigned int new_blocks;
 extern unsigned int found_blocks;
@@ -1181,6 +1191,9 @@ struct pool {
 	int solved;
 	int diff1;
 	char diff[8];
+	int quota;
+	int quota_gcd;
+	int quota_used;
 
 	double diff_accepted;
 	double diff_rejected;
@@ -1365,6 +1378,7 @@ extern struct work *__find_work_bymidstate(struct work *que, char *midstate, siz
 extern struct work *find_queued_work_bymidstate(struct cgpu_info *cgpu, char *midstate, size_t midstatelen, char *data, int offset, size_t datalen);
 extern struct work *clone_queued_work_bymidstate(struct cgpu_info *cgpu, char *midstate, size_t midstatelen, char *data, int offset, size_t datalen);
 extern void work_completed(struct cgpu_info *cgpu, struct work *work);
+extern struct work *take_queued_work_bymidstate(struct cgpu_info *cgpu, char *midstate, size_t midstatelen, char *data, int offset, size_t datalen);
 extern bool abandon_work(struct work *, struct timeval *work_runtime, uint64_t hashes);
 extern void hash_queued_work(struct thr_info *mythr);
 extern void get_statline3(char *buf, size_t bufsz, struct cgpu_info *, bool for_curses, bool opt_show_procs);

+ 197 - 58
miner.php

@@ -2,18 +2,21 @@
 session_start();
 date_default_timezone_set(@date_default_timezone_get());
 #
-global $title, $miner, $port, $readonly, $notify, $rigs;
-global $mcast, $mcastaddr, $mcastport, $mcastcode;
-global $mcastlistport, $mcasttimeout;
+global $doctype, $title, $miner, $port, $readonly, $notify, $rigs;
+global $mcast, $mcastexpect, $mcastaddr, $mcastport, $mcastcode;
+global $mcastlistport, $mcasttimeout, $mcastretries, $allowgen;
 global $rigipsecurity, $rigtotals, $forcerigtotals;
 global $socksndtimeoutsec, $sockrcvtimeoutsec;
 global $checklastshare, $poolinputs, $hidefields;
 global $ignorerefresh, $changerefresh, $autorefresh;
 global $allowcustompages, $customsummarypages;
 global $miner_font_family, $miner_font_size;
+global $bad_font_family, $bad_font_size;
 global $colouroverride, $placebuttons, $userlist;
 global $per_proc;
 #
+$doctype = "<!DOCTYPE html>\n";
+#
 # See README.RPC for more details of these variables and how
 # to configure miner.php
 #
@@ -53,6 +56,9 @@ $rigs = array('127.0.0.1:4028');
 # Set $mcast to true to look for your rigs and ignore $rigs
 $mcast = false;
 #
+# Set $mcastexpect to at least how many rigs you expect it to find
+$mcastexpect = 0;
+#
 # API Multicast address all cgminers are listening on
 $mcastaddr = '224.0.0.75';
 #
@@ -69,6 +75,13 @@ $mcastlistport = 4027;
 # to wait for replies to the Multicast message
 $mcasttimeout = 1.5;
 #
+# Set $mcastretries to the number of times to retry the multicast
+$mcastretries = 0;
+#
+# Set $allowgen to true to allow customsummarypages to use 'gen' 
+# false means ignore any 'gen' options
+$allowgen = false;
+#
 # Set $rigipsecurity to false to show the IP/Port of the rig
 # in the socket error messages and also show the full socket message
 $rigipsecurity = true;
@@ -141,7 +154,7 @@ $poolspage = array(
  'POOL+STATS' => array('STATS.ID=ID', 'POOL.URL=URL',
 			'POOL.Has Stratum=Stratum', 'POOL.Stratum Active=StrAct',
 			'STATS.Net Bytes Sent=NSent',
-			'STATS.Net Bytes Recv=NRecv'));
+			'STATS.Net Bytes Recv=NRecv', 'GEN.AvShr=AvShr'));
 #
 $poolssum = array(
  'SUMMARY' => array('MHS av', 'Found Blocks', 'Accepted',
@@ -156,7 +169,9 @@ $poolsext = array(
 	'group' => array('POOL.URL', 'POOL.Has Stratum', 'POOL.Stratum Active'),
 	'calc' => array(
 			'STATS.Net Bytes Sent' => 'sum',
-			'STATS.Net Bytes Recv' => 'sum'),
+			'STATS.Net Bytes Recv' => 'sum',
+			'POOL.Accepted' => 'sum'),
+	'gen' => array('AvShr' => 'round(POOL.Difficulty Accepted/max(POOL.Accepted,1)*100)/100'),
 	'having' => array(array('STATS.Bytes Recv', '>', 0)))
 );
 
@@ -176,9 +191,12 @@ $warnfont = '<font color=red><b>';
 $warnoff = '</b></font>';
 $dfmt = 'H:i:s j-M-Y \U\T\CP';
 #
-$miner_font_family = 'verdana,arial,sans';
+$miner_font_family = 'Verdana, Arial, sans-serif, sans';
 $miner_font_size = '13pt';
 #
+$bad_font_family = '"Times New Roman", Times, serif';
+$bad_font_size = '18pt';
+#
 # Edit this or redefine it in myminer.php to change the colour scheme
 # See $colourtable below for the list of names
 $colouroverride = array();
@@ -198,7 +216,7 @@ if (file_exists('myminer.php'))
 # This is the system default that must always contain all necessary
 # colours so it must be a constant
 # You can override these values with $colouroverride
-# The only one missing is in $warnfont
+# The only one missing is $warnfont
 # - which you can override directly anyway
 global $colourtable;
 $colourtable = array(
@@ -210,6 +228,8 @@ $colourtable = array(
 	'td.h background'	=> '#c4ffff',
 	'td.err color'		=> 'black',
 	'td.err background'	=> '#ff3050',
+	'td.bad color'		=> 'black',
+	'td.bad background'	=> '#ff3050',
 	'td.warn color'		=> 'black',
 	'td.warn background'	=> '#ffb050',
 	'td.sta color'		=> 'green',
@@ -269,9 +289,10 @@ function getdom($domname)
  return getcss($domname, true);
 }
 #
-function htmlhead($checkapi, $rig, $pg = null, $noscript = false)
+function htmlhead($mcerr, $checkapi, $rig, $pg = null, $noscript = false)
 {
- global $title, $miner_font_family, $miner_font_size;
+ global $doctype, $title, $miner_font_family, $miner_font_size;
+ global $bad_font_family, $bad_font_size;
  global $error, $readonly, $poolinputs, $here;
  global $ignorerefresh, $autorefresh;
 
@@ -300,14 +321,16 @@ function htmlhead($checkapi, $rig, $pg = null, $noscript = false)
 		$readonly = true;
  }
  $miner_font = "font-family:$miner_font_family; font-size:$miner_font_size;";
+ $bad_font = "font-family:$bad_font_family; font-size:$bad_font_size;";
 
- echo "<html><head>$refreshmeta
+ echo "$doctype<html><head>$refreshmeta
 <title>$title</title>
 <style type='text/css'>
 td { $miner_font ".getcss('td')."}
 td.two { $miner_font ".getcss('td.two')."}
 td.h { $miner_font ".getcss('td.h')."}
 td.err { $miner_font ".getcss('td.err')."}
+td.bad { $bad_font ".getcss('td.bad')."}
 td.warn { $miner_font ".getcss('td.warn')."}
 td.sta { $miner_font ".getcss('td.sta')."}
 td.tot { $miner_font ".getcss('td.tot')."}
@@ -339,16 +362,24 @@ echo "</script>\n";
 <tr><td align=center valign=top>
 <table border=0 cellpadding=4 cellspacing=0 summary='Mine'>
 <?php
+ echo $mcerr;
+}
+#
+function minhead($mcerr = '')
+{
+ global $readonly;
+ $readonly = true;
+ htmlhead($mcerr, false, null, null, true);
 }
 #
 global $haderror, $error;
 $haderror = false;
 $error = null;
 #
-function getrigs()
+function mcastrigs()
 {
- global $rigs, $mcastaddr, $mcastport, $mcastcode;
- global $mcastlistport, $mcasttimeout, $error;
+ global $rigs, $mcastexpect, $mcastaddr, $mcastport, $mcastcode;
+ global $mcastlistport, $mcasttimeout, $mcastretries, $error;
 
  $listname = "0.0.0.0";
 
@@ -386,48 +417,78 @@ function getrigs()
 	return;
  }
 
- $mcast_soc = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
- if ($mcast_soc === false || $mcast_soc == null)
+ $retries = $mcastretries;
+ $doretry = ($retries > 0);
+ do
  {
-	$msg = "ERR: mcast send socket create(UDP) failed";
-	if ($rigipsecurity === false)
+	$mcast_soc = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
+	if ($mcast_soc === false || $mcast_soc == null)
 	{
-		$error = socket_strerror(socket_last_error());
-		$error = "$msg '$error'\n";
-	}
-	else
-		$error = "$msg\n";
+		$msg = "ERR: mcast send socket create(UDP) failed";
+		if ($rigipsecurity === false)
+		{
+			$error = socket_strerror(socket_last_error());
+			$error = "$msg '$error'\n";
+		}
+		else
+			$error = "$msg\n";
 
-	socket_close($rep_soc);
-	return;
- }
+		socket_close($rep_soc);
+		return;
+	}
 
- $buf = "cgminer-$mcastcode-$mcastlistport";
- socket_sendto($mcast_soc, $buf, strlen($buf), 0, $mcastaddr, $mcastport);
- socket_close($mcast_soc);
+	$buf = "cgminer-$mcastcode-$mcastlistport";
+	socket_sendto($mcast_soc, $buf, strlen($buf), 0, $mcastaddr, $mcastport);
+	socket_close($mcast_soc);
 
- $stt = microtime(true);
- while (true)
- {
-	$got = @socket_recvfrom($rep_soc, $buf, 32, MSG_DONTWAIT, $ip, $p);
-	if ($got !== false && $got > 0)
+	$stt = microtime(true);
+	while (true)
 	{
-		$ans = explode('-', $buf);
-		if (count($ans) == 3 && $ans[0] == 'cgm' && $ans[1] == 'FTW')
+		$got = @socket_recvfrom($rep_soc, $buf, 32, MSG_DONTWAIT, $ip, $p);
+		if ($got !== false && $got > 0)
 		{
-			$rp = intval($ans[2]);
-			$rigs[] = "$ip:$rp";
+			$ans = explode('-', $buf, 4);
+			if (count($ans) >= 3 && $ans[0] == 'cgm' && $ans[1] == 'FTW')
+			{
+				$rp = intval($ans[2]);
+
+				if (count($ans) > 3)
+					$mdes = str_replace("\0", '', $ans[3]);
+				else
+					$mdes = '';
+
+				if (strlen($mdes) > 0)
+					$rig = "$ip:$rp:$mdes";
+				else
+					$rig = "$ip:$rp";
+
+				if (!in_array($rig, $rigs))
+					$rigs[] = $rig;
+			}
 		}
+		if ((microtime(true) - $stt) >= $mcasttimeout)
+			break;
+
+		usleep(100000);
 	}
-	if ((microtime(true) - $stt) >= $mcasttimeout)
-		break;
 
-	usleep(100000);
- }
+	if ($mcastexpect > 0 && count($rigs) >= $mcastexpect)
+		$doretry = false;
+
+ } while ($doretry && --$retries > 0);
 
  socket_close($rep_soc);
 }
 #
+function getrigs()
+{
+ global $rigs;
+
+ mcastrigs();
+
+ sort($rigs);
+}
+#
 function getsock($rig, $addr, $port)
 {
  global $rigipsecurity;
@@ -642,9 +703,14 @@ function newrow()
  echo '<tr>';
 }
 #
+function othrow($row)
+{
+ return "<tr>$row</tr>";
+}
+#
 function otherrow($row)
 {
- echo "<tr>$row</tr>";
+ echo othrow($row);
 }
 #
 function endrow()
@@ -1833,8 +1899,6 @@ function doOne($rig, $preprocess)
  global $haderror, $readonly, $notify, $rigs;
  global $placebuttons;
 
- htmlhead(true, $rig);
-
  if ($placebuttons == 'top' || $placebuttons == 'both')
 	pagebuttons($rig, null);
 
@@ -2347,8 +2411,55 @@ function processcompare($which, $ext, $section, $res)
  return $res;
 }
 #
-function processext($ext, $section, $res)
+function ss($a, $b)
 {
+ $la = strlen($a);
+ $lb = strlen($b);
+ if ($la != $lb)
+	return $lb - $la;
+ return strcmp($a, $b);
+}
+#
+function genfld($row, $calc)
+{
+ uksort($row, "ss");
+
+ foreach ($row as $name => $value)
+	if (strstr($calc, $name) !== FALSE)
+		$calc = str_replace($name, $value, $calc);
+
+ eval("\$val = $calc;");
+
+ return $val;
+}
+#
+function dogen($ext, $section, &$res, &$fields)
+{
+ $gen = $ext[$section]['gen'];
+
+ foreach ($gen as $fld => $calc)
+	$fields[] = "GEN.$fld";
+
+ foreach ($res as $rig => $result)
+	foreach ($result as $sec => $row)
+	{
+		$secname = preg_replace('/\d/', '', $sec);
+		if (secmatch($section, $secname))
+			foreach ($gen as $fld => $calc)
+			{
+				$name = "GEN.$fld";
+
+				$val = genfld($row, $calc);
+
+				$res[$rig][$sec][$name] = $val;
+			}
+	}
+}
+#
+function processext($ext, $section, $res, &$fields)
+{
+ global $allowgen;
+
  $res = processcompare('where', $ext, $section, $res);
 
  if (isset($ext[$section]['group']))
@@ -2416,6 +2527,10 @@ function processext($ext, $section, $res)
 	}
  }
 
+ // Generated fields (functions of other fields)
+ if ($allowgen === true && isset($ext[$section]['gen']))
+	dogen($ext, $section, $res, $fields);
+
  return processcompare('having', $ext, $section, $res);
 }
 #
@@ -2474,6 +2589,8 @@ function processcustompage($pagename, $sections, $sum, $ext, $namemap)
 				$results[$cmd][$num] = $process;
 		}
 	}
+	else
+		otherrow('<td class=bad>Bad "$rigs" array</td>');
  }
 
  $shownsomething = false;
@@ -2510,7 +2627,8 @@ function processcustompage($pagename, $sections, $sum, $ext, $namemap)
 
 		if (isset($results[$sectionmap[$section]]))
 		{
-			$rigresults = processext($ext, $section, $results[$sectionmap[$section]]);
+			$rigresults = processext($ext, $section, $results[$sectionmap[$section]], $fields);
+
 			$showfields = array();
 			$showhead = array();
 			foreach ($fields as $field)
@@ -2589,21 +2707,19 @@ function showcustompage($pagename)
  global $customsummarypages;
  global $placebuttons;
 
- htmlhead(false, null, $pagename);
-
  if ($placebuttons == 'top' || $placebuttons == 'both')
 	pagebuttons(null, $pagename);
 
  if (!isset($customsummarypages[$pagename]))
  {
-	otherrow("<td colspan=100>Unknown custom summary page '$pagename'</td>");
+	otherrow("<td colspan=100 class=bad>Unknown custom summary page '$pagename'</td>");
 	return;
  }
 
  $c = count($customsummarypages[$pagename]);
  if ($c < 2 || $c > 3)
  {
-	$rw = "<td colspan=100>Invalid custom summary page '$pagename' (";
+	$rw = "<td colspan=100 class=bad>Invalid custom summary page '$pagename' (";
 	$rw .= count($customsummarypages[$pagename]).')</td>';
 	otherrow($rw);
 	return;
@@ -2648,7 +2764,7 @@ function showcustompage($pagename)
 
  if (count($page) <= 1)
  {
-	otherrow("<td colspan=100>Invalid custom summary page '$pagename' no content </td>");
+	otherrow("<td colspan=100 class=bad>Invalid custom summary page '$pagename' no content </td>");
 	return;
  }
 
@@ -2662,7 +2778,7 @@ function onlylogin()
 {
  global $here;
 
- htmlhead(false, null, null, true);
+ htmlhead('', false, null, null, true);
 
 ?>
 <tr height=15%><td>&nbsp;</td></tr>
@@ -2762,6 +2878,7 @@ function checklogin()
 function display()
 {
  global $miner, $port;
+ global $mcast, $mcastexpect;
  global $readonly, $notify, $rigs;
  global $ignorerefresh, $autorefresh;
  global $allowcustompages, $customsummarypages;
@@ -2773,11 +2890,24 @@ function display()
  if ($pagesonly === 'login')
 	return;
 
+ $mcerr = '';
+
  if ($rigs == null or count($rigs) == 0)
  {
-	otherrow("<td>No rigs defined</td>");
+	if ($mcast === true)
+		$action = 'found';
+	else
+		$action = 'defined';
+
+	minhead();
+	otherrow("<td class=bad>No rigs $action</td>");
 	return;
  }
+ else
+ {
+	if ($mcast === true && count($rigs) < $mcastexpect)
+		$mcerr = othrow('<td class=bad>Found '.count($rigs)." rigs but expected at least $mcastexpect</td>");
+ }
 
  if ($ignorerefresh == false)
  {
@@ -2834,7 +2964,8 @@ function display()
 
 	if ($pg !== null && $pg !== '')
 	{
-		showcustompage($pg);
+		htmlhead($mcerr, false, null, $pg);
+		showcustompage($pg, $mcerr);
 		return;
 	}
  }
@@ -2853,10 +2984,14 @@ function display()
 		$miner = $parts[0];
 		$port = $parts[1];
 
+		htmlhead($mcerr, true, 0);
 		doOne(0, $preprocess);
 	}
 	else
-		otherrow('<td>Invalid "$rigs" array</td>');
+	{
+		minhead($mcerr);
+		otherrow('<td class=bad>Invalid "$rigs" array</td>');
+	}
 
 	return;
  }
@@ -2869,15 +3004,19 @@ function display()
 		$miner = $parts[0];
 		$port = $parts[1];
 
+		htmlhead($mcerr, true, 0);
 		doOne($rig, $preprocess);
 	}
 	else
-		otherrow('<td>Invalid "$rigs" array</td>');
+	{
+		minhead($mcerr);
+		otherrow('<td class=bad>Invalid "$rigs" array</td>');
+	}
 
 	return;
  }
 
- htmlhead(false, null);
+ htmlhead($mcerr, false, null);
 
  if ($placebuttons == 'top' || $placebuttons == 'both')
 	pagebuttons(null, null);
@@ -2903,7 +3042,7 @@ function display()
 	pagebuttons(null, null);
 }
 #
-if (isset($mcast) && $mcast === true)
+if ($mcast === true)
  getrigs();
 display();
 #

+ 15 - 10
util.c

@@ -434,7 +434,7 @@ void json_rpc_call_async(CURL *curl, const char *url,
 		curl_easy_setopt(curl, CURLOPT_PROXY, pool->rpc_proxy);
 	} else if (opt_socks_proxy) {
 		curl_easy_setopt(curl, CURLOPT_PROXY, opt_socks_proxy);
-		curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4);
+		curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
 	}
 	if (userpass) {
 		curl_easy_setopt(curl, CURLOPT_USERPWD, userpass);
@@ -1366,7 +1366,7 @@ double tdiff(struct timeval *end, struct timeval *start)
 	return end->tv_sec - start->tv_sec + (end->tv_usec - start->tv_usec) / 1000000.0;
 }
 
-bool extract_sockaddr(struct pool *pool, char *url)
+bool extract_sockaddr(char *url, char **sockaddr_url, char **sockaddr_port)
 {
 	char *url_begin, *url_end, *ipv6_begin, *ipv6_end, *port_start = NULL;
 	char url_address[256], port[6];
@@ -1399,15 +1399,20 @@ bool extract_sockaddr(struct pool *pool, char *url)
 
 	sprintf(url_address, "%.*s", url_len, url_begin);
 
-	if (port_len)
+	if (port_len) {
+		char *slash;
+
 		snprintf(port, 6, "%.*s", port_len, port_start);
-	else
+		slash = strchr(port, '/');
+		if (slash)
+			*slash = '\0';
+	} else
 		strcpy(port, "80");
 
-	free(pool->stratum_port);
-	pool->stratum_port = strdup(port);
-	free(pool->sockaddr_url);
-	pool->sockaddr_url = strdup(url_address);
+	free(*sockaddr_port);
+	*sockaddr_port = strdup(port);
+	free(*sockaddr_url);
+	*sockaddr_url = strdup(url_address);
 
 	return true;
 }
@@ -1854,7 +1859,7 @@ static bool parse_reconnect(struct pool *pool, json_t *val)
 
 	snprintf(address, sizeof(address), "%s:%s", url, port);
 
-	if (!extract_sockaddr(pool, address))
+	if (!extract_sockaddr(address, &pool->sockaddr_url, &pool->stratum_port))
 		return false;
 
 	pool->stratum_url = pool->sockaddr_url;
@@ -2110,7 +2115,7 @@ static bool setup_stratum_curl(struct pool *pool)
 		curl_easy_setopt(curl, CURLOPT_PROXY, pool->rpc_proxy);
 	} else if (opt_socks_proxy) {
 		curl_easy_setopt(curl, CURLOPT_PROXY, opt_socks_proxy);
-		curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4);
+		curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
 	}
 	curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 1);
 	pool->sock = INVSOCK;

+ 1 - 1
util.h

@@ -153,7 +153,7 @@ bool _stratum_send(struct pool *pool, char *s, ssize_t len, bool force);
 bool sock_full(struct pool *pool);
 char *recv_line(struct pool *pool);
 bool parse_method(struct pool *pool, char *s);
-bool extract_sockaddr(struct pool *pool, char *url);
+bool extract_sockaddr(char *url, char **sockaddr_url, char **sockaddr_port);
 bool auth_stratum(struct pool *pool);
 bool initiate_stratum(struct pool *pool);
 bool restart_stratum(struct pool *pool);