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
 --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)
 --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")
 --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|-l <arg>      Interval in seconds between log output (default: 5)
 --log-file|-L <arg> Append log file for output messages
 --log-file|-L <arg> Append log file for output messages
 --log-microseconds  Include microseconds in log output
 --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)
 --shares <arg>      Quit after mining N shares (default: unlimited)
 --show-processors   Show per processor statistics in summary
 --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)
 --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)
 --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)
 --submit-threads    Minimum number of concurrent share submissions (default: 64)
 --syslog            Use system log for output messages (default: standard error)
 --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.
 skipping pools that are idle.
 
 
 LOAD BALANCE:
 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:
 BALANCE:
 This strategy monitors the amount of difficulty 1 shares solved for each pool
 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!"
     --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
 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
 --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)
            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)
            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
 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
 ~315 difficulty 1 shares
 'long' mode requires it to always be stable to ensure accuracy, however, over
 'long' mode requires it to always be stable to ensure accuracy, however, over
 time it continually corrects itself
 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
 When in 'short' or 'long' mode, it will report the hash time value each time it
 is re-calculated
 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
                               stating the results of changing pool priorities
                               See usage below
                               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 (*)
  disablepool|N (*)
                none           There is no reply section just the STATUS section
                none           There is no reply section just the STATUS section
                               stating the results of disabling pool N
                               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:
 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)
 API V2.0 (BFGMiner v3.3.0)
 
 
 Removed API commands:
 Removed API commands:
@@ -986,7 +1003,9 @@ or in your bfgminer.conf
 And in miner.php set $mcast = true;
 And in miner.php set $mcast = true;
 
 
 This will ignore the value of $rigs and overwrite it with the list of zero or
 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 ':'
 Each rig string is 2 or 3 values separated by colons ':'
 They are simply an IP address or host name, followed by the
 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:
 Default:
  $mcastaddr = '224.0.0.75';
  $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:
 Default:
  $rigipsecurity = true;
  $rigipsecurity = true;
 
 
@@ -1531,6 +1590,7 @@ The example given:
 With BFGMiner 2.10.1 and later, miner.php includes an extension to the custom
 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,
 pages that allows you to apply SQL style commands to the data: where, group,
 and having
 and having
+BFGMiner 3.4.0 also includes another option 'gen'
 
 
 As an example, miner.php includes a more complex custom page called 'Pools'
 As an example, miner.php includes a more complex custom page called 'Pools'
 this includes the extension:
 this includes the extension:
@@ -1542,6 +1602,7 @@ $poolsext = array(
                          'POOL.Stratum Active'),
                          'POOL.Stratum Active'),
         'calc' => array('STATS.Bytes Sent' => 'sum',
         'calc' => array('STATS.Bytes Sent' => 'sum',
                         'STATS.Bytes Recv' => 'sum'),
                         'STATS.Bytes Recv' => 'sum'),
+        'gen' => array('AvShr', 'POOL.Difficulty Accepted/max(POOL.Accepted,1)),
         'having' => array(array('STATS.Bytes Recv', '>', 0)))
         '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
  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
 'any' is effectively random: the field value in the 1st row of the grouped data
 An unrecognised 'function' uses 'any'
 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-2013 Andrew Smith
- * Copyright 2011-2012 Con Kolivas
+ * Copyright 2011-2013 Con Kolivas
  * Copyright 2012-2013 Luke Dashjr
  * Copyright 2012-2013 Luke Dashjr
  *
  *
  * This program is free software; you can redistribute it and/or modify it
  * This program is free software; you can redistribute it and/or modify it
@@ -59,7 +59,7 @@ static const char SEPARATOR = '|';
 #define SEPSTR "|"
 #define SEPSTR "|"
 static const char GPUSEP = ',';
 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 *DEAD = "Dead";
 static const char *SICK = "Sick";
 static const char *SICK = "Sick";
 static const char *NOSTART = "NoStart";
 static const char *NOSTART = "NoStart";
@@ -351,6 +351,9 @@ static const char *JSON_PARAMETER = "parameter";
 
 
 #define MSG_DEVSCAN 0x100
 #define MSG_DEVSCAN 0x100
 
 
+#define MSG_INVNEG 121
+#define MSG_SETQUOTA 122
+
 enum code_severity {
 enum code_severity {
 	SEVERITY_ERR,
 	SEVERITY_ERR,
 	SEVERITY_WARN,
 	SEVERITY_WARN,
@@ -519,6 +522,8 @@ struct CODES {
  { SEVERITY_SUCC,  MSG_SETCONFIG,PARAM_SET,	"Set config '%s' to %d" },
  { SEVERITY_SUCC,  MSG_SETCONFIG,PARAM_SET,	"Set config '%s' to %d" },
  { SEVERITY_ERR,   MSG_UNKCON,	PARAM_STR,	"Unknown config '%s'" },
  { 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_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_CONPAR,	PARAM_NONE,	"Missing config parameters 'name,N'" },
  { SEVERITY_ERR,   MSG_CONVAL,	PARAM_STR,	"Missing config value N for '%s,N'" },
  { SEVERITY_ERR,   MSG_CONVAL,	PARAM_STR,	"Missing config value N for '%s,N'" },
 #ifdef HAVE_AN_FPGA
 #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);
 	n = find_index_by_cgpu(cgpu);
 
 
+	double runtime = cgpu_runtime(cgpu);
 	bool enabled = false;
 	bool enabled = false;
 	double total_mhashes = 0, rolling = 0, utility = 0;
 	double total_mhashes = 0, rolling = 0, utility = 0;
 	enum alive status = cgpu->status;
 	enum alive status = cgpu->status;
 	float temp = -1;
 	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;
 	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;
 	int last_share_pool = -1;
 	time_t last_share_pool_time = -1, last_device_valid_work = -1;
 	time_t last_share_pool_time = -1, last_device_valid_work = -1;
 	double last_share_diff = -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;
 		utility += proc->utility;
 		accepted += proc->accepted;
 		accepted += proc->accepted;
 		rejected += proc->rejected;
 		rejected += proc->rejected;
+		stale += proc->stale;
 		hw_errors += proc->hw_errors;
 		hw_errors += proc->hw_errors;
 		diff1 += proc->diff1;
 		diff1 += proc->diff1;
 		diff_accepted += proc->diff_accepted;
 		diff_accepted += proc->diff_accepted;
 		diff_rejected += proc->diff_rejected;
 		diff_rejected += proc->diff_rejected;
+		diff_stale += proc->diff_stale;
 		bad_nonces += proc->bad_nonces;
 		bad_nonces += proc->bad_nonces;
 		if (status != proc->status)
 		if (status != proc->status)
 			status = LIFE_MIXED;
 			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);
 	root = api_add_string(root, "Status", status2str(status), false);
 	if (temp > 0)
 	if (temp > 0)
 		root = api_add_temp(root, "Temperature", &temp, false);
 		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);
 	root = api_add_mhs(root, "MHS av", &mhs, false);
 	char mhsname[27];
 	char mhsname[27];
 	sprintf(mhsname, "MHS %ds", opt_log_interval);
 	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, "Rejected", &rejected, false);
 	root = api_add_int(root, "Hardware Errors", &hw_errors, false);
 	root = api_add_int(root, "Hardware Errors", &hw_errors, false);
 	root = api_add_utility(root, "Utility", &utility, false);
 	root = api_add_utility(root, "Utility", &utility, false);
+	root = api_add_int(root, "Stale", &stale, false);
 	if (last_share_pool != -1)
 	if (last_share_pool != -1)
 	{
 	{
 		root = api_add_int(root, "Last Share Pool", &last_share_pool, false);
 		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_time(root, "Last Share Time", &last_share_pool_time, false);
 	}
 	}
 	root = api_add_mhtotal(root, "Total MH", &total_mhashes, 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_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 Accepted", &diff_accepted, false);
 	root = api_add_diff(root, "Difficulty Rejected", &diff_rejected, 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)
 	if (last_share_diff > 0)
 		root = api_add_diff(root, "Last Share Difficulty", &last_share_diff, false);
 		root = api_add_diff(root, "Last Share Difficulty", &last_share_diff, false);
 	if (last_device_valid_work != -1)
 	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_escape(root, "URL", pool->rpc_url, false);
 		root = api_add_string(root, "Status", status, false);
 		root = api_add_string(root, "Status", status, false);
 		root = api_add_int(root, "Priority", &(pool->prio), 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_string(root, "Long Poll", lp, false);
 		root = api_add_uint(root, "Getworks", &(pool->getwork_requested), false);
 		root = api_add_uint(root, "Getworks", &(pool->getwork_requested), false);
 		root = api_add_int(root, "Accepted", &(pool->accepted), 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);
 	root = api_add_string(root, "Algorithm", algo, false);
 #endif
 #endif
 	root = api_add_mhs(root, "MHS av", &(mhs), false);
 	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_uint(root, "Found Blocks", &(found_blocks), true);
 	root = api_add_int(root, "Getworks", &(total_getworks), true);
 	root = api_add_int(root, "Getworks", &(total_getworks), true);
 	root = api_add_int(root, "Accepted", &(total_accepted), 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, "Remote Failures", &(total_ro), true);
 	root = api_add_uint(root, "Network Blocks", &(new_blocks), 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_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_utility(root, "Work Utility", &(work_utility), false);
 	root = api_add_diff(root, "Difficulty Accepted", &(total_diff_accepted), true);
 	root = api_add_diff(root, "Difficulty Accepted", &(total_diff_accepted), true);
 	root = api_add_diff(root, "Difficulty Rejected", &(total_diff_rejected), 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)
 static void disablepool(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
 {
 {
 	struct pool *pool;
 	struct pool *pool;
@@ -3381,6 +3442,7 @@ struct CMDS {
 	{ "switchpool",		switchpool,	true },
 	{ "switchpool",		switchpool,	true },
 	{ "addpool",		addpool,	true },
 	{ "addpool",		addpool,	true },
 	{ "poolpriority",	poolpriority,	true },
 	{ "poolpriority",	poolpriority,	true },
+	{ "poolquota",		poolquota,	true },
 	{ "enablepool",		enablepool,	true },
 	{ "enablepool",		enablepool,	true },
 	{ "disablepool",	disablepool,	true },
 	{ "disablepool",	disablepool,	true },
 	{ "removepool",		removepool,	true },
 	{ "removepool",		removepool,	true },
@@ -3909,9 +3971,9 @@ static void mcast()
 				reply_sock = socket(AF_INET, SOCK_DGRAM, 0);
 				reply_sock = socket(AF_INET, SOCK_DGRAM, 0);
 
 
 				snprintf(replybuf, sizeof(replybuf),
 				snprintf(replybuf, sizeof(replybuf),
-							"cgm-%s-%d",
+							"cgm-%s-%d-%s",
 							opt_api_mcast_code,
 							opt_api_mcast_code,
-							opt_api_port);
+							opt_api_port, opt_api_mcast_des);
 
 
 				rep = sendto(reply_sock, replybuf, strlen(replybuf)+1,
 				rep = sendto(reply_sock, replybuf, strlen(replybuf)+1,
 						0, (struct sockaddr *)(&came_from),
 						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_SRCDIR([miner.c])
 AC_CONFIG_HEADERS([config.h])
 AC_CONFIG_HEADERS([config.h])
 
 
-AM_INIT_AUTOMAKE([foreign])
+AM_INIT_AUTOMAKE([foreign subdir-objects])
 m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
 m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
 AC_USE_SYSTEM_EXTENSIONS
 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))
 #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
 // In timing mode: Default starting value until an estimate can be obtained
 // 5 seconds allows for up to a ~840MH/s device
 // 5 seconds allows for up to a ~840MH/s device
 #define ICARUS_READ_COUNT_TIMING	(5 * TIME_FACTOR)
 #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_DEFAULT_STR = "default";
 static const char *MODE_SHORT_STR = "short";
 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_STR = "long";
+static const char *MODE_LONG_STREQ = "long=";
 static const char *MODE_VALUE_STR = "value";
 static const char *MODE_VALUE_STR = "value";
 static const char *MODE_UNKNOWN_STR = "unknown";
 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 (thr && thr->work_restart) {
 			if (epollfd != -1)
 			if (epollfd != -1)
 				close(epollfd);
 				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;
 			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 (rc >= read_count) {
 			if (epollfd != -1)
 			if (epollfd != -1)
 				close(epollfd);
 				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;
 			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 = 0;
+	info->read_count_limit = 0; // 0 = no limit
 
 
 	if (strcasecmp(buf, MODE_SHORT_STR) == 0) {
 	if (strcasecmp(buf, MODE_SHORT_STR) == 0) {
+		// short
 		info->read_count = ICARUS_READ_COUNT_TIMING;
 		info->read_count = ICARUS_READ_COUNT_TIMING;
 
 
 		info->timing_mode = MODE_SHORT;
 		info->timing_mode = MODE_SHORT;
 		info->do_icarus_timing = true;
 		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) {
 	} else if (strcasecmp(buf, MODE_LONG_STR) == 0) {
+		// long
 		info->read_count = ICARUS_READ_COUNT_TIMING;
 		info->read_count = ICARUS_READ_COUNT_TIMING;
 
 
 		info->timing_mode = MODE_LONG;
 		info->timing_mode = MODE_LONG;
 		info->do_icarus_timing = true;
 		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) {
 	} else if ((Hs = atof(buf)) != 0) {
+		// ns[=read_count]
 		info->Hs = Hs / NANOSEC;
 		info->Hs = Hs / NANOSEC;
 		info->fullnonce = info->Hs * (((double)0xffffffff) + 1);
 		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;
 	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,
 		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)
 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;
 	int count;
 	double Hs, W, fullnonce;
 	double Hs, W, fullnonce;
 	int read_count;
 	int read_count;
+	bool limited;
 	int64_t estimate_hashes;
 	int64_t estimate_hashes;
 	uint32_t values;
 	uint32_t values;
 	int64_t hash_count_range;
 	int64_t hash_count_range;
@@ -1062,12 +1095,10 @@ keepwaiting:
 		if (unlikely(estimate_hashes > 0xffffffff))
 		if (unlikely(estimate_hashes > 0xffffffff))
 			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;
 		hash_count = estimate_hashes;
 		goto out;
 		goto out;
@@ -1085,13 +1116,11 @@ keepwaiting:
 	hash_count++;
 	hash_count++;
 	hash_count *= info->fpga_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) {
 	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);
 		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
 	if (info->do_icarus_timing
 	&&  !was_hw_error
 	&&  !was_hw_error
 	&&  ((nonce & info->nonce_mask) > END_CONDITION)
 	&&  ((nonce & info->nonce_mask) > END_CONDITION)
@@ -1197,6 +1228,11 @@ keepwaiting:
 
 
 			fullnonce = W + Hs * (((double)0xffffffff) + 1);
 			fullnonce = W + Hs * (((double)0xffffffff) + 1);
 			read_count = (int)(fullnonce * TIME_FACTOR) - 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->Hs = Hs;
 			info->read_count = read_count;
 			info->read_count = read_count;
@@ -1212,10 +1248,11 @@ keepwaiting:
 			else if (info->timing_mode == MODE_SHORT)
 			else if (info->timing_mode == MODE_SHORT)
 				info->do_icarus_timing = false;
 				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,
 					icarus->proc_repr,
-					Hs, W, read_count, fullnonce);
+					Hs, W, read_count,
+					limited ? " (limited)" : "", fullnonce);
 		}
 		}
 		info->history_count++;
 		info->history_count++;
 		cgtime(&tv_history_finish);
 		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'
 	// locking access to displaying API debug 'stats'
 	// If locking becomes an issue for any of them, use copy_data=true also
 	// 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", &(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_double(root, "fullnonce", &(info->fullnonce), false);
 	root = api_add_int(root, "count", &(info->count), false);
 	root = api_add_int(root, "count", &(info->count), false);
 	root = api_add_hs(root, "Hs", &(info->Hs), 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
 	// seconds per Hash
 	double Hs;
 	double Hs;
 	int read_count;
 	int read_count;
+	// ds limit for (short=/long=) read_count
+	int read_count_limit;
 
 
 	enum timing_mode timing_mode;
 	enum timing_mode timing_mode;
 	bool do_icarus_timing;
 	bool do_icarus_timing;

+ 8 - 0
logging.h

@@ -57,6 +57,14 @@ extern void _applog(int prio, const char *str);
 	} \
 	} \
 } while (0)
 } 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 {  \
 #define applogr(rv, prio, ...)  do {  \
 	applog(prio, __VA_ARGS__);  \
 	applog(prio, __VA_ARGS__);  \
 	return rv;  \
 	return rv;  \

+ 229 - 41
miner.c

@@ -148,6 +148,7 @@ int opt_expiry_lp = 3600;
 int opt_bench_algo = -1;
 int opt_bench_algo = -1;
 unsigned long long global_hashrate;
 unsigned long long global_hashrate;
 static bool opt_unittest = false;
 static bool opt_unittest = false;
+unsigned long global_quota_gcd = 1;
 
 
 #ifdef HAVE_OPENCL
 #ifdef HAVE_OPENCL
 int opt_dynamic_interval = 7;
 int opt_dynamic_interval = 7;
@@ -210,6 +211,7 @@ bool opt_api_listen;
 bool opt_api_mcast;
 bool opt_api_mcast;
 char *opt_api_mcast_addr = API_MCAST_ADDR;
 char *opt_api_mcast_addr = API_MCAST_ADDR;
 char *opt_api_mcast_code = API_MCAST_CODE;
 char *opt_api_mcast_code = API_MCAST_CODE;
+char *opt_api_mcast_des = "";
 int opt_api_mcast_port = 4028;
 int opt_api_mcast_port = 4028;
 bool opt_api_network;
 bool opt_api_network;
 bool opt_delaynet;
 bool opt_delaynet;
@@ -261,6 +263,7 @@ pthread_cond_t gws_cond;
 
 
 bool shutting_down;
 bool shutting_down;
 
 
+double total_rolling;
 double total_mhashes_done;
 double total_mhashes_done;
 static struct timeval total_tv_start, total_tv_end;
 static struct timeval total_tv_start, total_tv_end;
 static struct timeval miner_started;
 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";
 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 */
 /* Return value is ignored if not called from add_pool_details */
 struct pool *add_pool(void)
 struct pool *add_pool(void)
 {
 {
@@ -627,6 +671,8 @@ struct pool *add_pool(void)
 	cgtime(&pool->cgminer_stats.start_tv);
 	cgtime(&pool->cgminer_stats.start_tv);
 
 
 	pool->rpc_proxy = NULL;
 	pool->rpc_proxy = NULL;
+	pool->quota = 1;
+	adjust_quota_gcd();
 
 
 	pool->sock = INVSOCK;
 	pool->sock = INVSOCK;
 	pool->lp_socket = CURL_SOCKET_BAD;
 	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 */
  * stratum+tcp or by detecting a stratum server response */
 bool detect_stratum(struct pool *pool, char *url)
 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;
 		return false;
 
 
 	if (!strncasecmp(url, "stratum+tcp://", 14)) {
 	if (!strncasecmp(url, "stratum+tcp://", 14)) {
@@ -1024,17 +1070,18 @@ bool detect_stratum(struct pool *pool, char *url)
 	return false;
 	return false;
 }
 }
 
 
-static char *set_url(char *arg)
+static struct pool *add_url(void)
 {
 {
-	struct pool *pool;
-
 	total_urls++;
 	total_urls++;
 	if (total_urls > total_pools)
 	if (total_urls > total_pools)
 		add_pool();
 		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))
 	if (detect_stratum(pool, arg))
-		return NULL;
+		return;
 
 
 	opt_set_charp(arg, &pool->rpc_url);
 	opt_set_charp(arg, &pool->rpc_url);
 	if (strncmp(arg, "http://", 7) &&
 	if (strncmp(arg, "http://", 7) &&
@@ -1048,6 +1095,41 @@ static char *set_url(char *arg)
 		strncat(httpinput, arg, 248);
 		strncat(httpinput, arg, 248);
 		pool->rpc_url = httpinput;
 		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;
 	return NULL;
 }
 }
@@ -1378,6 +1460,13 @@ static char *set_api_description(const char *arg)
 	return NULL;
 	return NULL;
 }
 }
 
 
+static char *set_api_mcast_des(const char *arg)
+{
+	opt_set_charp(arg, &opt_api_mcast_des);
+
+	return NULL;
+}
+
 #ifdef USE_ICARUS
 #ifdef USE_ICARUS
 static char *set_icarus_options(const char *arg)
 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_WITH_ARG("--api-mcast-code",
 		     opt_set_charp, opt_show_charp, &opt_api_mcast_code,
 		     opt_set_charp, opt_show_charp, &opt_api_mcast_code,
 		     "Code expected in the API Multicast message, don't use '-'"),
 		     "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",
 	OPT_WITH_ARG("--api-mcast-port",
 		     set_int_1_to_65535, opt_show_intval, &opt_api_mcast_port,
 		     set_int_1_to_65535, opt_show_intval, &opt_api_mcast_port,
 		     "API Multicast listen port"),
 		     "API Multicast listen port"),
@@ -1657,7 +1749,7 @@ static struct opt_table opt_config_table[] = {
 #endif
 #endif
 	OPT_WITHOUT_ARG("--load-balance",
 	OPT_WITHOUT_ARG("--load-balance",
 		     set_loadbalance, &pool_strategy,
 		     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",
 	OPT_WITH_ARG("--log|-l",
 		     set_int_0_to_9999, opt_show_intval, &opt_log_interval,
 		     set_int_0_to_9999, opt_show_intval, &opt_log_interval,
 		     "Interval in seconds between log output"),
 		     "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_WITHOUT_ARG("--quiet-work-updates|--quiet-work-update",
 			opt_set_bool, &opt_quiet_work_updates,
 			opt_set_bool, &opt_quiet_work_updates,
 			opt_hidden),
 			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_WITHOUT_ARG("--real-quiet",
 			opt_set_bool, &opt_realquiet,
 			opt_set_bool, &opt_realquiet,
 			"Disable all output"),
 			"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)"),
 			"Skip security checks sometimes to save bandwidth; only check 1/<arg>th of the time (default: never skip)"),
 	OPT_WITH_ARG("--socks-proxy",
 	OPT_WITH_ARG("--socks-proxy",
 		     opt_set_charp, NULL, &opt_socks_proxy,
 		     opt_set_charp, NULL, &opt_socks_proxy,
-		     "Set socks4 proxy (host:port)"),
+		     "Set socks proxy (host:port)"),
 #ifdef USE_LIBEVENT
 #ifdef USE_LIBEVENT
 	OPT_WITH_ARG("--stratum-port",
 	OPT_WITH_ARG("--stratum-port",
 	             opt_set_intval, opt_show_intval, &stratumsrv_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. */
 		/* We don't handle subtables. */
 		assert(!(opt->type & OPT_SUBTABLE));
 		assert(!(opt->type & OPT_SUBTABLE));
 
 
+		if (!opt->names)
+			continue;
+
 		/* Pull apart the option name(s). */
 		/* Pull apart the option name(s). */
 		name = strdup(opt->names);
 		name = strdup(opt->names);
 		for (p = strtok_r(name, "|", &sp); p; p = strtok_r(NULL, "|", &sp)) {
 		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);
 		best_share);
 	wclrtoeol(statuswin);
 	wclrtoeol(statuswin);
 	if ((pool_strategy == POOL_LOADBALANCE  || pool_strategy == POOL_BALANCE) && total_pools > 1) {
 	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");
 			have_longpoll ? "": "out");
 	} else if (pool->has_stratum) {
 	} else if (pool->has_stratum) {
 		cg_mvwprintw(statuswin, 2, 0, " Connected to %s diff %s with stratum as user %s",
 		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 bool pool_active(struct pool *, bool pinging);
 static void pool_died(struct pool *);
 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 inline struct pool *select_pool(bool lagging)
 {
 {
 	static int rotating_pool = 0;
 	static int rotating_pool = 0;
 	struct pool *pool, *cp;
 	struct pool *pool, *cp;
-	int tested;
+	bool avail = false;
+	int tested, i;
 
 
 	cp = current_pool();
 	cp = current_pool();
 
 
 retry:
 retry:
-	if (pool_strategy == POOL_BALANCE)
-	{
+	if (pool_strategy == POOL_BALANCE) {
 		pool = select_balanced(cp);
 		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;
 		pool = cp;
-	else
+		goto out;
+	} else
 		pool = NULL;
 		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 */
 	/* Try to find the first pool in the rotation that is usable */
 	tested = 0;
 	tested = 0;
 	while (!pool && tested++ < total_pools) {
 	while (!pool && tested++ < total_pools) {
-		if (++rotating_pool >= total_pools)
-			rotating_pool = 0;
 		pool = pools[rotating_pool];
 		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;
 		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 still nothing is usable, use the current pool */
 	if (!pool)
 	if (!pool)
 		pool = cp;
 		pool = cp;
 
 
-have_pool:
+out:
 	if (cp != pool)
 	if (cp != pool)
 	{
 	{
 		if (!pool_active(pool, false))
 		if (!pool_active(pool, false))
@@ -3981,6 +4120,7 @@ have_pool:
 		}
 		}
 		pool_tclear(pool, &pool->idle);
 		pool_tclear(pool, &pool->idle);
 	}
 	}
+	applog(LOG_DEBUG, "Selecting pool %d for work", pool->pool_no);
 	return pool;
 	return pool;
 }
 }
 
 
@@ -5350,7 +5490,7 @@ void switch_pools(struct pool *selected)
 	}
 	}
 
 
 	switch (pool_strategy) {
 	switch (pool_strategy) {
-		/* Both of these set to the master pool */
+		/* All of these set to the master pool */
 		case POOL_BALANCE:
 		case POOL_BALANCE:
 		case POOL_FAILOVER:
 		case POOL_FAILOVER:
 		case POOL_LOADBALANCE:
 		case POOL_LOADBALANCE:
@@ -5959,14 +6099,23 @@ void write_config(FILE *fcfg)
 	/* Write pool values */
 	/* Write pool values */
 	fputs("{\n\"pools\" : [", fcfg);
 	fputs("{\n\"pools\" : [", fcfg);
 	for(i = 0; i < total_pools; i++) {
 	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}");
 		fprintf(fcfg, "\n\t}");
 	}
 	}
 	fputs("\n]\n", fcfg);
 	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));
 		fprintf(fcfg, ",\n\"api-mcast-addr\" : \"%s\"", json_escape(opt_api_mcast_addr));
 	if (strcmp(opt_api_mcast_code, API_MCAST_CODE) != 0)
 	if (strcmp(opt_api_mcast_code, API_MCAST_CODE) != 0)
 		fprintf(fcfg, ",\n\"api-mcast-code\" : \"%s\"", json_escape(opt_api_mcast_code));
 		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)
 	if (strcmp(opt_api_description, PACKAGE_STRING) != 0)
 		fprintf(fcfg, ",\n\"api-description\" : \"%s\"", json_escape(opt_api_description));
 		fprintf(fcfg, ",\n\"api-description\" : \"%s\"", json_escape(opt_api_description));
 	if (opt_api_groups)
 	if (opt_api_groups)
@@ -6200,6 +6351,7 @@ void zero_stats(void)
 
 
 	cgtime(&total_tv_start);
 	cgtime(&total_tv_start);
 	miner_started = total_tv_start;
 	miner_started = total_tv_start;
+	total_rolling = 0;
 	total_mhashes_done = 0;
 	total_mhashes_done = 0;
 	total_getworks = 0;
 	total_getworks = 0;
 	total_accepted = 0;
 	total_accepted = 0;
@@ -6355,7 +6507,8 @@ updated:
 					default:
 					default:
 						wlogprint("Alive");
 						wlogprint("Alive");
 				}
 				}
-			wlogprint(" Pool %d: %s  User:%s\n",
+			wlogprint(" Quota %d Pool %d: %s  User:%s\n",
+				pool->quota,
 				pool->pool_no,
 				pool->pool_no,
 				pool->rpc_url, pool->rpc_user);
 				pool->rpc_url, pool->rpc_user);
 			wattroff(logwin, A_BOLD | A_DIM);
 			wattroff(logwin, A_BOLD | A_DIM);
@@ -6369,7 +6522,7 @@ retry:
 	if (pool_strategy == POOL_ROTATE)
 	if (pool_strategy == POOL_ROTATE)
 		wlogprint("Set to rotate every %d minutes\n", opt_rotate_period);
 		wlogprint("Set to rotate every %d minutes\n", opt_rotate_period);
 	wlogprint("[F]ailover only %s\n", opt_fail_only ? "enabled" : "disabled");
 	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("[C]hange management strategy [S]witch pool [I]nformation\n");
 	wlogprint("Or press any other key to continue\n");
 	wlogprint("Or press any other key to continue\n");
 	logwin_update();
 	logwin_update();
@@ -6463,6 +6616,21 @@ retry:
 		pool = pools[selected];
 		pool = pools[selected];
 		display_pool_summary(pool);
 		display_pool_summary(pool);
 		goto retry;
 		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)) {
 	} else if (!strncasecmp(&input, "f", 1)) {
 		opt_fail_only ^= true;
 		opt_fail_only ^= true;
 		goto updated;
 		goto updated;
@@ -7056,7 +7224,6 @@ static void hashmeter(int thr_id, struct timeval *diff,
 	double secs;
 	double secs;
 	double local_secs;
 	double local_secs;
 	static double local_mhashes_done = 0;
 	static double local_mhashes_done = 0;
-	static double rolling = 0;
 	double local_mhashes = (double)hashes_done / 1000000.0;
 	double local_mhashes = (double)hashes_done / 1000000.0;
 	bool showlog = false;
 	bool showlog = false;
 	char cHr[h2bs_fmt_size[H2B_NOUNIT]], aHr[h2bs_fmt_size[H2B_NOUNIT]], uHr[h2bs_fmt_size[H2B_SPACED]];
 	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);
 	cgtime(&total_tv_end);
 
 
 	local_secs = (double)total_diff.tv_sec + ((double)total_diff.tv_usec / 1000000.0);
 	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);
 	timersub(&total_tv_end, &total_tv_start, &total_diff);
 	total_secs = (double)total_diff.tv_sec +
 	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]}),
 		((size_t[]){h2bs_fmt_size[H2B_NOUNIT], h2bs_fmt_size[H2B_NOUNIT], h2bs_fmt_size[H2B_SPACED]}),
 		true, "h/s", H2B_SHORT,
 		true, "h/s", H2B_SHORT,
 		3,
 		3,
-		1e6*rolling,
+		1e6*total_rolling,
 		1e6*total_mhashes_done / total_secs,
 		1e6*total_mhashes_done / total_secs,
 		utility_to_hashrate(total_diff1 * (wtotal ? (total_diff_accepted / wtotal) : 1) * 60 / 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)
 static void init_stratum_thread(struct pool *pool)
 {
 {
+	have_longpoll = true;
+
 	if (unlikely(pthread_create(&pool->stratum_thread, NULL, stratum_thread, (void *)pool)))
 	if (unlikely(pthread_create(&pool->stratum_thread, NULL, stratum_thread, (void *)pool)))
 		quit(1, "Failed to create stratum thread");
 		quit(1, "Failed to create stratum thread");
 }
 }
@@ -7753,7 +7922,7 @@ static void *longpoll_thread(void *userdata);
 static bool stratum_works(struct pool *pool)
 static bool stratum_works(struct pool *pool)
 {
 {
 	applog(LOG_INFO, "Testing pool %d stratum %s", pool->pool_no, pool->stratum_url);
 	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;
 		return false;
 
 
 	if (pool->stratum_active)
 	if (pool->stratum_active)
@@ -7936,7 +8105,7 @@ badwork:
 nohttp:
 nohttp:
 		/* If we failed to parse a getwork, this could be a stratum
 		/* If we failed to parse a getwork, this could be a stratum
 		 * url without the prefix stratum+tcp:// so let's check it */
 		 * 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;
 			pool->has_stratum = true;
 			goto retry_stratum;
 			goto retry_stratum;
 		}
 		}
@@ -8578,19 +8747,38 @@ struct work *clone_queued_work_bymidstate(struct cgpu_info *cgpu, char *midstate
 	return ret;
 	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
 /* This function should be used by queued device drivers when they're sure
  * the work struct is no longer in use. */
  * the work struct is no longer in use. */
 void work_completed(struct cgpu_info *cgpu, struct work *work)
 void work_completed(struct cgpu_info *cgpu, struct work *work)
 {
 {
 	wr_lock(&cgpu->qlock);
 	wr_lock(&cgpu->qlock);
-	if (work->queued)
-		cgpu->queued_count--;
-	HASH_DEL(cgpu->queued_work, work);
+	__work_completed(cgpu, work);
 	wr_unlock(&cgpu->qlock);
 	wr_unlock(&cgpu->qlock);
 
 
 	free_work(work);
 	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)
 static void flush_queue(struct cgpu_info *cgpu)
 {
 {
 	struct work *work, *tmp;
 	struct work *work, *tmp;

+ 17 - 3
miner.h

@@ -831,7 +831,7 @@ static inline void cglock_init(cglock_t *lock)
 	rwlock_init(&lock->rwlock);
 	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)
 static inline void cg_rlock(cglock_t *lock)
 {
 {
 	mutex_lock(&lock->mutex);
 	mutex_lock(&lock->mutex);
@@ -839,7 +839,8 @@ static inline void cg_rlock(cglock_t *lock)
 	mutex_unlock_noyield(&lock->mutex);
 	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)
 static inline void cg_ilock(cglock_t *lock)
 {
 {
 	mutex_lock(&lock->mutex);
 	mutex_lock(&lock->mutex);
@@ -866,6 +867,12 @@ static inline void cg_dwlock(cglock_t *lock)
 	mutex_unlock_noyield(&lock->mutex);
 	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 */
 /* Downgrade intermediate variant to a read lock */
 static inline void cg_dlock(cglock_t *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)
 static inline void cg_wunlock(cglock_t *lock)
 {
 {
-	wr_unlock(&lock->rwlock);
+	wr_unlock_noyield(&lock->rwlock);
 	mutex_unlock(&lock->mutex);
 	mutex_unlock(&lock->mutex);
 }
 }
 
 
@@ -909,6 +916,7 @@ extern char *opt_api_allow;
 extern bool opt_api_mcast;
 extern bool opt_api_mcast;
 extern char *opt_api_mcast_addr;
 extern char *opt_api_mcast_addr;
 extern char *opt_api_mcast_code;
 extern char *opt_api_mcast_code;
+extern char *opt_api_mcast_des;
 extern int opt_api_mcast_port;
 extern int opt_api_mcast_port;
 extern char *opt_api_groups;
 extern char *opt_api_groups;
 extern char *opt_api_description;
 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 get_intrange(const char *arg, int *val1, int *val2);
 extern bool detect_stratum(struct pool *pool, char *url);
 extern bool detect_stratum(struct pool *pool, char *url);
 extern void print_summary(void);
 extern void print_summary(void);
+extern void adjust_quota_gcd(void);
 extern struct pool *add_pool(void);
 extern struct pool *add_pool(void);
 extern bool add_pool_details(struct pool *pool, bool live, char *url, char *user, char *pass);
 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 struct strategies strategies[];
 extern enum pool_strategy pool_strategy;
 extern enum pool_strategy pool_strategy;
 extern int opt_rotate_period;
 extern int opt_rotate_period;
+extern double total_rolling;
 extern double total_mhashes_done;
 extern double total_mhashes_done;
 extern unsigned int new_blocks;
 extern unsigned int new_blocks;
 extern unsigned int found_blocks;
 extern unsigned int found_blocks;
@@ -1181,6 +1191,9 @@ struct pool {
 	int solved;
 	int solved;
 	int diff1;
 	int diff1;
 	char diff[8];
 	char diff[8];
+	int quota;
+	int quota_gcd;
+	int quota_used;
 
 
 	double diff_accepted;
 	double diff_accepted;
 	double diff_rejected;
 	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 *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 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 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 bool abandon_work(struct work *, struct timeval *work_runtime, uint64_t hashes);
 extern void hash_queued_work(struct thr_info *mythr);
 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);
 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();
 session_start();
 date_default_timezone_set(@date_default_timezone_get());
 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 $rigipsecurity, $rigtotals, $forcerigtotals;
 global $socksndtimeoutsec, $sockrcvtimeoutsec;
 global $socksndtimeoutsec, $sockrcvtimeoutsec;
 global $checklastshare, $poolinputs, $hidefields;
 global $checklastshare, $poolinputs, $hidefields;
 global $ignorerefresh, $changerefresh, $autorefresh;
 global $ignorerefresh, $changerefresh, $autorefresh;
 global $allowcustompages, $customsummarypages;
 global $allowcustompages, $customsummarypages;
 global $miner_font_family, $miner_font_size;
 global $miner_font_family, $miner_font_size;
+global $bad_font_family, $bad_font_size;
 global $colouroverride, $placebuttons, $userlist;
 global $colouroverride, $placebuttons, $userlist;
 global $per_proc;
 global $per_proc;
 #
 #
+$doctype = "<!DOCTYPE html>\n";
+#
 # See README.RPC for more details of these variables and how
 # See README.RPC for more details of these variables and how
 # to configure miner.php
 # 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
 # Set $mcast to true to look for your rigs and ignore $rigs
 $mcast = false;
 $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
 # API Multicast address all cgminers are listening on
 $mcastaddr = '224.0.0.75';
 $mcastaddr = '224.0.0.75';
 #
 #
@@ -69,6 +75,13 @@ $mcastlistport = 4027;
 # to wait for replies to the Multicast message
 # to wait for replies to the Multicast message
 $mcasttimeout = 1.5;
 $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
 # Set $rigipsecurity to false to show the IP/Port of the rig
 # in the socket error messages and also show the full socket message
 # in the socket error messages and also show the full socket message
 $rigipsecurity = true;
 $rigipsecurity = true;
@@ -141,7 +154,7 @@ $poolspage = array(
  'POOL+STATS' => array('STATS.ID=ID', 'POOL.URL=URL',
  'POOL+STATS' => array('STATS.ID=ID', 'POOL.URL=URL',
 			'POOL.Has Stratum=Stratum', 'POOL.Stratum Active=StrAct',
 			'POOL.Has Stratum=Stratum', 'POOL.Stratum Active=StrAct',
 			'STATS.Net Bytes Sent=NSent',
 			'STATS.Net Bytes Sent=NSent',
-			'STATS.Net Bytes Recv=NRecv'));
+			'STATS.Net Bytes Recv=NRecv', 'GEN.AvShr=AvShr'));
 #
 #
 $poolssum = array(
 $poolssum = array(
  'SUMMARY' => array('MHS av', 'Found Blocks', 'Accepted',
  'SUMMARY' => array('MHS av', 'Found Blocks', 'Accepted',
@@ -156,7 +169,9 @@ $poolsext = array(
 	'group' => array('POOL.URL', 'POOL.Has Stratum', 'POOL.Stratum Active'),
 	'group' => array('POOL.URL', 'POOL.Has Stratum', 'POOL.Stratum Active'),
 	'calc' => array(
 	'calc' => array(
 			'STATS.Net Bytes Sent' => 'sum',
 			'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)))
 	'having' => array(array('STATS.Bytes Recv', '>', 0)))
 );
 );
 
 
@@ -176,9 +191,12 @@ $warnfont = '<font color=red><b>';
 $warnoff = '</b></font>';
 $warnoff = '</b></font>';
 $dfmt = 'H:i:s j-M-Y \U\T\CP';
 $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';
 $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
 # Edit this or redefine it in myminer.php to change the colour scheme
 # See $colourtable below for the list of names
 # See $colourtable below for the list of names
 $colouroverride = array();
 $colouroverride = array();
@@ -198,7 +216,7 @@ if (file_exists('myminer.php'))
 # This is the system default that must always contain all necessary
 # This is the system default that must always contain all necessary
 # colours so it must be a constant
 # colours so it must be a constant
 # You can override these values with $colouroverride
 # 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
 # - which you can override directly anyway
 global $colourtable;
 global $colourtable;
 $colourtable = array(
 $colourtable = array(
@@ -210,6 +228,8 @@ $colourtable = array(
 	'td.h background'	=> '#c4ffff',
 	'td.h background'	=> '#c4ffff',
 	'td.err color'		=> 'black',
 	'td.err color'		=> 'black',
 	'td.err background'	=> '#ff3050',
 	'td.err background'	=> '#ff3050',
+	'td.bad color'		=> 'black',
+	'td.bad background'	=> '#ff3050',
 	'td.warn color'		=> 'black',
 	'td.warn color'		=> 'black',
 	'td.warn background'	=> '#ffb050',
 	'td.warn background'	=> '#ffb050',
 	'td.sta color'		=> 'green',
 	'td.sta color'		=> 'green',
@@ -269,9 +289,10 @@ function getdom($domname)
  return getcss($domname, true);
  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 $error, $readonly, $poolinputs, $here;
  global $ignorerefresh, $autorefresh;
  global $ignorerefresh, $autorefresh;
 
 
@@ -300,14 +321,16 @@ function htmlhead($checkapi, $rig, $pg = null, $noscript = false)
 		$readonly = true;
 		$readonly = true;
  }
  }
  $miner_font = "font-family:$miner_font_family; font-size:$miner_font_size;";
  $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>
 <title>$title</title>
 <style type='text/css'>
 <style type='text/css'>
 td { $miner_font ".getcss('td')."}
 td { $miner_font ".getcss('td')."}
 td.two { $miner_font ".getcss('td.two')."}
 td.two { $miner_font ".getcss('td.two')."}
 td.h { $miner_font ".getcss('td.h')."}
 td.h { $miner_font ".getcss('td.h')."}
 td.err { $miner_font ".getcss('td.err')."}
 td.err { $miner_font ".getcss('td.err')."}
+td.bad { $bad_font ".getcss('td.bad')."}
 td.warn { $miner_font ".getcss('td.warn')."}
 td.warn { $miner_font ".getcss('td.warn')."}
 td.sta { $miner_font ".getcss('td.sta')."}
 td.sta { $miner_font ".getcss('td.sta')."}
 td.tot { $miner_font ".getcss('td.tot')."}
 td.tot { $miner_font ".getcss('td.tot')."}
@@ -339,16 +362,24 @@ echo "</script>\n";
 <tr><td align=center valign=top>
 <tr><td align=center valign=top>
 <table border=0 cellpadding=4 cellspacing=0 summary='Mine'>
 <table border=0 cellpadding=4 cellspacing=0 summary='Mine'>
 <?php
 <?php
+ echo $mcerr;
+}
+#
+function minhead($mcerr = '')
+{
+ global $readonly;
+ $readonly = true;
+ htmlhead($mcerr, false, null, null, true);
 }
 }
 #
 #
 global $haderror, $error;
 global $haderror, $error;
 $haderror = false;
 $haderror = false;
 $error = null;
 $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";
  $listname = "0.0.0.0";
 
 
@@ -386,48 +417,78 @@ function getrigs()
 	return;
 	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);
  socket_close($rep_soc);
 }
 }
 #
 #
+function getrigs()
+{
+ global $rigs;
+
+ mcastrigs();
+
+ sort($rigs);
+}
+#
 function getsock($rig, $addr, $port)
 function getsock($rig, $addr, $port)
 {
 {
  global $rigipsecurity;
  global $rigipsecurity;
@@ -642,9 +703,14 @@ function newrow()
  echo '<tr>';
  echo '<tr>';
 }
 }
 #
 #
+function othrow($row)
+{
+ return "<tr>$row</tr>";
+}
+#
 function otherrow($row)
 function otherrow($row)
 {
 {
- echo "<tr>$row</tr>";
+ echo othrow($row);
 }
 }
 #
 #
 function endrow()
 function endrow()
@@ -1833,8 +1899,6 @@ function doOne($rig, $preprocess)
  global $haderror, $readonly, $notify, $rigs;
  global $haderror, $readonly, $notify, $rigs;
  global $placebuttons;
  global $placebuttons;
 
 
- htmlhead(true, $rig);
-
  if ($placebuttons == 'top' || $placebuttons == 'both')
  if ($placebuttons == 'top' || $placebuttons == 'both')
 	pagebuttons($rig, null);
 	pagebuttons($rig, null);
 
 
@@ -2347,8 +2411,55 @@ function processcompare($which, $ext, $section, $res)
  return $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);
  $res = processcompare('where', $ext, $section, $res);
 
 
  if (isset($ext[$section]['group']))
  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);
  return processcompare('having', $ext, $section, $res);
 }
 }
 #
 #
@@ -2474,6 +2589,8 @@ function processcustompage($pagename, $sections, $sum, $ext, $namemap)
 				$results[$cmd][$num] = $process;
 				$results[$cmd][$num] = $process;
 		}
 		}
 	}
 	}
+	else
+		otherrow('<td class=bad>Bad "$rigs" array</td>');
  }
  }
 
 
  $shownsomething = false;
  $shownsomething = false;
@@ -2510,7 +2627,8 @@ function processcustompage($pagename, $sections, $sum, $ext, $namemap)
 
 
 		if (isset($results[$sectionmap[$section]]))
 		if (isset($results[$sectionmap[$section]]))
 		{
 		{
-			$rigresults = processext($ext, $section, $results[$sectionmap[$section]]);
+			$rigresults = processext($ext, $section, $results[$sectionmap[$section]], $fields);
+
 			$showfields = array();
 			$showfields = array();
 			$showhead = array();
 			$showhead = array();
 			foreach ($fields as $field)
 			foreach ($fields as $field)
@@ -2589,21 +2707,19 @@ function showcustompage($pagename)
  global $customsummarypages;
  global $customsummarypages;
  global $placebuttons;
  global $placebuttons;
 
 
- htmlhead(false, null, $pagename);
-
  if ($placebuttons == 'top' || $placebuttons == 'both')
  if ($placebuttons == 'top' || $placebuttons == 'both')
 	pagebuttons(null, $pagename);
 	pagebuttons(null, $pagename);
 
 
  if (!isset($customsummarypages[$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;
 	return;
  }
  }
 
 
  $c = count($customsummarypages[$pagename]);
  $c = count($customsummarypages[$pagename]);
  if ($c < 2 || $c > 3)
  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>';
 	$rw .= count($customsummarypages[$pagename]).')</td>';
 	otherrow($rw);
 	otherrow($rw);
 	return;
 	return;
@@ -2648,7 +2764,7 @@ function showcustompage($pagename)
 
 
  if (count($page) <= 1)
  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;
 	return;
  }
  }
 
 
@@ -2662,7 +2778,7 @@ function onlylogin()
 {
 {
  global $here;
  global $here;
 
 
- htmlhead(false, null, null, true);
+ htmlhead('', false, null, null, true);
 
 
 ?>
 ?>
 <tr height=15%><td>&nbsp;</td></tr>
 <tr height=15%><td>&nbsp;</td></tr>
@@ -2762,6 +2878,7 @@ function checklogin()
 function display()
 function display()
 {
 {
  global $miner, $port;
  global $miner, $port;
+ global $mcast, $mcastexpect;
  global $readonly, $notify, $rigs;
  global $readonly, $notify, $rigs;
  global $ignorerefresh, $autorefresh;
  global $ignorerefresh, $autorefresh;
  global $allowcustompages, $customsummarypages;
  global $allowcustompages, $customsummarypages;
@@ -2773,11 +2890,24 @@ function display()
  if ($pagesonly === 'login')
  if ($pagesonly === 'login')
 	return;
 	return;
 
 
+ $mcerr = '';
+
  if ($rigs == null or count($rigs) == 0)
  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;
 	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)
  if ($ignorerefresh == false)
  {
  {
@@ -2834,7 +2964,8 @@ function display()
 
 
 	if ($pg !== null && $pg !== '')
 	if ($pg !== null && $pg !== '')
 	{
 	{
-		showcustompage($pg);
+		htmlhead($mcerr, false, null, $pg);
+		showcustompage($pg, $mcerr);
 		return;
 		return;
 	}
 	}
  }
  }
@@ -2853,10 +2984,14 @@ function display()
 		$miner = $parts[0];
 		$miner = $parts[0];
 		$port = $parts[1];
 		$port = $parts[1];
 
 
+		htmlhead($mcerr, true, 0);
 		doOne(0, $preprocess);
 		doOne(0, $preprocess);
 	}
 	}
 	else
 	else
-		otherrow('<td>Invalid "$rigs" array</td>');
+	{
+		minhead($mcerr);
+		otherrow('<td class=bad>Invalid "$rigs" array</td>');
+	}
 
 
 	return;
 	return;
  }
  }
@@ -2869,15 +3004,19 @@ function display()
 		$miner = $parts[0];
 		$miner = $parts[0];
 		$port = $parts[1];
 		$port = $parts[1];
 
 
+		htmlhead($mcerr, true, 0);
 		doOne($rig, $preprocess);
 		doOne($rig, $preprocess);
 	}
 	}
 	else
 	else
-		otherrow('<td>Invalid "$rigs" array</td>');
+	{
+		minhead($mcerr);
+		otherrow('<td class=bad>Invalid "$rigs" array</td>');
+	}
 
 
 	return;
 	return;
  }
  }
 
 
- htmlhead(false, null);
+ htmlhead($mcerr, false, null);
 
 
  if ($placebuttons == 'top' || $placebuttons == 'both')
  if ($placebuttons == 'top' || $placebuttons == 'both')
 	pagebuttons(null, null);
 	pagebuttons(null, null);
@@ -2903,7 +3042,7 @@ function display()
 	pagebuttons(null, null);
 	pagebuttons(null, null);
 }
 }
 #
 #
-if (isset($mcast) && $mcast === true)
+if ($mcast === true)
  getrigs();
  getrigs();
 display();
 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);
 		curl_easy_setopt(curl, CURLOPT_PROXY, pool->rpc_proxy);
 	} else if (opt_socks_proxy) {
 	} else if (opt_socks_proxy) {
 		curl_easy_setopt(curl, CURLOPT_PROXY, 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) {
 	if (userpass) {
 		curl_easy_setopt(curl, CURLOPT_USERPWD, 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;
 	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_begin, *url_end, *ipv6_begin, *ipv6_end, *port_start = NULL;
 	char url_address[256], port[6];
 	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);
 	sprintf(url_address, "%.*s", url_len, url_begin);
 
 
-	if (port_len)
+	if (port_len) {
+		char *slash;
+
 		snprintf(port, 6, "%.*s", port_len, port_start);
 		snprintf(port, 6, "%.*s", port_len, port_start);
-	else
+		slash = strchr(port, '/');
+		if (slash)
+			*slash = '\0';
+	} else
 		strcpy(port, "80");
 		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;
 	return true;
 }
 }
@@ -1854,7 +1859,7 @@ static bool parse_reconnect(struct pool *pool, json_t *val)
 
 
 	snprintf(address, sizeof(address), "%s:%s", url, port);
 	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;
 		return false;
 
 
 	pool->stratum_url = pool->sockaddr_url;
 	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);
 		curl_easy_setopt(curl, CURLOPT_PROXY, pool->rpc_proxy);
 	} else if (opt_socks_proxy) {
 	} else if (opt_socks_proxy) {
 		curl_easy_setopt(curl, CURLOPT_PROXY, 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);
 	curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 1);
 	pool->sock = INVSOCK;
 	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);
 bool sock_full(struct pool *pool);
 char *recv_line(struct pool *pool);
 char *recv_line(struct pool *pool);
 bool parse_method(struct pool *pool, char *s);
 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 auth_stratum(struct pool *pool);
 bool initiate_stratum(struct pool *pool);
 bool initiate_stratum(struct pool *pool);
 bool restart_stratum(struct pool *pool);
 bool restart_stratum(struct pool *pool);